@tryvira/opencode 0.0.0-stage → 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 (3) hide show
  1. package/README.md +16 -2
  2. package/dist/index.js +1304 -0
  3. package/package.json +12 -4
package/README.md CHANGED
@@ -1,3 +1,17 @@
1
- # Temporary Holding Version
1
+ # `@tryvira/opencode`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ OpenCode V2 server plugin for Vira host lifecycle governance.
4
+
5
+ It resolves Vira behavior on every `context` hook, observes tool results, and in `enforce` mode blocks tool execution unless a host action authorizer returns an exact Vira `ALLOW` receipt. OpenCode V2 does not expose a supported response-before-display hook, so post-model verification is reported as unsupported rather than claimed as enforced.
6
+
7
+ Configuration is supplied through OpenCode plugin options or environment variables: `VIRA_CONTROL_URL`, `VIRA_API_KEY`, `VIRA_ORGANIZATION_ID`, `VIRA_PROJECT_ID`, `VIRA_ENVIRONMENT_ID`, `VIRA_AI_SYSTEM_ID`, `VIRA_CONNECTION_ID`, and `VIRA_HOST_MODE` (`off`, `observe`, `enforce`).
8
+
9
+ `enforce` requires a host-provided `authorizeAction` callback that calls the
10
+ server-owned action authorization boundary and returns the exact Vira
11
+ `ALLOW`/`BLOCK`/`ESCALATE` decision with receipt digests. The plugin never
12
+ turns `observe` into enforcement and never grants an action from model text.
13
+
14
+ When the runtime context hook exposes a server-issued `executionId` and a
15
+ `vira.request-behavior-context.v2`, the plugin can call
16
+ `/v1/runtime/actions/authorize` automatically. If that binding is absent,
17
+ enforce mode blocks the tool instead of guessing an execution or release.
package/dist/index.js ADDED
@@ -0,0 +1,1304 @@
1
+ // src/index.ts
2
+ import { createHash as createHash2 } from "node:crypto";
3
+
4
+ // ../../sdks/js-client.ts
5
+ var ViraApiError = class extends Error {
6
+ status;
7
+ body;
8
+ requestId;
9
+ constructor(message, status, body, requestId) {
10
+ super(message);
11
+ this.name = "ViraApiError";
12
+ this.status = status;
13
+ this.body = body;
14
+ this.requestId = requestId;
15
+ }
16
+ };
17
+ var required = (value, code) => {
18
+ if (typeof value !== "string" || !value.trim()) throw new Error(code);
19
+ return value;
20
+ };
21
+ var normalizeBaseUrl = (value, allowInsecureLocalhost = false) => {
22
+ let url;
23
+ try {
24
+ url = new URL(required(value, "SDK_BASE_URL_REQUIRED"));
25
+ } catch {
26
+ throw new Error("SDK_BASE_URL_INVALID");
27
+ }
28
+ const hostname = url.hostname.toLowerCase();
29
+ const loopback = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
30
+ if (url.username || url.password || url.search || url.hash) throw new Error("SDK_BASE_URL_INVALID");
31
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback && allowInsecureLocalhost))
32
+ throw new Error("SDK_HTTPS_REQUIRED");
33
+ return value.replace(/\/$/, "");
34
+ };
35
+ var canonicalJson = (value) => JSON.stringify(
36
+ value,
37
+ (_key, item) => item && typeof item === "object" && !Array.isArray(item) ? Object.fromEntries(Object.entries(item).sort(([left], [right]) => left.localeCompare(right))) : item
38
+ );
39
+ var contentIdempotencyKey = async (operation, request) => {
40
+ if (!globalThis.crypto?.subtle) throw new Error("SDK_IDEMPOTENCY_CRYPTO_UNAVAILABLE");
41
+ const bytes = new TextEncoder().encode(canonicalJson(request));
42
+ const digest3 = await globalThis.crypto.subtle.digest("SHA-256", bytes);
43
+ const hex = [...new Uint8Array(digest3)].map((value) => value.toString(16).padStart(2, "0")).join("");
44
+ return `${operation}:${hex}`;
45
+ };
46
+ var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
47
+ var ViraClient = class {
48
+ evaluations;
49
+ teach;
50
+ packs;
51
+ baseUrl;
52
+ apiKey;
53
+ fetcher;
54
+ timeoutMs;
55
+ retries;
56
+ failureMode;
57
+ defaultHeaders;
58
+ boundConnectionId;
59
+ constructor(options) {
60
+ this.baseUrl = normalizeBaseUrl(options.baseUrl, options.allowInsecureLocalhost === true);
61
+ this.apiKey = options.apiKey;
62
+ this.fetcher = options.fetcher ?? fetch;
63
+ this.timeoutMs = Math.max(
64
+ 1e3,
65
+ Math.min(options.timeoutMs ?? 15e3, 12e4)
66
+ );
67
+ this.retries = Math.max(0, Math.min(options.retries ?? 2, 5));
68
+ this.failureMode = options.failureMode ?? "throw";
69
+ this.defaultHeaders = options.defaultHeaders ?? {};
70
+ this.boundConnectionId = options.connectionId;
71
+ this.evaluations = {
72
+ createPlan: (input, options2 = {}) => this.createEvaluationPlan(input, options2),
73
+ get: (id) => this.getEvaluation(id),
74
+ compare: (id) => this.getEvaluationComparison(id),
75
+ reviewFinding: (evaluationId, findingId, input, options2 = {}) => this.reviewComparativeFinding(evaluationId, findingId, input, options2),
76
+ proposeImprovement: (id, input, options2 = {}) => this.createImprovementProposal(id, input, options2)
77
+ };
78
+ this.teach = {
79
+ review: (id, action = "request-review", options2 = {}) => this.reviewImprovementProposal(id, action, options2),
80
+ createCandidate: (input, options2 = {}) => this.createBehaviorRevisionCandidate(input, options2),
81
+ evaluateCandidate: (id, input, options2 = {}) => this.evaluateBehaviorRevisionCandidate(id, input, options2)
82
+ };
83
+ this.packs = {
84
+ createRevision: (input, options2 = {}) => this.createPackRevision(input, options2),
85
+ getRevision: (id) => this.getPackRevision(id),
86
+ diffRevision: (id) => this.getPackRevisionDiff(id),
87
+ transitionRevision: (id, transition, options2 = {}) => this.transitionPackRevision(id, transition, options2)
88
+ };
89
+ }
90
+ async request(request, options = {}) {
91
+ const url = new URL(this.baseUrl + request.path);
92
+ for (const [key, value] of Object.entries(request.query ?? {}))
93
+ url.searchParams.set(key, value);
94
+ const headers = {
95
+ accept: "application/json",
96
+ ...this.defaultHeaders
97
+ };
98
+ if (request.body) headers["content-type"] = "application/json";
99
+ if (this.apiKey) {
100
+ headers.authorization = `Bearer ${this.apiKey}`;
101
+ headers["x-vira-api-key"] = this.apiKey;
102
+ }
103
+ if (options.idempotencyKey)
104
+ headers["idempotency-key"] = options.idempotencyKey;
105
+ const retryable = request.method === "GET" || Boolean(options.idempotencyKey);
106
+ const attempts = retryable ? this.retries + 1 : 1;
107
+ let lastError;
108
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
109
+ const controller = new AbortController();
110
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
111
+ try {
112
+ const response = await this.fetcher(url, {
113
+ method: request.method,
114
+ headers,
115
+ body: request.body ? JSON.stringify(request.body) : void 0,
116
+ signal: controller.signal
117
+ });
118
+ const raw = await response.text();
119
+ let body = null;
120
+ try {
121
+ body = raw ? JSON.parse(raw) : null;
122
+ } catch {
123
+ body = raw;
124
+ }
125
+ const requestId = response.headers.get("x-request-id") ?? "";
126
+ if (!response.ok) {
127
+ const error = new ViraApiError(
128
+ `VIRA_API_${response.status}`,
129
+ response.status,
130
+ body,
131
+ requestId
132
+ );
133
+ if ((response.status >= 500 || response.status === 429) && attempt + 1 < attempts) {
134
+ const retryAfter = Number(response.headers.get("retry-after") ?? "");
135
+ const serverDelay = Number.isFinite(retryAfter) && retryAfter >= 0 ? Math.min(retryAfter * 1e3, 12e4) : 0;
136
+ const jitter = Math.floor(Math.random() * 25);
137
+ await wait(Math.max(serverDelay, 25 * 2 ** attempt) + jitter);
138
+ continue;
139
+ }
140
+ if (this.failureMode === "open" && !options.enforcing && response.status >= 500)
141
+ return null;
142
+ throw error;
143
+ }
144
+ return { status: response.status, body, requestId };
145
+ } catch (error) {
146
+ lastError = error;
147
+ if (error instanceof ViraApiError) throw error;
148
+ if (attempt + 1 < attempts) {
149
+ await wait(25 * 2 ** attempt);
150
+ continue;
151
+ }
152
+ if (this.failureMode === "open" && !options.enforcing) return null;
153
+ throw error;
154
+ } finally {
155
+ clearTimeout(timer);
156
+ }
157
+ }
158
+ throw lastError;
159
+ }
160
+ connectSystem(input) {
161
+ const request = buildAISystemRequest(input);
162
+ return this.request(request, {
163
+ idempotencyKey: input.idempotencyKey
164
+ });
165
+ }
166
+ listAISystems() {
167
+ return this.request({ method: "GET", path: "/v1/ai-systems" });
168
+ }
169
+ async deleteAISystem(systemId, options = {}) {
170
+ required(systemId, "SDK_SYSTEM_ID_REQUIRED");
171
+ const path = `/v1/ai-systems/${encodeURIComponent(systemId)}`, body = { confirm: options.confirm === true };
172
+ return this.request({ method: "DELETE", path, body }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey(`sdk:delete:ai-system:${systemId}`, { path, body }) });
173
+ }
174
+ async deleteAISystemCascade(systemId, options = {}) {
175
+ required(systemId, "SDK_SYSTEM_ID_REQUIRED");
176
+ const path = `/v1/ai-systems/${encodeURIComponent(systemId)}/cleanup`, body = { confirm: options.confirm === true, deleteObservations: options.deleteObservations === true };
177
+ return this.request({ method: "DELETE", path, body }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey(`sdk:delete:ai-system-cascade:${systemId}`, { path, body }) });
178
+ }
179
+ getAISystem(systemId) {
180
+ required(systemId, "SDK_SYSTEM_ID_REQUIRED");
181
+ return this.request({ method: "GET", path: `/v1/ai-systems/${encodeURIComponent(systemId)}` });
182
+ }
183
+ connectAdapter(systemId, input) {
184
+ required(systemId, "SDK_SYSTEM_ID_REQUIRED");
185
+ const body = {
186
+ ...input,
187
+ capabilities: [...input.capabilities ?? []]
188
+ };
189
+ delete body.idempotencyKey;
190
+ return this.request(
191
+ {
192
+ method: "POST",
193
+ path: `/v1/ai-systems/${encodeURIComponent(systemId)}/connections`,
194
+ body
195
+ },
196
+ { idempotencyKey: input.idempotencyKey }
197
+ );
198
+ }
199
+ async connectOnce(input) {
200
+ const systemResult = await this.connectSystem(input);
201
+ if (!systemResult) return null;
202
+ const systemId = String(systemResult.body.system.id ?? "");
203
+ required(systemId, "SDK_SYSTEM_ID_MISSING");
204
+ const connectionResult = await this.connectAdapter(systemId, {
205
+ adapterType: input.adapterType,
206
+ adapterVersion: input.adapterVersion,
207
+ capabilities: input.capabilities,
208
+ config: input.config,
209
+ idempotencyKey: input.idempotencyKey
210
+ });
211
+ if (!connectionResult) return null;
212
+ const connectedId = String(connectionResult.body.connection?.id ?? "").trim();
213
+ if (connectedId) this.boundConnectionId = connectedId;
214
+ return {
215
+ status: connectionResult.status,
216
+ requestId: connectionResult.requestId,
217
+ body: {
218
+ system: systemResult.body.system,
219
+ connection: connectionResult.body.connection,
220
+ handshake: connectionResult.body.handshake
221
+ }
222
+ };
223
+ }
224
+ observe(event, options = {}) {
225
+ return this.request(buildObservationRequest(event), {
226
+ idempotencyKey: options.idempotencyKey ?? event.eventId
227
+ });
228
+ }
229
+ trace(event, options = {}) {
230
+ return this.observe({ ...event, eventType: "model.response" }, options);
231
+ }
232
+ searchRuntimeTraces(query = {}) {
233
+ const params = new URLSearchParams();
234
+ for (const [key, value] of Object.entries(query)) if (value !== void 0) params.set(key, String(value));
235
+ return this.request({ method: "GET", path: `/v1/runtime/traces/search${params.size ? `?${params.toString()}` : ""}` });
236
+ }
237
+ outcome(event, options = {}) {
238
+ return this.observe({ ...event, eventType: "business.outcome" }, options);
239
+ }
240
+ feedback(event, options = {}) {
241
+ return this.observe({ ...event, eventType: "feedback.user" }, options);
242
+ }
243
+ observeBatch(events, options = {}) {
244
+ if (!Array.isArray(events) || events.length < 1 || events.length > 100)
245
+ throw new Error("SDK_OBSERVATION_BATCH_INVALID");
246
+ if (events.some((event) => !event.organizationId?.trim() || !event.projectId?.trim() || !event.environmentId?.trim()))
247
+ throw new Error("SDK_OBSERVATION_SCOPE_REQUIRED");
248
+ return this.request(
249
+ {
250
+ method: "POST",
251
+ path: "/v1/observations/batch",
252
+ body: { events: [...events] }
253
+ },
254
+ { idempotencyKey: options.idempotencyKey }
255
+ );
256
+ }
257
+ observeSdk(delivery, options = {}) {
258
+ return this.request(
259
+ { method: "POST", path: "/v1/observations/sdk", body: delivery },
260
+ { idempotencyKey: options.idempotencyKey }
261
+ );
262
+ }
263
+ discover(reviewState) {
264
+ return this.request({
265
+ method: "GET",
266
+ path: "/v1/discovery/candidates",
267
+ query: reviewState ? { reviewState } : void 0
268
+ });
269
+ }
270
+ async createSignalDiscoveryProposal(input, options = {}) {
271
+ return this.request({ method: "POST", path: "/v1/signals/discovery-proposals", body: input }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey("sdk:signal-discovery-proposal", input) });
272
+ }
273
+ async reviewSignalDiscoveryProposal(proposalId, decision, options = {}) {
274
+ required(proposalId, "SDK_SIGNAL_PROPOSAL_ID_REQUIRED");
275
+ const path = `/v1/signals/discovery-proposals/${encodeURIComponent(proposalId)}/review`, body = { decision };
276
+ return this.request({ method: "POST", path, body }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey(`signal-review:${proposalId}`, body) });
277
+ }
278
+ async materializeSignalDiscovery(proposalId, input = {}, options = {}) {
279
+ required(proposalId, "SDK_SIGNAL_PROPOSAL_ID_REQUIRED");
280
+ const path = `/v1/signals/discovery-proposals/${encodeURIComponent(proposalId)}/materialize`;
281
+ return this.request({ method: "POST", path, body: input }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey(`signal-materialize:${proposalId}`, input) });
282
+ }
283
+ async createSignalMapping(input, options = {}) {
284
+ return this.request({ method: "POST", path: "/v1/signals/mappings", body: input }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey("sdk:signal-mapping", input) });
285
+ }
286
+ getSignalMapping(mappingId) {
287
+ required(mappingId, "SDK_SIGNAL_MAPPING_ID_REQUIRED");
288
+ return this.request({ method: "GET", path: `/v1/signals/mappings/${encodeURIComponent(mappingId)}` });
289
+ }
290
+ async reviewSignalMapping(mappingId, input = {}, options = {}) {
291
+ required(mappingId, "SDK_SIGNAL_MAPPING_ID_REQUIRED");
292
+ const path = `/v1/signals/mappings/${encodeURIComponent(mappingId)}/review`;
293
+ return this.request({ method: "POST", path, body: input }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey(`signal-mapping-review:${mappingId}`, input) });
294
+ }
295
+ connectionHealth(connectionId) {
296
+ required(connectionId, "SDK_CONNECTION_ID_REQUIRED");
297
+ return this.request({
298
+ method: "GET",
299
+ path: `/v1/connections/${encodeURIComponent(connectionId)}/health`
300
+ });
301
+ }
302
+ getConnection(connectionId) {
303
+ required(connectionId, "SDK_CONNECTION_ID_REQUIRED");
304
+ return this.request({
305
+ method: "GET",
306
+ path: `/v1/connections/${encodeURIComponent(connectionId)}`
307
+ });
308
+ }
309
+ listSystemConnections(systemId) {
310
+ required(systemId, "SDK_SYSTEM_ID_REQUIRED");
311
+ return this.request({
312
+ method: "GET",
313
+ path: `/v1/ai-systems/${encodeURIComponent(systemId)}/connections`
314
+ });
315
+ }
316
+ inventory(systemId) {
317
+ required(systemId, "SDK_SYSTEM_ID_REQUIRED");
318
+ return this.request({
319
+ method: "GET",
320
+ path: `/v1/ai-systems/${encodeURIComponent(systemId)}/inventory`
321
+ });
322
+ }
323
+ listPacks() {
324
+ return this.request({
325
+ method: "GET",
326
+ path: "/v1/packs"
327
+ });
328
+ }
329
+ async deletePack(packId, options = {}) {
330
+ required(packId, "SDK_PACK_ID_REQUIRED");
331
+ const path = `/v1/packs/${encodeURIComponent(packId)}`;
332
+ const body = { confirm: options.confirm === true };
333
+ return this.request({ method: "DELETE", path, body }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey(`sdk:delete:pack:${packId}`, { path, body }) });
334
+ }
335
+ async importPack(input) {
336
+ if (!input?.manifest && !input?.archiveBase64)
337
+ throw new Error("SDK_PACK_MANIFEST_OR_ARCHIVE_REQUIRED");
338
+ if (input.archiveBase64 && !input.fileName)
339
+ throw new Error("SDK_PACK_FILE_NAME_REQUIRED");
340
+ const body = {
341
+ ...input.manifest ? { manifest: input.manifest } : {},
342
+ objects: input.objects ?? {},
343
+ ...input.archiveBase64 ? { archiveBase64: input.archiveBase64, fileName: input.fileName } : {}
344
+ };
345
+ return this.request(
346
+ {
347
+ method: "POST",
348
+ path: "/v1/packs/import",
349
+ body
350
+ },
351
+ { idempotencyKey: input.idempotencyKey ?? await contentIdempotencyKey("sdk:pack:import", body) }
352
+ );
353
+ }
354
+ async installPack(packId, variables = {}) {
355
+ required(packId, "SDK_PACK_ID_REQUIRED");
356
+ const path = `/v1/packs/${encodeURIComponent(packId)}/install`;
357
+ const body = { variables };
358
+ return this.request(
359
+ { method: "POST", path, body },
360
+ { idempotencyKey: await contentIdempotencyKey(`sdk:pack:install:${packId}`, body) }
361
+ );
362
+ }
363
+ async evaluatePack(packId, input) {
364
+ required(packId, "SDK_PACK_ID_REQUIRED");
365
+ required(input?.evaluationId, "SDK_EVALUATION_ID_REQUIRED");
366
+ const path = `/v1/packs/${encodeURIComponent(packId)}/evaluate`;
367
+ const body = { evaluationId: input.evaluationId };
368
+ return this.request({ method: "POST", path, body }, { idempotencyKey: await contentIdempotencyKey(`sdk:pack:evaluate:${packId}`, body) });
369
+ }
370
+ async approvePack(packId, options = {}) {
371
+ required(packId, "SDK_PACK_ID_REQUIRED");
372
+ const body = options.manualApproval ? { manualApproval: true, reason: required(options.reason ?? "", "SDK_MANUAL_PACK_APPROVAL_REASON_REQUIRED") } : {};
373
+ return this.request(
374
+ {
375
+ method: "POST",
376
+ path: `/v1/packs/${encodeURIComponent(packId)}/approve`,
377
+ body
378
+ },
379
+ { idempotencyKey: await contentIdempotencyKey(`sdk:pack:approve:${packId}`, body) }
380
+ );
381
+ }
382
+ async releasePack(packId) {
383
+ required(packId, "SDK_PACK_ID_REQUIRED");
384
+ return this.request(
385
+ {
386
+ method: "POST",
387
+ path: `/v1/packs/${encodeURIComponent(packId)}/release`,
388
+ body: {}
389
+ },
390
+ { idempotencyKey: await contentIdempotencyKey(`sdk:pack:release:${packId}`, {}) }
391
+ );
392
+ }
393
+ async previewPackUpgrade(packId, incoming) {
394
+ required(packId, "SDK_PACK_ID_REQUIRED");
395
+ if (!incoming || typeof incoming !== "object")
396
+ throw new Error("SDK_PACK_UPGRADE_INPUT_REQUIRED");
397
+ return this.request(
398
+ {
399
+ method: "POST",
400
+ path: `/v1/packs/${encodeURIComponent(packId)}/upgrade-preview`,
401
+ body: { incoming }
402
+ },
403
+ { idempotencyKey: await contentIdempotencyKey(`sdk:pack:upgrade-preview:${packId}`, { incoming }) }
404
+ );
405
+ }
406
+ executeRuntime(input) {
407
+ return this.request(
408
+ {
409
+ method: "POST",
410
+ path: "/v1/runtime/execute",
411
+ body: buildRuntimeRequest(
412
+ input
413
+ )
414
+ },
415
+ { idempotencyKey: input.idempotencyKey, enforcing: true }
416
+ );
417
+ }
418
+ resolveRuntime(input) {
419
+ if (!input.behaviorPoint && !input.executionBoundary && !input.semanticContext)
420
+ throw new Error("SDK_RUNTIME_ROUTE_REQUIRED");
421
+ return this.request(
422
+ {
423
+ method: "POST",
424
+ path: "/v1/runtime/resolve",
425
+ body: {
426
+ ...input.behaviorPoint ? { behaviorPoint: input.behaviorPoint } : {},
427
+ ...input.executionBoundary ? { executionBoundary: input.executionBoundary } : {},
428
+ ...input.semanticContext ? { semanticContext: input.semanticContext } : {}
429
+ }
430
+ },
431
+ {
432
+ idempotencyKey: input.idempotencyKey ?? `sdk:resolve:${input.behaviorPoint ?? "boundary"}`,
433
+ enforcing: true
434
+ }
435
+ );
436
+ }
437
+ /** Resolve a runtime route from a governed execution boundary and semantic context. */
438
+ resolveBoundary(input) {
439
+ if (!input.executionBoundary || typeof input.executionBoundary !== "object")
440
+ throw new Error("SDK_EXECUTION_BOUNDARY_REQUIRED");
441
+ return this.resolveRuntime({
442
+ executionBoundary: input.executionBoundary,
443
+ ...input.semanticContext ? { semanticContext: input.semanticContext } : {},
444
+ idempotencyKey: input.idempotencyKey
445
+ });
446
+ }
447
+ resolveSemantic(input) {
448
+ required(input.input, "SDK_SEMANTIC_INPUT_REQUIRED");
449
+ const { idempotencyKey, requestId, ...body } = input;
450
+ const resolvedRequestId = requestId ?? `sdk-semantic-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
451
+ return this.request(
452
+ {
453
+ method: "POST",
454
+ path: "/v1/semantic/resolve",
455
+ body: { ...body, requestId: resolvedRequestId }
456
+ },
457
+ { idempotencyKey: idempotencyKey ?? `sdk:semantic:${resolvedRequestId}` }
458
+ );
459
+ }
460
+ async resolveSemanticRuntime(input) {
461
+ const semantic = await this.resolveSemantic(input);
462
+ if (!semantic) return null;
463
+ const body = semantic.body;
464
+ const receipt = body.receipt && typeof body.receipt === "object" ? body.receipt : {};
465
+ if (receipt.outcome !== "select" || typeof receipt.taskKey !== "string")
466
+ return { semantic, runtime: null };
467
+ const capsules = Array.isArray(body.capsules) ? body.capsules : [];
468
+ const firstCapsule = capsules[0] && typeof capsules[0] === "object" ? capsules[0] : {};
469
+ const behaviorPoint = typeof firstCapsule.behaviorKey === "string" && firstCapsule.behaviorKey.trim() ? firstCapsule.behaviorKey : receipt.taskKey;
470
+ const runtime = input.executionBoundary ? await this.resolveBoundary({
471
+ executionBoundary: input.executionBoundary,
472
+ semanticContext: {
473
+ taskKey: receipt.taskKey,
474
+ behaviorPoint,
475
+ capsule: firstCapsule
476
+ },
477
+ idempotencyKey: input.idempotencyKey ? `sdk:runtime:${input.idempotencyKey}` : void 0
478
+ }) : await this.resolveRuntime({
479
+ semanticContext: { taskKey: receipt.taskKey, behaviorPoint, capsule: firstCapsule },
480
+ idempotencyKey: input.idempotencyKey ? `sdk:runtime:${input.idempotencyKey}` : void 0
481
+ });
482
+ return { semantic, runtime };
483
+ }
484
+ async authorizeHostAction(input) {
485
+ required(input.scope.organizationId, "SDK_ACTION_ORGANIZATION_REQUIRED");
486
+ required(input.scope.projectId, "SDK_ACTION_PROJECT_REQUIRED");
487
+ required(input.scope.environmentId, "SDK_ACTION_ENVIRONMENT_REQUIRED");
488
+ required(input.executionId, "SDK_ACTION_EXECUTION_REQUIRED");
489
+ return this.request({
490
+ method: "POST",
491
+ path: "/v1/runtime/actions/authorize",
492
+ body: { executionId: input.executionId, scope: input.scope, context: input.context, proposal: input.proposal }
493
+ });
494
+ }
495
+ listBehaviorStrategies() {
496
+ return this.request({
497
+ method: "GET",
498
+ path: "/v1/behavior-strategies"
499
+ });
500
+ }
501
+ async deleteBehaviorStrategy(strategyId, options = {}) {
502
+ required(strategyId, "SDK_STRATEGY_ID_REQUIRED");
503
+ const path = `/v1/behavior-strategies/${encodeURIComponent(strategyId)}`, body = { confirm: options.confirm === true };
504
+ return this.request({ method: "DELETE", path, body }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey(`sdk:delete:strategy:${strategyId}`, { path, body }) });
505
+ }
506
+ getBehaviorStrategy(strategyId) {
507
+ required(strategyId, "SDK_STRATEGY_ID_REQUIRED");
508
+ return this.request({
509
+ method: "GET",
510
+ path: `/v1/behavior-strategies/${encodeURIComponent(strategyId)}`
511
+ });
512
+ }
513
+ createBehaviorStrategy(definition, id) {
514
+ if (!definition) throw new Error("SDK_STRATEGY_DEFINITION_REQUIRED");
515
+ return this.request({
516
+ method: "POST",
517
+ path: "/v1/behavior-strategies",
518
+ body: { definition, ...id ? { id } : {} }
519
+ });
520
+ }
521
+ listSemanticTasks() {
522
+ return this.request({ method: "GET", path: "/v1/semantic-tasks" });
523
+ }
524
+ async deleteSemanticTask(taskId, options = {}) {
525
+ required(taskId, "SDK_SEMANTIC_TASK_ID_REQUIRED");
526
+ const path = `/v1/semantic-tasks/${encodeURIComponent(taskId)}`, body = { confirm: options.confirm === true };
527
+ return this.request({ method: "DELETE", path, body }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey(`sdk:delete:semantic-task:${taskId}`, { path, body }) });
528
+ }
529
+ async createSemanticTask(task, options = {}) {
530
+ required(task?.taskKey, "SDK_SEMANTIC_TASK_KEY_REQUIRED");
531
+ const body = { ...task, ...options.id ? { id: options.id } : {} };
532
+ return this.request({ method: "POST", path: "/v1/semantic-tasks", body }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey(`sdk:semantic-task:${task.taskKey}`, body) });
533
+ }
534
+ listBehaviorPoints() {
535
+ return this.request({ method: "GET", path: "/v1/behavior-points" });
536
+ }
537
+ async deleteBehaviorPoint(pointId, options = {}) {
538
+ required(pointId, "SDK_BEHAVIOR_POINT_ID_REQUIRED");
539
+ const path = `/v1/behavior-points/${encodeURIComponent(pointId)}`, body = { confirm: options.confirm === true };
540
+ return this.request({ method: "DELETE", path, body }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey(`sdk:delete:behavior-point:${pointId}`, { path, body }) });
541
+ }
542
+ async createBehaviorPoint(point, options = {}) {
543
+ required(point?.key, "SDK_BEHAVIOR_POINT_KEY_REQUIRED");
544
+ if (!point.taskId && !point.taskIds?.length) throw new Error("SDK_BEHAVIOR_POINT_TASK_REQUIRED");
545
+ const body = { ...point, ...options.id ? { id: options.id } : {} };
546
+ return this.request({ method: "POST", path: "/v1/behavior-points", body }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey(`sdk:behavior-point:${point.key}`, body) });
547
+ }
548
+ createBehaviorStrategyVariant(strategyId, variant, options = {}) {
549
+ required(strategyId, "SDK_STRATEGY_ID_REQUIRED");
550
+ if (!variant || typeof variant !== "object")
551
+ throw new Error("SDK_STRATEGY_VARIANT_REQUIRED");
552
+ return this.request(
553
+ {
554
+ method: "POST",
555
+ path: `/v1/behavior-strategies/${encodeURIComponent(strategyId)}/variants`,
556
+ body: variant
557
+ },
558
+ { idempotencyKey: options.idempotencyKey }
559
+ );
560
+ }
561
+ validateBehaviorStrategy(strategyId) {
562
+ required(strategyId, "SDK_STRATEGY_ID_REQUIRED");
563
+ return this.request({
564
+ method: "POST",
565
+ path: `/v1/behavior-strategies/${encodeURIComponent(strategyId)}/validate`,
566
+ body: {}
567
+ });
568
+ }
569
+ activateBehaviorStrategy(strategyId) {
570
+ required(strategyId, "SDK_STRATEGY_ID_REQUIRED");
571
+ return this.request({ method: "POST", path: `/v1/behavior-strategies/${encodeURIComponent(strategyId)}/activate`, body: {} });
572
+ }
573
+ simulateBehaviorStrategy(strategyId, initialState = {}) {
574
+ required(strategyId, "SDK_STRATEGY_ID_REQUIRED");
575
+ return this.request({
576
+ method: "POST",
577
+ path: `/v1/behavior-strategies/${encodeURIComponent(strategyId)}/simulate`,
578
+ body: { initialState }
579
+ });
580
+ }
581
+ startStrategyExecution(input) {
582
+ required(input.artifactDigest, "SDK_ARTIFACT_DIGEST_REQUIRED");
583
+ for (const value of [
584
+ input.organizationId,
585
+ input.projectId,
586
+ input.environmentId,
587
+ input.revisionId,
588
+ input.idempotencyKey,
589
+ input.nonce,
590
+ input.decisionPlanId,
591
+ input.policy
592
+ ])
593
+ required(value, "SDK_STRATEGY_START_FIELD_REQUIRED");
594
+ if (input.behaviorPoint !== void 0)
595
+ required(input.behaviorPoint, "SDK_BEHAVIOR_POINT_REQUIRED");
596
+ if (!Array.isArray(input.candidates) || input.candidates.length === 0)
597
+ throw new Error("SDK_STRATEGY_CANDIDATES_REQUIRED");
598
+ if (!Number.isFinite(input.expiresAt))
599
+ throw new Error("SDK_EXPIRY_REQUIRED");
600
+ const body = {
601
+ ...buildRuntimeRequest(input),
602
+ artifactDigest: input.artifactDigest,
603
+ ...input.behaviorPoint ? { behaviorPoint: input.behaviorPoint } : {},
604
+ ...input.executionBoundary ? { executionBoundary: input.executionBoundary } : {},
605
+ ...input.traceId ? { traceId: input.traceId } : {},
606
+ ...input.executionId ? { executionId: input.executionId } : {},
607
+ ...input.state ? { state: input.state } : {}
608
+ };
609
+ return this.request(
610
+ { method: "POST", path: "/v1/runtime/strategy-executions", body },
611
+ { idempotencyKey: input.idempotencyKey }
612
+ );
613
+ }
614
+ getStrategyExecution(executionId) {
615
+ required(executionId, "SDK_EXECUTION_ID_REQUIRED");
616
+ return this.request({
617
+ method: "GET",
618
+ path: `/v1/runtime/strategy-executions/${encodeURIComponent(executionId)}`
619
+ });
620
+ }
621
+ getStrategyExecutionTrace(executionId) {
622
+ required(executionId, "SDK_EXECUTION_ID_REQUIRED");
623
+ return this.request({
624
+ method: "GET",
625
+ path: `/v1/runtime/strategy-executions/${encodeURIComponent(executionId)}/trace`
626
+ });
627
+ }
628
+ continueStrategyExecution(executionId, continuation) {
629
+ required(executionId, "SDK_EXECUTION_ID_REQUIRED");
630
+ const input = typeof continuation === "string" ? { authorityDigest: continuation } : continuation ?? {};
631
+ return this.request({
632
+ method: "POST",
633
+ path: `/v1/runtime/strategy-executions/${encodeURIComponent(executionId)}/continue`,
634
+ body: {
635
+ ...input.authorityDigest ? { authorityDigest: input.authorityDigest } : {},
636
+ ...input.state ? { state: input.state } : {},
637
+ ...input.references ? { references: input.references } : {}
638
+ }
639
+ });
640
+ }
641
+ cancelStrategyExecution(executionId) {
642
+ required(executionId, "SDK_EXECUTION_ID_REQUIRED");
643
+ return this.request({
644
+ method: "POST",
645
+ path: `/v1/runtime/strategy-executions/${encodeURIComponent(executionId)}/cancel`,
646
+ body: {}
647
+ });
648
+ }
649
+ resolveTaskRun(input) {
650
+ required(input.input, "SDK_TASKRUN_INPUT_REQUIRED");
651
+ if (input.requestedMode) required(input.requestedMode, "SDK_TASKRUN_MODE_REQUIRED");
652
+ const connectionId = input.connectionId ?? this.boundConnectionId;
653
+ return this.request({ method: "POST", path: "/v1/task-runs/resolve", body: { ...input, ...connectionId ? { connectionId } : {} } }, { idempotencyKey: `sdk:task-run:resolve:${input.input}` });
654
+ }
655
+ startTaskRun(input, options = {}) {
656
+ for (const value of [input.taskRunId, input.requestId]) required(value, "SDK_TASKRUN_ADMISSION_FIELD_REQUIRED");
657
+ if (input.input === void 0 || input.input === null) throw new Error("SDK_TASKRUN_INPUT_REQUIRED");
658
+ const body = { ...input, ...input.ownerActorRef ? { ownerActorRef: input.ownerActorRef } : {}, ...input.requestedMode ? { requestedMode: input.requestedMode } : {}, ...!input.connectionId && this.boundConnectionId ? { connectionId: this.boundConnectionId } : {} };
659
+ return this.request({ method: "POST", path: "/v1/task-runs", body }, { idempotencyKey: options.idempotencyKey ?? `sdk:task-run:start:${input.requestId}` });
660
+ }
661
+ startTaskRunFromPrompt(input, options = {}) {
662
+ if (input.input === void 0 || input.input === null) throw new Error("SDK_TASKRUN_INPUT_REQUIRED");
663
+ const id = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}`;
664
+ const requestId = input.requestId ?? `sdk-task-run-${id()}`;
665
+ return this.startTaskRun({ taskRunId: input.taskRunId ?? `task-run-${id()}`, requestId, input: input.input, ...input.conversationId ? { conversationId: input.conversationId } : {}, ...input.episodeId ? { episodeId: input.episodeId } : {}, ...input.traceRootRef ? { traceRootRef: input.traceRootRef } : {}, ...input.budget ? { budget: input.budget } : {}, ...input.connectionId ? { connectionId: input.connectionId } : {} }, options);
666
+ }
667
+ getTaskRun(taskRunId) {
668
+ required(taskRunId, "SDK_TASKRUN_ID_REQUIRED");
669
+ return this.request({ method: "GET", path: `/v1/task-runs/${encodeURIComponent(taskRunId)}` });
670
+ }
671
+ listTaskRunEvents(taskRunId) {
672
+ required(taskRunId, "SDK_TASKRUN_ID_REQUIRED");
673
+ return this.request({ method: "GET", path: `/v1/task-runs/${encodeURIComponent(taskRunId)}/events` });
674
+ }
675
+ continueTaskRun(taskRunId, expectedVersion, options = {}) {
676
+ required(taskRunId, "SDK_TASKRUN_ID_REQUIRED");
677
+ if (!Number.isInteger(expectedVersion) || expectedVersion < 0) throw new Error("SDK_TASKRUN_STATE_VERSION_REQUIRED");
678
+ return this.request({ method: "POST", path: `/v1/task-runs/${encodeURIComponent(taskRunId)}/continue`, body: { expectedVersion, ...options.cursor ? { cursor: options.cursor } : {} } }, { idempotencyKey: options.idempotencyKey ?? `sdk:task-run:continue:${taskRunId}:${expectedVersion}` });
679
+ }
680
+ cancelTaskRun(taskRunId, expectedVersion, options = {}) {
681
+ required(taskRunId, "SDK_TASKRUN_ID_REQUIRED");
682
+ if (!Number.isInteger(expectedVersion) || expectedVersion < 0) throw new Error("SDK_TASKRUN_STATE_VERSION_REQUIRED");
683
+ return this.request({ method: "POST", path: `/v1/task-runs/${encodeURIComponent(taskRunId)}/cancel`, body: { expectedVersion } }, { idempotencyKey: options.idempotencyKey ?? `sdk:task-run:cancel:${taskRunId}:${expectedVersion}` });
684
+ }
685
+ retryTaskRun(taskRunId, expectedVersion, options = {}) {
686
+ required(taskRunId, "SDK_TASKRUN_ID_REQUIRED");
687
+ if (!Number.isInteger(expectedVersion) || expectedVersion < 0) throw new Error("SDK_TASKRUN_STATE_VERSION_REQUIRED");
688
+ return this.request({ method: "POST", path: `/v1/task-runs/${encodeURIComponent(taskRunId)}/retry`, body: { expectedVersion } }, { idempotencyKey: options.idempotencyKey ?? `sdk:task-run:retry:${taskRunId}:${expectedVersion}` });
689
+ }
690
+ listBehaviorPrograms() {
691
+ return this.request({
692
+ method: "GET",
693
+ path: "/v1/behavior-programs"
694
+ });
695
+ }
696
+ listCapabilities(options = {}) {
697
+ const query = options.surface ? `?surface=${encodeURIComponent(options.surface)}` : "";
698
+ return this.request({ method: "GET", path: `/v1/capabilities${query}` });
699
+ }
700
+ getBehaviorProgram(programId) {
701
+ required(programId, "SDK_PROGRAM_ID_REQUIRED");
702
+ return this.request({
703
+ method: "GET",
704
+ path: `/v1/behavior-programs/${encodeURIComponent(programId)}`
705
+ });
706
+ }
707
+ async deleteBehaviorProgram(programId, options = {}) {
708
+ required(programId, "SDK_PROGRAM_ID_REQUIRED");
709
+ const path = `/v1/behavior-programs/${encodeURIComponent(programId)}`, body = { confirm: options.confirm === true };
710
+ return this.request({ method: "DELETE", path, body }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey(`sdk:delete:behavior-program:${programId}`, { path, body }) });
711
+ }
712
+ async deleteBehaviorProgramCascade(programId, options = {}) {
713
+ required(programId, "SDK_PROGRAM_ID_REQUIRED");
714
+ const path = `/v1/behavior-programs/${encodeURIComponent(programId)}/cascade`, body = { confirm: options.confirm === true, purgeRuntime: options.purgeRuntime === true };
715
+ return this.request({ method: "DELETE", path, body }, { idempotencyKey: options.idempotencyKey ?? await contentIdempotencyKey(`sdk:delete:behavior-program-cascade:${programId}`, { path, body }) });
716
+ }
717
+ createBehaviorProgram(program, options = {}) {
718
+ if (!program || typeof program !== "object")
719
+ throw new Error("SDK_PROGRAM_REQUIRED");
720
+ return this.request(
721
+ { method: "POST", path: "/v1/behavior-programs", body: program },
722
+ {
723
+ idempotencyKey: options.idempotencyKey ?? `sdk:program:${String(program.id ?? "")}`
724
+ }
725
+ );
726
+ }
727
+ attestCapabilities(attestation, options = {}) {
728
+ return this.request(
729
+ {
730
+ method: "POST",
731
+ path: "/v1/runtime/capabilities/attest",
732
+ body: attestation
733
+ },
734
+ { idempotencyKey: options.idempotencyKey }
735
+ );
736
+ }
737
+ resolveCapabilities(attestationId, request, options = {}) {
738
+ required(attestationId, "SDK_ATTESTATION_ID_REQUIRED");
739
+ return this.request(
740
+ {
741
+ method: "POST",
742
+ path: "/v1/runtime/capabilities/resolve",
743
+ body: { attestationId, request }
744
+ },
745
+ { idempotencyKey: options.idempotencyKey, enforcing: true }
746
+ );
747
+ }
748
+ recordCapabilityTrace(decisionDigest, trace, options = {}) {
749
+ required(decisionDigest, "SDK_DECISION_DIGEST_REQUIRED");
750
+ return this.request(
751
+ {
752
+ method: "POST",
753
+ path: "/v1/runtime/capabilities/traces",
754
+ body: { decisionDigest, trace }
755
+ },
756
+ { idempotencyKey: options.idempotencyKey }
757
+ );
758
+ }
759
+ resolveModelServing(request, options = {}) {
760
+ return this.request(
761
+ { method: "POST", path: "/v1/model-serving/resolve", body: request },
762
+ { idempotencyKey: options.idempotencyKey, enforcing: true }
763
+ );
764
+ }
765
+ recordModelServingTrace(trace, selection, options = {}) {
766
+ return this.request(
767
+ {
768
+ method: "POST",
769
+ path: "/v1/model-serving/traces",
770
+ body: { trace, selection }
771
+ },
772
+ { idempotencyKey: options.idempotencyKey }
773
+ );
774
+ }
775
+ submitTeachEvidence(evidence, options = {}) {
776
+ return this.request(
777
+ { method: "POST", path: "/v1/teach/evidence", body: evidence },
778
+ { idempotencyKey: options.idempotencyKey }
779
+ );
780
+ }
781
+ listConversationArchive(input = {}) {
782
+ return this.request({ method: "GET", path: "/v1/teach/conversations", query: { ...input.limit === void 0 ? {} : { limit: String(input.limit) }, ...input.cursor ? { cursor: input.cursor } : {} } });
783
+ }
784
+ importConversationArchive(conversations, options = {}) {
785
+ if (!Array.isArray(conversations) || !conversations.length) throw new Error("SDK_CONVERSATION_IMPORT_REQUIRED");
786
+ return this.request({ method: "POST", path: "/v1/teach/conversations/import", body: { conversations: [...conversations] } }, { idempotencyKey: options.idempotencyKey });
787
+ }
788
+ revokeConversation(evidenceId, reason, options = {}) {
789
+ required(evidenceId, "SDK_CONVERSATION_ID_REQUIRED");
790
+ required(reason, "SDK_CONVERSATION_REVOCATION_REASON_REQUIRED");
791
+ return this.request({ method: "POST", path: `/v1/teach/conversations/${encodeURIComponent(evidenceId)}/revoke`, body: { reason } }, { idempotencyKey: options.idempotencyKey });
792
+ }
793
+ createTeachAnalysis(evidenceIds, options = {}) {
794
+ if (!Array.isArray(evidenceIds) || evidenceIds.length < 1) throw new Error("SDK_TEACH_ANALYSIS_EVIDENCE_REQUIRED");
795
+ const body = { evidenceIds: [...evidenceIds] };
796
+ if (options.analysisId) body.analysisId = options.analysisId;
797
+ if (options.controlPointId) body.controlPointId = options.controlPointId;
798
+ if (options.summary) body.summary = options.summary;
799
+ const requestKey = JSON.stringify([evidenceIds, options.analysisId ?? "", options.controlPointId ?? "", options.summary ?? ""]);
800
+ return this.request({ method: "POST", path: "/v1/teach/analyses", body }, { idempotencyKey: options.idempotencyKey ?? `sdk:teach:analysis:${requestKey}` });
801
+ }
802
+ getTeachAnalysis(analysisId) {
803
+ required(analysisId, "SDK_TEACH_ANALYSIS_ID_REQUIRED");
804
+ return this.request({ method: "GET", path: `/v1/teach/analyses/${encodeURIComponent(analysisId)}` });
805
+ }
806
+ createCrossConversationTeachAnalysis(input, options = {}) {
807
+ if (!Array.isArray(input.conversationRefs) || input.conversationRefs.length < 2) throw new Error("SDK_TEACH_CROSS_CONVERSATION_REFS_REQUIRED");
808
+ const body = { conversationRefs: input.conversationRefs };
809
+ if (input.analysisId) body.analysisId = input.analysisId;
810
+ if (input.controlPointId) body.controlPointId = input.controlPointId;
811
+ if (input.objective) body.objective = input.objective;
812
+ return this.request({ method: "POST", path: "/v1/teach/cross-conversation-analyses", body }, { idempotencyKey: options.idempotencyKey ?? `sdk:teach:cross-conversation:${JSON.stringify(body)}` });
813
+ }
814
+ createTeachProposal(input, options = {}) {
815
+ return this.request({ method: "POST", path: "/v1/teach/proposals", body: input }, { idempotencyKey: options.idempotencyKey });
816
+ }
817
+ getTeachRunBehaviorSet(teachRunId) {
818
+ required(teachRunId, "SDK_TEACH_RUN_ID_REQUIRED");
819
+ return this.request({ method: "GET", path: `/v1/teach/runs/${encodeURIComponent(teachRunId)}/behavior-set` });
820
+ }
821
+ listTeachRuns(input = {}) {
822
+ return this.request({ method: "GET", path: "/v1/teach/runs", query: { ...input.limit === void 0 ? {} : { limit: String(input.limit) }, ...input.cursor ? { cursor: input.cursor } : {}, ...input.status ? { status: input.status } : {} } });
823
+ }
824
+ getTeachRunRecommendation(teachRunId) {
825
+ required(teachRunId, "SDK_TEACH_RUN_ID_REQUIRED");
826
+ return this.request({ method: "GET", path: `/v1/teach/runs/${encodeURIComponent(teachRunId)}/recommendation` });
827
+ }
828
+ acceptTeachRecommendation(teachRunId, actorId, options = {}) {
829
+ required(teachRunId, "SDK_TEACH_RUN_ID_REQUIRED");
830
+ required(actorId, "SDK_TEACH_RECOMMENDATION_ACTOR_REQUIRED");
831
+ return this.request({ method: "POST", path: `/v1/teach/runs/${encodeURIComponent(teachRunId)}/recommendation/accept`, body: { actorId } }, { idempotencyKey: options.idempotencyKey ?? `sdk:teach:recommendation:accept:${teachRunId}` });
832
+ }
833
+ rejectTeachRecommendation(teachRunId, reason, options = {}) {
834
+ required(teachRunId, "SDK_TEACH_RUN_ID_REQUIRED");
835
+ required(reason, "SDK_TEACH_RECOMMENDATION_REASON_REQUIRED");
836
+ return this.request({ method: "POST", path: `/v1/teach/runs/${encodeURIComponent(teachRunId)}/recommendation/reject`, body: { reason } }, { idempotencyKey: options.idempotencyKey ?? `sdk:teach:recommendation:reject:${teachRunId}` });
837
+ }
838
+ editTeachRecommendation(teachRunId, input, options = {}) {
839
+ required(teachRunId, "SDK_TEACH_RUN_ID_REQUIRED");
840
+ required(input?.revisionId, "SDK_TEACH_RECOMMENDATION_REVISION_ID_REQUIRED");
841
+ required(input?.reason, "SDK_TEACH_RECOMMENDATION_REASON_REQUIRED");
842
+ return this.request({ method: "POST", path: `/v1/teach/runs/${encodeURIComponent(teachRunId)}/recommendation/edit`, body: input }, { idempotencyKey: options.idempotencyKey ?? `sdk:teach:recommendation:edit:${input.revisionId}` });
843
+ }
844
+ transitionTeachProposal(proposalId, transition, input = {}, options = {}) {
845
+ required(proposalId, "SDK_TEACH_PROPOSAL_ID_REQUIRED");
846
+ return this.request({ method: "POST", path: `/v1/teach/proposals/${encodeURIComponent(proposalId)}/${transition}`, body: input }, { idempotencyKey: options.idempotencyKey ?? `sdk:teach:${transition}:${proposalId}` });
847
+ }
848
+ requestTeachProposalChanges(proposalId, feedback, options = {}) {
849
+ required(proposalId, "SDK_TEACH_PROPOSAL_ID_REQUIRED");
850
+ required(feedback, "SDK_TEACH_REQUEST_CHANGES_FEEDBACK_REQUIRED");
851
+ return this.request({ method: "POST", path: `/v1/teach/proposals/${encodeURIComponent(proposalId)}/request-changes`, body: { feedback } }, { idempotencyKey: options.idempotencyKey ?? `sdk:teach:request-changes:${proposalId}:${feedback}` });
852
+ }
853
+ startEvaluation(input, options = {}) {
854
+ return this.request(
855
+ { method: "POST", path: "/v1/evaluations", body: input },
856
+ { idempotencyKey: options.idempotencyKey }
857
+ );
858
+ }
859
+ /** Request a server-executed OFF/current-ON/candidate-ON comparison.
860
+ * The server owns arm execution and receipts; callers cannot submit outputs.
861
+ */
862
+ startComparativeEvaluation(input, options = {}) {
863
+ if (!input.comparisonPlan || typeof input.comparisonPlan !== "object")
864
+ throw new Error("SDK_COMPARISON_PLAN_REQUIRED");
865
+ return this.startEvaluation(input, options);
866
+ }
867
+ evaluateQuality(input, options = {}) {
868
+ return this.request(
869
+ { method: "POST", path: "/v1/quality/evaluate", body: input },
870
+ { idempotencyKey: options.idempotencyKey }
871
+ );
872
+ }
873
+ getEvaluation(evaluationId) {
874
+ required(evaluationId, "SDK_EVALUATION_ID_REQUIRED");
875
+ return this.request({
876
+ method: "GET",
877
+ path: `/v1/evaluations/${encodeURIComponent(evaluationId)}`
878
+ });
879
+ }
880
+ getEvaluationComparison(evaluationId) {
881
+ required(evaluationId, "SDK_EVALUATION_ID_REQUIRED");
882
+ return this.request({
883
+ method: "GET",
884
+ path: `/v1/evaluations/${encodeURIComponent(evaluationId)}/comparison`
885
+ });
886
+ }
887
+ reviewComparativeFinding(evaluationId, findingId, input, options = {}) {
888
+ required(evaluationId, "SDK_EVALUATION_ID_REQUIRED");
889
+ required(findingId, "SDK_COMPARATIVE_FINDING_ID_REQUIRED");
890
+ if (!["ACCEPT", "REJECT", "EDIT", "MORE_EVIDENCE"].includes(input?.decision)) throw new Error("SDK_COMPARATIVE_FINDING_DECISION_INVALID");
891
+ return this.request({ method: "POST", path: `/v1/evaluations/${encodeURIComponent(evaluationId)}/comparison/findings/${encodeURIComponent(findingId)}/review`, body: { ...input } }, { idempotencyKey: options.idempotencyKey ?? `sdk:comparison:finding:${evaluationId}:${findingId}` });
892
+ }
893
+ createEvaluationPlan(input, options = {}) {
894
+ return this.request({ method: "POST", path: "/v1/evaluation-plans", body: input }, { idempotencyKey: options.idempotencyKey });
895
+ }
896
+ getEvaluationPlan(planId) {
897
+ required(planId, "SDK_EVALUATION_PLAN_ID_REQUIRED");
898
+ return this.request({ method: "GET", path: `/v1/evaluation-plans/${encodeURIComponent(planId)}` });
899
+ }
900
+ sealEvaluationPlan(planId, options = {}) {
901
+ required(planId, "SDK_EVALUATION_PLAN_ID_REQUIRED");
902
+ return this.request({ method: "POST", path: `/v1/evaluation-plans/${encodeURIComponent(planId)}/seal`, body: {} }, { idempotencyKey: options.idempotencyKey ?? `sdk:evaluation-plan:seal:${planId}` });
903
+ }
904
+ createImprovementProposal(evaluationId, input, options = {}) {
905
+ required(evaluationId, "SDK_EVALUATION_ID_REQUIRED");
906
+ return this.request({ method: "POST", path: `/v1/evaluations/${encodeURIComponent(evaluationId)}/improvement-proposal`, body: input }, { idempotencyKey: options.idempotencyKey });
907
+ }
908
+ getImprovementProposal(proposalId) {
909
+ required(proposalId, "SDK_IMPROVEMENT_PROPOSAL_ID_REQUIRED");
910
+ return this.request({ method: "GET", path: `/v1/behavior-improvement-proposals/${encodeURIComponent(proposalId)}` });
911
+ }
912
+ reviewImprovementProposal(proposalId, action = "request-review", options = {}) {
913
+ required(proposalId, "SDK_IMPROVEMENT_PROPOSAL_ID_REQUIRED");
914
+ return this.request({ method: "POST", path: `/v1/behavior-improvement-proposals/${encodeURIComponent(proposalId)}`, body: { action } }, { idempotencyKey: options.idempotencyKey ?? `sdk:improvement-proposal:${action}:${proposalId}` });
915
+ }
916
+ editImprovementProposalAsNew(proposalId, input, options = {}) {
917
+ required(proposalId, "SDK_IMPROVEMENT_PROPOSAL_ID_REQUIRED");
918
+ required(input?.id, "SDK_IMPROVEMENT_EDIT_ID_REQUIRED");
919
+ required(input?.reason, "SDK_IMPROVEMENT_EDIT_REASON_REQUIRED");
920
+ return this.request({ method: "POST", path: `/v1/behavior-improvement-proposals/${encodeURIComponent(proposalId)}`, body: { ...input, action: "edit" } }, { idempotencyKey: options.idempotencyKey ?? `sdk:improvement-proposal:edit:${input.id}` });
921
+ }
922
+ createBehaviorRevisionCandidate(input, options = {}) {
923
+ return this.request({ method: "POST", path: "/v1/behavior-revision-candidates", body: input }, { idempotencyKey: options.idempotencyKey });
924
+ }
925
+ evaluateBehaviorRevisionCandidate(candidateId, input, options = {}) {
926
+ required(candidateId, "SDK_BEHAVIOR_CANDIDATE_ID_REQUIRED");
927
+ if (!Array.isArray(input.dataset) || input.dataset.length === 0) throw new Error("SDK_CANDIDATE_EVALUATION_DATASET_REQUIRED");
928
+ return this.request({ method: "POST", path: `/v1/behavior-revision-candidates/${encodeURIComponent(candidateId)}/evaluate`, body: input }, { idempotencyKey: options.idempotencyKey ?? `sdk:behavior-candidate:evaluate:${candidateId}` });
929
+ }
930
+ transitionBehaviorRevisionCandidate(candidateId, transition, input = {}, options = {}) {
931
+ required(candidateId, "SDK_BEHAVIOR_CANDIDATE_ID_REQUIRED");
932
+ return this.request({ method: "POST", path: `/v1/behavior-revision-candidates/${encodeURIComponent(candidateId)}/${transition}`, body: input }, { idempotencyKey: options.idempotencyKey ?? `sdk:behavior-candidate:${transition}:${candidateId}` });
933
+ }
934
+ createPackRevision(input, options = {}) {
935
+ return this.request({ method: "POST", path: "/v1/pack-revisions", body: input }, { idempotencyKey: options.idempotencyKey });
936
+ }
937
+ getPackRevision(revisionId) {
938
+ required(revisionId, "SDK_PACK_REVISION_ID_REQUIRED");
939
+ return this.request({ method: "GET", path: `/v1/pack-revisions/${encodeURIComponent(revisionId)}` });
940
+ }
941
+ getPackRevisionDiff(revisionId) {
942
+ required(revisionId, "SDK_PACK_REVISION_ID_REQUIRED");
943
+ return this.request({ method: "GET", path: `/v1/pack-revisions/${encodeURIComponent(revisionId)}/diff` });
944
+ }
945
+ transitionPackRevision(revisionId, transition, options = {}) {
946
+ required(revisionId, "SDK_PACK_REVISION_ID_REQUIRED");
947
+ return this.request({ method: "POST", path: `/v1/pack-revisions/${encodeURIComponent(revisionId)}/${transition}`, body: {} }, { idempotencyKey: options.idempotencyKey ?? `sdk:pack-revision:${transition}:${revisionId}` });
948
+ }
949
+ materializePackRevision(revisionId, input, options = {}) {
950
+ required(revisionId, "SDK_PACK_REVISION_ID_REQUIRED");
951
+ return this.request({ method: "POST", path: `/v1/pack-revisions/${encodeURIComponent(revisionId)}/materialize`, body: input }, { idempotencyKey: options.idempotencyKey ?? `sdk:pack-revision:materialize:${revisionId}` });
952
+ }
953
+ createRelease(input, options = {}) {
954
+ return this.request(
955
+ { method: "POST", path: "/v1/releases", body: input },
956
+ { idempotencyKey: options.idempotencyKey }
957
+ );
958
+ }
959
+ createManualRelease(input, options = {}) {
960
+ if (!input || typeof input !== "object" || !input.manualApproval) throw new Error("SDK_MANUAL_RELEASE_APPROVAL_REQUIRED");
961
+ return this.createRelease(input, { idempotencyKey: options.idempotencyKey });
962
+ }
963
+ releaseProgramManually(input, options = {}) {
964
+ required(input?.programId, "SDK_MANUAL_RELEASE_PROGRAM_REQUIRED");
965
+ required(input?.reason, "SDK_MANUAL_RELEASE_REASON_REQUIRED");
966
+ return this.request({ method: "POST", path: "/v1/releases/manual-program", body: { programId: input.programId, reason: input.reason } }, { idempotencyKey: options.idempotencyKey ?? `sdk:manual-release:${input.programId}` });
967
+ }
968
+ recordDeploymentComparison(deploymentId, comparison, options = {}) {
969
+ required(deploymentId, "SDK_DEPLOYMENT_ID_REQUIRED");
970
+ return this.request(
971
+ {
972
+ method: "POST",
973
+ path: `/v1/deployments/${encodeURIComponent(deploymentId)}/comparisons`,
974
+ body: comparison
975
+ },
976
+ { idempotencyKey: options.idempotencyKey }
977
+ );
978
+ }
979
+ recordDeploymentHealth(deploymentId, health, options = {}) {
980
+ required(deploymentId, "SDK_DEPLOYMENT_ID_REQUIRED");
981
+ return this.request(
982
+ {
983
+ method: "POST",
984
+ path: `/v1/deployments/${encodeURIComponent(deploymentId)}/authoritative-health`,
985
+ body: health
986
+ },
987
+ { idempotencyKey: options.idempotencyKey }
988
+ );
989
+ }
990
+ async compileBehaviorCapsule(input) {
991
+ required(input.programId, "SDK_PROGRAM_ID_REQUIRED");
992
+ required(input.input, "SDK_RUNTIME_INPUT_REQUIRED");
993
+ const body = { ...input };
994
+ return this.request(
995
+ {
996
+ method: "POST",
997
+ path: "/v1/runtime/behavior-capsule",
998
+ body
999
+ },
1000
+ {
1001
+ idempotencyKey: input.idempotencyKey ?? await contentIdempotencyKey(`sdk:capsule:${input.programId}`, body),
1002
+ enforcing: true
1003
+ }
1004
+ );
1005
+ }
1006
+ async serveBehaviorProjection(input) {
1007
+ required(input.input, "SDK_RUNTIME_INPUT_REQUIRED");
1008
+ required(input.provider, "SDK_PROVIDER_REQUIRED");
1009
+ const body = { ...input };
1010
+ return this.request(
1011
+ {
1012
+ method: "POST",
1013
+ path: "/v1/runtime/behavior-projection",
1014
+ body
1015
+ },
1016
+ {
1017
+ idempotencyKey: input.idempotencyKey ?? await contentIdempotencyKey(`sdk:projection:${input.programId ?? "active"}`, body),
1018
+ enforcing: true
1019
+ }
1020
+ );
1021
+ }
1022
+ async testConnection(connectionId) {
1023
+ required(connectionId, "SDK_CONNECTION_ID_REQUIRED");
1024
+ return this.request({
1025
+ method: "POST",
1026
+ path: `/v1/connections/${encodeURIComponent(connectionId)}/test`,
1027
+ body: {}
1028
+ });
1029
+ }
1030
+ };
1031
+ function buildRuntimeRequest(input) {
1032
+ const {
1033
+ organizationId,
1034
+ projectId,
1035
+ environmentId,
1036
+ revisionId,
1037
+ idempotencyKey,
1038
+ nonce,
1039
+ expiresAt,
1040
+ decisionPlanId,
1041
+ calibrationId,
1042
+ candidates,
1043
+ allowedIds,
1044
+ policy,
1045
+ postModelJudge,
1046
+ shadow
1047
+ } = input;
1048
+ for (const value of [
1049
+ organizationId,
1050
+ projectId,
1051
+ environmentId,
1052
+ revisionId,
1053
+ idempotencyKey,
1054
+ nonce
1055
+ ])
1056
+ if (typeof value !== "string" || !value.trim())
1057
+ throw new Error("SDK_REQUEST_FIELD_REQUIRED");
1058
+ if (!Number.isFinite(expiresAt)) throw new Error("SDK_EXPIRY_REQUIRED");
1059
+ const boundary = {
1060
+ ...typeof decisionPlanId === "string" ? { decisionPlanId } : {},
1061
+ ...typeof calibrationId === "string" ? { calibrationId } : {},
1062
+ ...Array.isArray(candidates) ? { candidates } : {},
1063
+ ...Array.isArray(allowedIds) ? { allowedIds } : {},
1064
+ ...typeof policy === "string" ? { policy } : {},
1065
+ ...postModelJudge !== void 0 ? { postModelJudge } : {},
1066
+ ...shadow !== void 0 ? { shadow } : {}
1067
+ };
1068
+ return Object.freeze({
1069
+ organizationId,
1070
+ projectId,
1071
+ environmentId,
1072
+ revisionId,
1073
+ idempotencyKey,
1074
+ nonce,
1075
+ expiresAt,
1076
+ ...boundary
1077
+ });
1078
+ }
1079
+ function buildAISystemRequest(input) {
1080
+ const { name, systemType, organizationId, projectId, environmentId } = input;
1081
+ for (const value of [
1082
+ name,
1083
+ systemType,
1084
+ organizationId,
1085
+ projectId,
1086
+ environmentId
1087
+ ])
1088
+ if (typeof value !== "string" || !value.trim())
1089
+ throw new Error("SDK_AI_SYSTEM_FIELD_REQUIRED");
1090
+ return Object.freeze({
1091
+ method: "POST",
1092
+ path: "/v1/ai-systems",
1093
+ body: Object.freeze({
1094
+ name,
1095
+ systemType,
1096
+ organizationId,
1097
+ projectId,
1098
+ environmentId
1099
+ })
1100
+ });
1101
+ }
1102
+ function buildObservationRequest(event) {
1103
+ if (!event?.organizationId?.trim() || !event?.projectId?.trim() || !event?.environmentId?.trim())
1104
+ throw new Error("SDK_OBSERVATION_SCOPE_REQUIRED");
1105
+ if (!event?.eventId || !event.eventType || !event.aiSystemId || !event.connectionId)
1106
+ throw new Error("SDK_OBSERVATION_FIELD_REQUIRED");
1107
+ return Object.freeze({
1108
+ method: "POST",
1109
+ path: "/v1/observations",
1110
+ body: Object.freeze({ ...event })
1111
+ });
1112
+ }
1113
+
1114
+ // ../../sdks/host-lifecycle.ts
1115
+ import { createHash } from "node:crypto";
1116
+ var nonEmpty = (value, code) => {
1117
+ if (typeof value !== "string" || !value.trim()) throw new Error(code);
1118
+ return value.trim();
1119
+ };
1120
+ var bounded = (value, fallback) => {
1121
+ const parsed = Number(value ?? fallback);
1122
+ if (!Number.isFinite(parsed) || parsed < 256) throw new Error("SDK_HOST_PROJECTION_BUDGET_INVALID");
1123
+ return Math.min(Math.floor(parsed), 64e3);
1124
+ };
1125
+ var lifecycleTimeout = (value) => {
1126
+ const parsed = Number(value ?? 15e3);
1127
+ if (!Number.isFinite(parsed) || parsed < 250) throw new Error("SDK_HOST_TIMEOUT_INVALID");
1128
+ return Math.min(Math.floor(parsed), 12e4);
1129
+ };
1130
+ var withTimeout = async (operation, timeoutMs, code) => {
1131
+ let timer;
1132
+ try {
1133
+ return await Promise.race([operation, new Promise((_, reject) => {
1134
+ timer = setTimeout(() => reject(new Error(code)), timeoutMs);
1135
+ })]);
1136
+ } finally {
1137
+ if (timer) clearTimeout(timer);
1138
+ }
1139
+ };
1140
+ var bodyOf = (response) => {
1141
+ if (response && typeof response === "object" && "body" in response) {
1142
+ const body = response.body;
1143
+ return body && typeof body === "object" && !Array.isArray(body) ? body : {};
1144
+ }
1145
+ return response && typeof response === "object" && !Array.isArray(response) ? response : {};
1146
+ };
1147
+ var isAllow = (decision) => decision.decision === "ALLOW" && Boolean(decision.authorityReceiptDigest && decision.decisionDigest);
1148
+ var canonical = (value) => Array.isArray(value) ? `[${value.map(canonical).join(",")}]` : value && typeof value === "object" ? `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => `${JSON.stringify(key)}:${canonical(child)}`).join(",")}}` : JSON.stringify(value);
1149
+ var digest = (value) => createHash("sha256").update(canonical(value), "utf8").digest("hex");
1150
+ function createViraHostRuntime(client, config) {
1151
+ if (!client) throw new Error("SDK_HOST_CLIENT_REQUIRED");
1152
+ const mode = config.mode ?? "off";
1153
+ if (!["off", "observe", "enforce"].includes(mode)) throw new Error("SDK_HOST_MODE_INVALID");
1154
+ const capabilities = config.capabilities;
1155
+ if (!capabilities || typeof capabilities !== "object") throw new Error("SDK_HOST_CAPABILITIES_REQUIRED");
1156
+ const maxBytes = bounded(config.projectionMaxBytes, 8e3);
1157
+ const timeoutMs = lifecycleTimeout(config.timeoutMs);
1158
+ if (mode === "enforce" && (!capabilities.canInterceptToolBefore || !capabilities.canPreventSideEffect)) throw new Error("SDK_HOST_ENFORCE_CAPABILITY_REQUIRED");
1159
+ if (mode === "enforce" && typeof config.authorizeAction !== "function") throw new Error("SDK_HOST_ACTION_AUTHORIZER_REQUIRED");
1160
+ let connected = null;
1161
+ const projections = /* @__PURE__ */ new Map();
1162
+ const connectOnce = async () => {
1163
+ if (mode === "off" || !config.connect) return null;
1164
+ if (!connected) connected = withTimeout(client.connectOnce(config.connect), timeoutMs, "SDK_HOST_CONNECT_TIMEOUT");
1165
+ return connected;
1166
+ };
1167
+ const sessionKey = (request) => `${request.organizationId}:${request.projectId}:${request.environmentId}:${request.conversationId}`;
1168
+ const beforeModel = async (request) => {
1169
+ nonEmpty(request.input, "SDK_HOST_MODEL_INPUT_REQUIRED");
1170
+ if (mode === "off") return { status: "off" };
1171
+ if (!capabilities.canInjectContext) return { status: "unavailable", reasonCode: "HOST_CONTEXT_INJECTION_UNSUPPORTED" };
1172
+ await connectOnce();
1173
+ const semantic = await withTimeout(client.resolveSemanticRuntime({ input: request.input, sessionId: request.sessionId ?? request.conversationId, requestId: request.requestId }), timeoutMs, "SDK_HOST_RESOLVE_TIMEOUT");
1174
+ if (!semantic) return { status: "unavailable", reasonCode: "VIRA_SEMANTIC_UNAVAILABLE" };
1175
+ const semanticBody = bodyOf(semantic.semantic);
1176
+ const receipt = semanticBody.receipt && typeof semanticBody.receipt === "object" ? semanticBody.receipt : {};
1177
+ if (receipt.outcome !== "select" || typeof receipt.taskKey !== "string" || !semantic.runtime) {
1178
+ const result2 = { status: "abstain", taskKey: typeof receipt.taskKey === "string" ? receipt.taskKey : void 0, reasonCode: String(receipt.abstainReason ?? receipt.clarifyReason ?? "NO_BEHAVIOR_MATCH") };
1179
+ projections.set(sessionKey(request), result2);
1180
+ return result2;
1181
+ }
1182
+ const runtimeBody = bodyOf(semantic.runtime);
1183
+ const projection = runtimeBody.behaviorProjection && typeof runtimeBody.behaviorProjection === "object" ? runtimeBody.behaviorProjection : {};
1184
+ const rendered = typeof projection.rendered === "string" ? projection.rendered : "";
1185
+ if (!rendered.trim()) return { status: "abstain", taskKey: String(receipt.taskKey), reasonCode: "EMPTY_BEHAVIOR_PROJECTION" };
1186
+ if (Buffer.byteLength(rendered, "utf8") > maxBytes) return { status: "unavailable", taskKey: String(receipt.taskKey), reasonCode: "BEHAVIOR_PROJECTION_BUDGET_EXCEEDED" };
1187
+ const result = { status: "applied", response: semantic.runtime, projection: rendered, projectionDigest: typeof projection.projectionDigest === "string" ? projection.projectionDigest : void 0, taskKey: String(receipt.taskKey), behaviorPoint: typeof runtimeBody.behaviorPoint === "string" ? runtimeBody.behaviorPoint : void 0 };
1188
+ projections.set(sessionKey(request), result);
1189
+ return result;
1190
+ };
1191
+ const beforeTool = async (proposal) => {
1192
+ nonEmpty(proposal.callId, "SDK_HOST_TOOL_CALL_ID_REQUIRED");
1193
+ nonEmpty(proposal.toolName, "SDK_HOST_TOOL_NAME_REQUIRED");
1194
+ if (mode !== "enforce") return { decision: "ABSTAIN", reasonCode: mode === "off" ? "HOST_MODE_OFF" : "OBSERVE_ONLY" };
1195
+ let decision;
1196
+ try {
1197
+ decision = await withTimeout(Promise.resolve(config.authorizeAction(proposal)), timeoutMs, "SDK_HOST_ACTION_AUTHORIZATION_TIMEOUT");
1198
+ } catch {
1199
+ return { decision: "ERROR", reasonCode: "VIRA_ACTION_AUTHORIZATION_TIMEOUT" };
1200
+ }
1201
+ if (!decision || !["ALLOW", "BLOCK", "ESCALATE", "ABSTAIN", "ERROR"].includes(decision.decision)) throw new Error("SDK_HOST_ACTION_DECISION_INVALID");
1202
+ if (decision.decision === "ALLOW" && !isAllow(decision)) throw new Error("SDK_HOST_ALLOW_RECEIPT_INVALID");
1203
+ return decision;
1204
+ };
1205
+ const afterTool = async (result) => {
1206
+ nonEmpty(result.callId, "SDK_HOST_TOOL_CALL_ID_REQUIRED");
1207
+ if (mode === "off" || !capabilities.canInspectToolAfter) return { status: "off" };
1208
+ const observedResult = result.ok ? result.result : { errorCode: result.errorCode ?? "HOST_TOOL_FAILED" };
1209
+ const evidence = { abi: "vira.host-tool-evidence.v1", eventId: result.callId, callId: result.callId, toolName: result.toolName, argumentsDigest: digest(result.args), resultDigest: result.resultDigest ?? digest(observedResult), outcome: result.ok ? "success" : "failure", requestId: result.requestId, conversationId: result.conversationId, ...result.sessionId ? { sessionId: result.sessionId } : {}, ...result.turnId ? { turnId: result.turnId } : {} };
1210
+ return withTimeout(client.observe({ eventId: result.callId, eventType: result.ok ? "tool.result" : "tool.failed", aiSystemId: result.aiSystemId, connectionId: result.connectionId, organizationId: result.organizationId, projectId: result.projectId, environmentId: result.environmentId, conversationId: result.conversationId, requestId: result.requestId, traceId: result.traceId, payload: { result: observedResult, evidence }, resultDigest: evidence.resultDigest }), timeoutMs, "SDK_HOST_OBSERVE_TIMEOUT");
1211
+ };
1212
+ const endSession = (identity) => {
1213
+ projections.delete(sessionKey({ ...identity, requestId: "session-end", input: "" }));
1214
+ };
1215
+ return Object.freeze({ mode, capabilities, connectOnce, beforeModel, beforeTool, afterTool, endSession, getProjection: (identity) => projections.get(sessionKey({ ...identity, requestId: "projection", input: "" })), supportsPostModelVerification: capabilities.canInterceptResponseBeforeDisplay });
1216
+ }
1217
+
1218
+ // src/index.ts
1219
+ var Plugin = { define: (plugin) => plugin };
1220
+ var text = (value) => typeof value === "string" ? value : "";
1221
+ var required2 = (value, code) => {
1222
+ const result = text(value).trim();
1223
+ if (!result) throw new Error(code);
1224
+ return result;
1225
+ };
1226
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
1227
+ var canonical2 = (value) => Array.isArray(value) ? "[" + value.map(canonical2).join(",") + "]" : value && typeof value === "object" ? "{" + Object.entries(value).filter(([, child]) => child !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => JSON.stringify(key) + ":" + canonical2(child)).join(",") + "}" : JSON.stringify(value);
1228
+ var digest2 = (value) => createHash2("sha256").update(canonical2(value), "utf8").digest("hex");
1229
+ var lastUserText = (event) => {
1230
+ const messages = Array.isArray(event.messages) ? event.messages : [];
1231
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
1232
+ const message = asRecord(messages[index]);
1233
+ if (message.role === "user") return text(message.content ?? message.text);
1234
+ }
1235
+ return text(event.input ?? event.prompt);
1236
+ };
1237
+ var ViraOpenCodeActionError = class extends Error {
1238
+ decision;
1239
+ constructor(decision, reason) {
1240
+ super(`VIRA_ACTION_${decision}:${reason}`);
1241
+ this.name = "ViraOpenCodeActionError";
1242
+ this.decision = decision;
1243
+ }
1244
+ };
1245
+ var index_default = Plugin.define({
1246
+ id: "vira",
1247
+ async setup(rawContext) {
1248
+ const ctx = rawContext;
1249
+ const options = ctx.options ?? {};
1250
+ const env = (name) => typeof process !== "undefined" ? process.env[name] : void 0;
1251
+ const mode = options.mode ?? env("VIRA_HOST_MODE") ?? "off";
1252
+ if (mode === "off") return;
1253
+ const apiUrl = options.apiUrl ?? env("VIRA_CONTROL_URL") ?? env("VIRA_API_URL");
1254
+ const apiKey = options.apiKey ?? env("VIRA_API_KEY");
1255
+ const scope = { organizationId: required2(options.organizationId ?? env("VIRA_ORGANIZATION_ID"), "VIRA_HOST_ORGANIZATION_REQUIRED"), projectId: required2(options.projectId ?? env("VIRA_PROJECT_ID"), "VIRA_HOST_PROJECT_REQUIRED"), environmentId: required2(options.environmentId ?? env("VIRA_ENVIRONMENT_ID"), "VIRA_HOST_ENVIRONMENT_REQUIRED") };
1256
+ const client = new ViraClient({ baseUrl: required2(apiUrl, "VIRA_HOST_API_URL_REQUIRED"), apiKey });
1257
+ const capabilities = { canObserveModel: true, canInjectContext: true, canInterceptToolBefore: true, canInspectToolAfter: true, canPreventSideEffect: mode === "enforce", canInterceptResponseBeforeDisplay: false, ...options.capabilities ?? {} };
1258
+ const actionContexts = /* @__PURE__ */ new Map();
1259
+ const runtime = createViraHostRuntime(client, { mode, capabilities, authorizeAction: options.authorizeAction ?? (async (proposal) => {
1260
+ const contextKey = proposal.organizationId + ":" + proposal.projectId + ":" + proposal.environmentId + ":" + proposal.conversationId;
1261
+ const bound = actionContexts.get(contextKey);
1262
+ if (!bound) return { decision: "BLOCK", reasonCode: "RUNTIME_ACTION_CONTEXT_UNAVAILABLE" };
1263
+ try {
1264
+ const response = await client.authorizeHostAction({ executionId: bound.executionId, scope, context: bound.context, proposal: { proposal_id: proposal.conversationId + ":" + proposal.callId, tool_key: proposal.toolName, arguments: proposal.args, arguments_digest: digest2(proposal.args), request_context_digest: digest2(bound.context), schema_digest: String(asRecord(asRecord(bound.context).tools).schema_digest ?? "") } });
1265
+ const body = asRecord(response?.body);
1266
+ return body.allowed === true ? { decision: "ALLOW", authorityReceiptDigest: text(body.authority_receipt_digest), decisionDigest: text(body.decision_digest) } : { decision: "BLOCK", reasonCode: text(body.reason_code) || "POLICY_DENIED", authorityReceiptDigest: text(body.authority_receipt_digest) };
1267
+ } catch (error) {
1268
+ return { decision: "ERROR", reasonCode: error instanceof Error ? error.message : "RUNTIME_ACTION_AUTHORIZATION_FAILED" };
1269
+ }
1270
+ }), connect: { ...scope, name: "OpenCode", systemType: "agent", adapterType: "opencode", adapterVersion: options.hostVersion ?? ctx.app?.version ?? "unknown", idempotencyKey: "opencode:vira:connect" } });
1271
+ const registrations = [];
1272
+ const sessionIdentity = (event) => ({ ...scope, hostName: options.hostName ?? "opencode", hostVersion: options.hostVersion ?? ctx.app?.version ?? "unknown", aiSystemId: required2(options.aiSystemId ?? env("VIRA_AI_SYSTEM_ID"), "VIRA_HOST_AI_SYSTEM_REQUIRED"), connectionId: required2(options.connectionId ?? env("VIRA_CONNECTION_ID"), "VIRA_HOST_CONNECTION_REQUIRED"), conversationId: required2(event.sessionID ?? event.conversationId, "VIRA_HOST_SESSION_REQUIRED") });
1273
+ registrations.push(await ctx.session.hook("context", async (event) => {
1274
+ const identity = sessionIdentity(event);
1275
+ const result = await runtime.beforeModel({ ...identity, requestId: text(event.messageID ?? event.requestId) || `opencode:${Date.now()}`, input: required2(lastUserText(event), "VIRA_HOST_MODEL_INPUT_REQUIRED"), traceId: text(event.traceID), turnId: text(event.turnID) });
1276
+ const responseBody = asRecord(result.response?.body);
1277
+ const runtimeContext = asRecord(event.viraRuntimeContext ?? responseBody.requestContext ?? responseBody.context);
1278
+ const executionId = text(event.executionId ?? responseBody.executionId);
1279
+ if (executionId && runtimeContext.abi === "vira.request-behavior-context.v2") actionContexts.set(identity.organizationId + ":" + identity.projectId + ":" + identity.environmentId + ":" + identity.conversationId, { executionId, context: runtimeContext });
1280
+ if (result.status === "applied" && result.projection) {
1281
+ const system = Array.isArray(event.system) ? event.system : event.system = [];
1282
+ if (!system.some((item) => text(asRecord(item).text) === result.projection)) system.push({ type: "text", text: result.projection });
1283
+ }
1284
+ }));
1285
+ registrations.push(await ctx.tool.hook("execute.before", async (event) => {
1286
+ const identity = sessionIdentity(event);
1287
+ const decision = await runtime.beforeTool({ ...identity, requestId: text(event.messageID ?? event.requestId) || `opencode:${Date.now()}`, callId: required2(event.callID ?? event.id, "VIRA_HOST_TOOL_CALL_REQUIRED"), toolName: required2(event.tool ?? event.name, "VIRA_HOST_TOOL_NAME_REQUIRED"), args: asRecord(event.input ?? event.args), traceId: text(event.traceID), turnId: text(event.turnID) });
1288
+ if (decision.decision !== "ALLOW") throw new ViraOpenCodeActionError(decision.decision, text(decision.reasonCode) || "VIRA_ACTION_NOT_ALLOWED");
1289
+ }));
1290
+ registrations.push(await ctx.tool.hook("execute.after", async (event) => {
1291
+ const identity = sessionIdentity(event);
1292
+ await runtime.afterTool({ ...identity, requestId: text(event.messageID ?? event.requestId) || `opencode:${Date.now()}`, callId: required2(event.callID ?? event.id, "VIRA_HOST_TOOL_CALL_REQUIRED"), toolName: required2(event.tool ?? event.name, "VIRA_HOST_TOOL_NAME_REQUIRED"), args: asRecord(event.input ?? event.args), ok: event.status === "completed", result: event.result, errorCode: text(asRecord(event.error).code), traceId: text(event.traceID), turnId: text(event.turnID) });
1293
+ }));
1294
+ registrations.push(await ctx.session.hook("fork", (event) => runtime.endSession({ ...scope, hostName: "opencode", hostVersion: ctx.app?.version ?? "unknown", aiSystemId: options.aiSystemId ?? "", connectionId: options.connectionId ?? "", conversationId: text(event.sessionID) })));
1295
+ return async () => {
1296
+ actionContexts.clear();
1297
+ for (const registration of registrations) await registration.dispose?.();
1298
+ };
1299
+ }
1300
+ });
1301
+ export {
1302
+ ViraOpenCodeActionError,
1303
+ index_default as default
1304
+ };
package/package.json CHANGED
@@ -1,6 +1,14 @@
1
1
  {
2
2
  "name": "@tryvira/opencode",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "OpenCode V2 host lifecycle integration for Vira",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "exports": {".": "./dist/index.js"},
8
+ "files": ["dist", "README.md"],
9
+ "scripts": {"build": "node ../../scripts/build-opencode-plugin.mjs", "pack:check": "npm run build && npm pack --dry-run"},
10
+ "dependencies": {"@tryvira/sdk": "0.1.46"},
11
+ "peerDependencies": {"effect": "^4.0.0"},
12
+ "engines": {"node": ">=20"},
13
+ "license": "MIT"
14
+ }