@dcdr/contracts 1.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +411 -0
  3. package/dist/capabilities.contract.d.ts +69 -0
  4. package/dist/capabilities.contract.d.ts.map +1 -0
  5. package/dist/capabilities.contract.js +126 -0
  6. package/dist/control.contract.d.ts +39 -0
  7. package/dist/control.contract.d.ts.map +1 -0
  8. package/dist/control.contract.js +2 -0
  9. package/dist/credentials.contract.d.ts +37 -0
  10. package/dist/credentials.contract.d.ts.map +1 -0
  11. package/dist/credentials.contract.js +15 -0
  12. package/dist/entitlements.contract.d.ts +107 -0
  13. package/dist/entitlements.contract.d.ts.map +1 -0
  14. package/dist/entitlements.contract.js +11 -0
  15. package/dist/errors.contract.d.ts +47 -0
  16. package/dist/errors.contract.d.ts.map +1 -0
  17. package/dist/errors.contract.js +48 -0
  18. package/dist/execution.contract.d.ts +240 -0
  19. package/dist/execution.contract.d.ts.map +1 -0
  20. package/dist/execution.contract.js +22 -0
  21. package/dist/http.contract.d.ts +20 -0
  22. package/dist/http.contract.d.ts.map +1 -0
  23. package/dist/http.contract.js +8 -0
  24. package/dist/implementations.contract.d.ts +120 -0
  25. package/dist/implementations.contract.d.ts.map +1 -0
  26. package/dist/implementations.contract.js +2 -0
  27. package/dist/index.d.ts +22 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +37 -0
  30. package/dist/intent.contract.d.ts +137 -0
  31. package/dist/intent.contract.d.ts.map +1 -0
  32. package/dist/intent.contract.js +76 -0
  33. package/dist/logs.contract.d.ts +10 -0
  34. package/dist/logs.contract.d.ts.map +1 -0
  35. package/dist/logs.contract.js +2 -0
  36. package/dist/messages.contract.d.ts +16 -0
  37. package/dist/messages.contract.d.ts.map +1 -0
  38. package/dist/messages.contract.js +2 -0
  39. package/dist/policies.contract.d.ts +253 -0
  40. package/dist/policies.contract.d.ts.map +1 -0
  41. package/dist/policies.contract.js +283 -0
  42. package/dist/prompt-variable-schema.contract.d.ts +75 -0
  43. package/dist/prompt-variable-schema.contract.d.ts.map +1 -0
  44. package/dist/prompt-variable-schema.contract.js +572 -0
  45. package/dist/prompts.contract.d.ts +97 -0
  46. package/dist/prompts.contract.d.ts.map +1 -0
  47. package/dist/prompts.contract.js +87 -0
  48. package/dist/provider.contract.d.ts +477 -0
  49. package/dist/provider.contract.d.ts.map +1 -0
  50. package/dist/provider.contract.js +3310 -0
  51. package/dist/registry.stats.contract.d.ts +39 -0
  52. package/dist/registry.stats.contract.d.ts.map +1 -0
  53. package/dist/registry.stats.contract.js +9 -0
  54. package/dist/runtime.client.d.ts +362 -0
  55. package/dist/runtime.client.d.ts.map +1 -0
  56. package/dist/runtime.client.js +545 -0
  57. package/dist/service-tokens.contract.d.ts +29 -0
  58. package/dist/service-tokens.contract.d.ts.map +1 -0
  59. package/dist/service-tokens.contract.js +10 -0
  60. package/dist/session.contract.d.ts +51 -0
  61. package/dist/session.contract.d.ts.map +1 -0
  62. package/dist/session.contract.js +187 -0
  63. package/dist/subscription.contract.d.ts +37 -0
  64. package/dist/subscription.contract.d.ts.map +1 -0
  65. package/dist/subscription.contract.js +45 -0
  66. package/dist/utils.contract.d.ts +21 -0
  67. package/dist/utils.contract.d.ts.map +1 -0
  68. package/dist/utils.contract.js +79 -0
  69. package/package.json +57 -0
@@ -0,0 +1,545 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DcdrRuntimeClient = exports.DcdrRuntimeAuthCheckEntitlementsStatus = void 0;
4
+ const execution_contract_1 = require("./execution.contract");
5
+ /**
6
+ * Max characters to include when embedding a response body preview inside an Error message.
7
+ *
8
+ * Notes
9
+ * - Keeps logs and CLI output readable.
10
+ * - Reduces the chance of leaking large/sensitive upstream HTML or debug payloads.
11
+ */
12
+ const ERROR_BODY_PREVIEW_MAX_CHARS = 4000;
13
+ /**
14
+ * Status for optional entitlements data returned by the auth check endpoint.
15
+ */
16
+ var DcdrRuntimeAuthCheckEntitlementsStatus;
17
+ (function (DcdrRuntimeAuthCheckEntitlementsStatus) {
18
+ DcdrRuntimeAuthCheckEntitlementsStatus["OK"] = "OK";
19
+ DcdrRuntimeAuthCheckEntitlementsStatus["ERROR"] = "ERROR";
20
+ DcdrRuntimeAuthCheckEntitlementsStatus["SKIPPED"] = "SKIPPED";
21
+ })(DcdrRuntimeAuthCheckEntitlementsStatus || (exports.DcdrRuntimeAuthCheckEntitlementsStatus = DcdrRuntimeAuthCheckEntitlementsStatus = {}));
22
+ /**
23
+ * HTTP client for interacting with the DCDR Runtime REST API.
24
+ *
25
+ * @remarks
26
+ * `DcdrRuntimeClient` wraps a `fetch` implementation and provides typed convenience methods for common runtime endpoints
27
+ * (execution, system info, health, metrics, circuit breaker inspection/reset). It supports request timeouts via
28
+ * {@link AbortController} and can be configured with additional headers.
29
+ *
30
+ * This client is intentionally small and dependency-free:
31
+ * - It does not attempt retries or circuit breaking (those are runtime concerns).
32
+ * - It does not validate payload schemas beyond basic response shape parsing.
33
+ *
34
+ * ## Authentication
35
+ * Exactly one of the following auth modes should be used:
36
+ * - **Bearer token**: sets `Authorization: Bearer <token>`
37
+ * - **API token**: sets `token: <token>` and optionally `x-session-bypass: <token>` when `sessionBypassToken` is provided
38
+ *
39
+ * If both `bearerToken` and `apiToken` are provided, construction throws.
40
+ *
41
+ * ## Timeouts
42
+ * Requests are aborted after `timeoutMs` (default: `10_000`) using `AbortController`.
43
+ *
44
+ * ## Error behavior
45
+ * - Non-OK HTTP responses throw an {@link Error} including method, path, status, and a short body preview.
46
+ * - JSON endpoints require `content-type: application/json` (runtime should set this).
47
+ * - Abort/timeout failures surface as a thrown {@link Error} from the underlying `fetch` implementation.
48
+ *
49
+ * ## Version / build info
50
+ * The {@link DcdrRuntimeClient.version} method calls `GET /api/system/version` and returns a {@link DcdrRuntimeVersionResponse}
51
+ * intended for support diagnostics (build number/date/SHA and, in runtime mode only, node version + uptime).
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * // Customer mode (cloud)
56
+ * const client = new DcdrRuntimeClient({
57
+ * bearerToken: process.env.DCDR_SESSION_TOKEN,
58
+ * });
59
+ *
60
+ * const hc = await client.healthcheck();
61
+ * const res = await client.executeIntent("MY_INTENT", { vars: { name: "Ada" } });
62
+ * const ver = await client.version();
63
+ * ```
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * // Internal mode (dev/ops)
68
+ * const client = new DcdrRuntimeClient({
69
+ * baseUrl: "http://localhost:8000",
70
+ * apiToken: "dev-token",
71
+ * sessionBypassToken: "bypass-token",
72
+ * });
73
+ *
74
+ * const ver = await client.version();
75
+ * ```
76
+ *
77
+ * @public
78
+ */
79
+ class DcdrRuntimeClient {
80
+ baseUrl;
81
+ bearerToken;
82
+ apiToken;
83
+ sessionBypassToken;
84
+ timeoutMs;
85
+ extraHeaders;
86
+ fetchFn;
87
+ /**
88
+ * Creates a new runtime client.
89
+ * @param cfg Client configuration.
90
+ */
91
+ constructor(cfg) {
92
+ const resolvedBaseUrl = cfg?.baseUrl ?? "https://runtime.dcdr.ai";
93
+ const resolvedBaseUrlTrimmed = String(resolvedBaseUrl).trim();
94
+ if (!resolvedBaseUrlTrimmed) {
95
+ throw new Error("DcdrRuntimeClient requires baseUrl (or omit it to use https://runtime.dcdr.ai)");
96
+ }
97
+ this.baseUrl = resolvedBaseUrlTrimmed.replace(/\/$/, "");
98
+ this.bearerToken = cfg.bearerToken ? String(cfg.bearerToken).trim() : undefined;
99
+ this.apiToken = cfg.apiToken ? String(cfg.apiToken).trim() : undefined;
100
+ this.sessionBypassToken = cfg.sessionBypassToken ? String(cfg.sessionBypassToken).trim() : undefined;
101
+ this.timeoutMs = typeof cfg.timeoutMs === "number" && cfg.timeoutMs > 0 ? cfg.timeoutMs : 10_000;
102
+ this.extraHeaders = cfg.extraHeaders ? { ...cfg.extraHeaders } : {};
103
+ const f = cfg.fetchFn ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : undefined);
104
+ if (!f) {
105
+ throw new Error("DcdrRuntimeClient requires a fetch implementation (global fetch missing)");
106
+ }
107
+ this.fetchFn = f;
108
+ // Basic config validation: do not silently pick an auth mode.
109
+ if (this.bearerToken && this.apiToken) {
110
+ throw new Error("DcdrRuntimeClient config should not set both bearerToken and apiToken");
111
+ }
112
+ }
113
+ /**
114
+ * Calls `POST /api/execution/run/:intent`.
115
+ * @param intent Intent name.
116
+ * @param request Execute request payload.
117
+ * @returns Execution result.
118
+ */
119
+ async executeIntent(intent, request) {
120
+ const safeIntent = encodeURIComponent(String(intent ?? "").trim());
121
+ return this.requestJson({
122
+ method: "POST",
123
+ path: `/api/execution/run/${safeIntent}`,
124
+ body: request ?? {},
125
+ timeoutMs: this.timeoutMs,
126
+ });
127
+ }
128
+ /**
129
+ * Calls `POST /api/execution/stream/:intent` and yields SSE events.
130
+ *
131
+ * Notes
132
+ * - The streaming endpoint is additive; `executeIntent()` remains the stable JSON path.
133
+ * - v1 streams a minimal envelope (`meta` then `final`). Providers without native streaming
134
+ * may yield zero `delta` events.
135
+ *
136
+ * @param intent Intent name.
137
+ * @param request Execute request payload.
138
+ * @param opts Streaming options (timeout/signal).
139
+ */
140
+ async *executeIntentStream(intent, request, opts) {
141
+ const safeIntent = encodeURIComponent(String(intent ?? "").trim());
142
+ const url = `${this.baseUrl}/api/execution/stream/${safeIntent}`;
143
+ const headers = {
144
+ "Content-Type": "application/json",
145
+ Accept: "text/event-stream",
146
+ ...this.extraHeaders,
147
+ };
148
+ if (this.bearerToken) {
149
+ headers["Authorization"] = `Bearer ${this.bearerToken}`;
150
+ }
151
+ else if (this.apiToken) {
152
+ headers["token"] = this.apiToken;
153
+ if (this.sessionBypassToken) {
154
+ headers["x-session-bypass"] = this.sessionBypassToken;
155
+ }
156
+ }
157
+ const controller = new AbortController();
158
+ const timeoutMs = typeof opts?.timeoutMs === "number" && opts.timeoutMs > 0 ? opts.timeoutMs : this.timeoutMs;
159
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
160
+ const onAbort = () => controller.abort();
161
+ if (opts?.signal) {
162
+ if (opts.signal.aborted)
163
+ controller.abort();
164
+ else
165
+ opts.signal.addEventListener("abort", onAbort);
166
+ }
167
+ try {
168
+ const resp = await this.fetchFn(url, {
169
+ method: "POST",
170
+ headers,
171
+ body: JSON.stringify(request ?? {}),
172
+ signal: controller.signal,
173
+ });
174
+ if (!resp.ok) {
175
+ const text = await resp.text();
176
+ const preview = buildBodyPreview(text);
177
+ throw new Error(`DcdrRuntimeClient request failed: POST /api/execution/stream/:intent status=${resp.status} body=${preview}`);
178
+ }
179
+ const ct = resp.headers.get("content-type") ?? "";
180
+ if (!/text\/event-stream/i.test(ct)) {
181
+ const text = await resp.text();
182
+ const preview = buildBodyPreview(text);
183
+ throw new Error(`DcdrRuntimeClient expected text/event-stream but got content-type='${ct}' body=${preview}`);
184
+ }
185
+ if (!resp.body) {
186
+ throw new Error("DcdrRuntimeClient streaming response body is missing");
187
+ }
188
+ for await (const evt of this.parseSseStream(resp.body)) {
189
+ const eventName = evt.event;
190
+ const json = evt.data ? JSON.parse(evt.data) : {};
191
+ if (eventName === execution_contract_1.ExecutionStreamEventType.META) {
192
+ yield { type: execution_contract_1.ExecutionStreamEventType.META, data: json };
193
+ }
194
+ else if (eventName === execution_contract_1.ExecutionStreamEventType.DELTA) {
195
+ yield { type: execution_contract_1.ExecutionStreamEventType.DELTA, data: json };
196
+ }
197
+ else if (eventName === execution_contract_1.ExecutionStreamEventType.FINAL) {
198
+ yield { type: execution_contract_1.ExecutionStreamEventType.FINAL, data: json };
199
+ return;
200
+ }
201
+ else if (eventName === execution_contract_1.ExecutionStreamEventType.ERROR) {
202
+ yield { type: execution_contract_1.ExecutionStreamEventType.ERROR, data: json };
203
+ return;
204
+ }
205
+ }
206
+ }
207
+ finally {
208
+ clearTimeout(timeout);
209
+ if (opts?.signal)
210
+ opts.signal.removeEventListener("abort", onAbort);
211
+ }
212
+ }
213
+ /**
214
+ * Parses a ReadableStream of SSE bytes into discrete `{event,data}` frames.
215
+ */
216
+ async *parseSseStream(body) {
217
+ const reader = body.getReader();
218
+ const decoder = new TextDecoder();
219
+ let buffer = "";
220
+ while (true) {
221
+ const { value, done } = await reader.read();
222
+ if (done)
223
+ break;
224
+ buffer += decoder.decode(value, { stream: true });
225
+ while (true) {
226
+ const idx = buffer.indexOf("\n\n");
227
+ if (idx < 0)
228
+ break;
229
+ const raw = buffer.slice(0, idx);
230
+ buffer = buffer.slice(idx + 2);
231
+ const parsed = this.parseSseEvent(raw);
232
+ if (parsed)
233
+ yield parsed;
234
+ }
235
+ }
236
+ // Flush remaining bytes.
237
+ buffer += decoder.decode();
238
+ const remaining = buffer.trim();
239
+ if (remaining) {
240
+ const parsed = this.parseSseEvent(remaining);
241
+ if (parsed)
242
+ yield parsed;
243
+ }
244
+ }
245
+ /**
246
+ * Parses a single SSE event frame.
247
+ */
248
+ parseSseEvent(raw) {
249
+ const lines = raw.split("\n");
250
+ let event = "message";
251
+ const dataLines = [];
252
+ for (const line of lines) {
253
+ const l = line.trimEnd();
254
+ if (!l)
255
+ continue;
256
+ if (l.startsWith(":"))
257
+ continue; // comment/heartbeat
258
+ if (l.startsWith("event:")) {
259
+ event = l.slice("event:".length).trim();
260
+ continue;
261
+ }
262
+ if (l.startsWith("data:")) {
263
+ dataLines.push(l.slice("data:".length).trim());
264
+ continue;
265
+ }
266
+ }
267
+ const data = dataLines.join("\n");
268
+ if (!event && !data)
269
+ return null;
270
+ return { event, data };
271
+ }
272
+ /**
273
+ * Calls `POST /api/execution/demo/:intent`.
274
+ * @param intent Demo intent (e.g. `DCDR_LOCAL_DEMO`).
275
+ * @param request Execute request payload.
276
+ * @returns Execution result.
277
+ */
278
+ async demo(intent, request) {
279
+ const safeIntent = encodeURIComponent(String(intent ?? "").trim());
280
+ return this.requestJson({
281
+ method: "POST",
282
+ path: `/api/execution/demo/${safeIntent}`,
283
+ body: request ?? {},
284
+ timeoutMs: this.timeoutMs,
285
+ });
286
+ }
287
+ /**
288
+ * Calls `GET /api/system/healthcheck`.
289
+ * @returns Healthcheck response.
290
+ */
291
+ async healthcheck() {
292
+ return this.requestJson({
293
+ method: "GET",
294
+ path: "/api/system/healthcheck",
295
+ timeoutMs: this.timeoutMs,
296
+ });
297
+ }
298
+ /**
299
+ * Calls `GET /api/system/version`.
300
+ * @returns Runtime version/build info.
301
+ */
302
+ async version() {
303
+ return this.requestJson({
304
+ method: "GET",
305
+ path: "/api/system/version",
306
+ timeoutMs: this.timeoutMs,
307
+ });
308
+ }
309
+ /**
310
+ * Calls `GET /api/system/metrics`.
311
+ * Returns the raw Prometheus exposition format text.
312
+ *
313
+ * @param token Optional metrics query token (`?token=...`) when the runtime is configured to require it.
314
+ * @returns Metrics text.
315
+ */
316
+ async metrics(token) {
317
+ const t = String(token ?? "").trim();
318
+ const qp = t ? `?token=${encodeURIComponent(t)}` : "";
319
+ return this.requestText({
320
+ method: "GET",
321
+ path: `/api/system/metrics${qp}`,
322
+ timeoutMs: this.timeoutMs,
323
+ });
324
+ }
325
+ /**
326
+ * Calls `GET /api/auth/check`.
327
+ *
328
+ * Notes
329
+ * - This endpoint is intended for customer integrations to verify their auth configuration.
330
+ * - It performs the same validation path as execution calls, but does not execute an Intent.
331
+ */
332
+ async authCheck() {
333
+ return this.requestJson({
334
+ method: "GET",
335
+ path: "/api/auth/check",
336
+ timeoutMs: this.timeoutMs,
337
+ });
338
+ }
339
+ /**
340
+ * Calls `POST /api/execution/dry-run/:intent`.
341
+ * @param intent Intent name.
342
+ * @param vars Template variables.
343
+ * @returns Dry-run response.
344
+ */
345
+ async dryRun(intent, vars) {
346
+ const safeIntent = encodeURIComponent(String(intent ?? "").trim());
347
+ return this.requestJson({
348
+ method: "POST",
349
+ path: `/api/execution/dry-run/${safeIntent}`,
350
+ body: { vars: vars ?? {} },
351
+ timeoutMs: this.timeoutMs,
352
+ });
353
+ }
354
+ /**
355
+ * Calls `POST /api/execution/eval/:intent`.
356
+ *
357
+ * Notes
358
+ * - Eval is disabled in freeware runtime mode (`--registry`).
359
+ *
360
+ * @param intent Intent name.
361
+ * @param vars Template variables.
362
+ * @returns Eval response.
363
+ */
364
+ async eval(intent, vars) {
365
+ const safeIntent = encodeURIComponent(String(intent ?? "").trim());
366
+ return this.requestJson({
367
+ method: "POST",
368
+ path: `/api/execution/eval/${safeIntent}`,
369
+ body: { vars: vars ?? {} },
370
+ timeoutMs: this.timeoutMs,
371
+ });
372
+ }
373
+ /**
374
+ * Calls `GET /api/execution/circuit-breakers?provider=...&model=...`.
375
+ *
376
+ * Notes
377
+ * - Breakers are tenant-scoped server-side based on auth.
378
+ * - In internal mode, you may optionally pass `tenantCid` to inspect a specific tenant.
379
+ *
380
+ * @param provider Provider name.
381
+ * @param model Optional model.
382
+ * @param tenantCid Optional tenant/customer identifier (internal mode only).
383
+ * @returns Circuit breaker snapshot.
384
+ */
385
+ async circuitBreakerStatus(provider, model, tenantCid) {
386
+ const safeProvider = encodeURIComponent(String(provider ?? "").trim());
387
+ const safeModel = model ? encodeURIComponent(String(model).trim()) : "";
388
+ const safeTenant = tenantCid ? encodeURIComponent(String(tenantCid).trim()) : "";
389
+ const qp = [];
390
+ if (safeTenant)
391
+ qp.push(`tenantCid=${safeTenant}`);
392
+ qp.push(`provider=${safeProvider}`);
393
+ if (safeModel)
394
+ qp.push(`model=${safeModel}`);
395
+ const query = `?${qp.join("&")}`;
396
+ return this.requestJson({
397
+ method: "GET",
398
+ path: `/api/execution/circuit-breakers${query}`,
399
+ timeoutMs: this.timeoutMs,
400
+ });
401
+ }
402
+ /**
403
+ * Calls `PUT /api/execution/circuit-breakers/reset`.
404
+ *
405
+ * Notes
406
+ * - Internal-only endpoint.
407
+ * - Breakers are tenant-scoped server-side.
408
+ * - In internal mode, you may optionally pass `tenantCid` to reset a specific tenant.
409
+ *
410
+ * @param provider Provider name.
411
+ * @param model Optional model.
412
+ * @param tenantCid Optional tenant/customer identifier (internal mode only).
413
+ * @returns Reset operation result.
414
+ */
415
+ async resetCircuitBreaker(provider, model, tenantCid) {
416
+ return this.requestJson({
417
+ method: "PUT",
418
+ path: "/api/execution/circuit-breakers/reset",
419
+ body: {
420
+ tenantCid: tenantCid ? String(tenantCid).trim() : undefined,
421
+ provider: String(provider ?? "").trim(),
422
+ model: model ? String(model).trim() : undefined,
423
+ },
424
+ timeoutMs: this.timeoutMs,
425
+ });
426
+ }
427
+ /**
428
+ * Performs an HTTP request and parses a JSON response.
429
+ *
430
+ * @remarks
431
+ * - Requires `content-type: application/json` on successful responses.
432
+ * - Allows an empty response body and treats it as `{}` (for endpoints that may return no content).
433
+ * - Applies auth headers based on the configured auth mode.
434
+ * - Aborts the request after `timeoutMs` via {@link AbortController}.
435
+ *
436
+ * @param args Request parameters.
437
+ * @returns Parsed JSON body.
438
+ * @throws {@link Error} If the HTTP response is not OK, if the response is not JSON, or if JSON parsing fails.
439
+ */
440
+ async requestJson(args) {
441
+ const url = `${this.baseUrl}${args.path}`;
442
+ const headers = {
443
+ "Content-Type": "application/json",
444
+ ...this.extraHeaders,
445
+ };
446
+ if (this.bearerToken) {
447
+ headers["Authorization"] = `Bearer ${this.bearerToken}`;
448
+ }
449
+ else if (this.apiToken) {
450
+ headers["token"] = this.apiToken;
451
+ if (this.sessionBypassToken) {
452
+ headers["x-session-bypass"] = this.sessionBypassToken;
453
+ }
454
+ }
455
+ const controller = new AbortController();
456
+ const timeout = setTimeout(() => controller.abort(), args.timeoutMs);
457
+ try {
458
+ const resp = await this.fetchFn(url, {
459
+ method: args.method,
460
+ headers,
461
+ body: args.body ? JSON.stringify(args.body) : undefined,
462
+ signal: controller.signal,
463
+ });
464
+ const text = await resp.text();
465
+ const isJson = /application\/json/i.test(resp.headers.get("content-type") ?? "");
466
+ if (!resp.ok) {
467
+ const preview = buildBodyPreview(text);
468
+ throw new Error(`DcdrRuntimeClient request failed: ${args.method} ${args.path} status=${resp.status} body=${preview}`);
469
+ }
470
+ if (!text) {
471
+ // Allow empty body for endpoints that might not return content.
472
+ return JSON.parse("{}");
473
+ }
474
+ if (!isJson) {
475
+ throw new Error(`DcdrRuntimeClient expected JSON but got content-type='${resp.headers.get("content-type") ?? ""}'`);
476
+ }
477
+ return JSON.parse(text);
478
+ }
479
+ finally {
480
+ clearTimeout(timeout);
481
+ }
482
+ }
483
+ /**
484
+ * Performs an HTTP request and returns the response body as plain text.
485
+ *
486
+ * @remarks
487
+ * Builds the request URL by concatenating {@link DcdrRuntimeClient.baseUrl} with {@link RequestTextArgs.path}.
488
+ * Merges {@link DcdrRuntimeClient.extraHeaders} into the request headers and applies authentication:
489
+ * - If {@link DcdrRuntimeClient.bearerToken} is set, adds an `Authorization: Bearer <token>` header.
490
+ * - Otherwise, if {@link DcdrRuntimeClient.apiToken} is set, adds a `token: <token>` header and, if present,
491
+ * adds `x-session-bypass: <token>` from {@link DcdrRuntimeClient.sessionBypassToken}.
492
+ *
493
+ * Uses an {@link AbortController} to abort the request after {@link RequestTextArgs.timeoutMs} milliseconds.
494
+ *
495
+ * @param args - Request configuration including method, path, and timeout.
496
+ * @returns The response body as text.
497
+ * @throws {@link Error} If the response status is not OK (`resp.ok === false`). The error message includes the
498
+ * HTTP method, path, status code, and a bounded preview of the response body.
499
+ */
500
+ async requestText(args) {
501
+ const url = `${this.baseUrl}${args.path}`;
502
+ const headers = {
503
+ ...this.extraHeaders,
504
+ };
505
+ if (this.bearerToken) {
506
+ headers["Authorization"] = `Bearer ${this.bearerToken}`;
507
+ }
508
+ else if (this.apiToken) {
509
+ headers["token"] = this.apiToken;
510
+ if (this.sessionBypassToken) {
511
+ headers["x-session-bypass"] = this.sessionBypassToken;
512
+ }
513
+ }
514
+ const controller = new AbortController();
515
+ const timeout = setTimeout(() => controller.abort(), args.timeoutMs);
516
+ try {
517
+ const resp = await this.fetchFn(url, {
518
+ method: args.method,
519
+ headers,
520
+ signal: controller.signal,
521
+ });
522
+ const text = await resp.text();
523
+ if (!resp.ok) {
524
+ const preview = buildBodyPreview(text);
525
+ throw new Error(`DcdrRuntimeClient request failed: ${args.method} ${args.path} status=${resp.status} body=${preview}`);
526
+ }
527
+ return text;
528
+ }
529
+ finally {
530
+ clearTimeout(timeout);
531
+ }
532
+ }
533
+ }
534
+ exports.DcdrRuntimeClient = DcdrRuntimeClient;
535
+ /**
536
+ * Builds a bounded, human-readable body preview string.
537
+ * @param text Full response body text.
538
+ * @returns Preview string capped to {@link ERROR_BODY_PREVIEW_MAX_CHARS}.
539
+ */
540
+ function buildBodyPreview(text) {
541
+ const t = String(text ?? "");
542
+ return t.length > ERROR_BODY_PREVIEW_MAX_CHARS
543
+ ? t.slice(0, ERROR_BODY_PREVIEW_MAX_CHARS) + "…"
544
+ : t;
545
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Snapshot of allowed (and revoked) customer service tokens.
3
+ *
4
+ * IMPORTANT:
5
+ * - Never include tokens in clear.
6
+ * - Tokens are referenced by sha256(utf8(tokenString)) in hex.
7
+ * - Backend should keep this snapshot stable & ordered for reproducible ETags.
8
+ */
9
+ export type DcdrServiceTokenStatus = "ACTIVE" | "REVOKED";
10
+ export type DcdrServiceTokenSnapshotItem = {
11
+ /** Human-friendly identifier ("ci-pipeline", "mobile-app", etc.). */
12
+ id: string;
13
+ /** SHA-256 hash of the full bearer token string (hex). */
14
+ sha256: string;
15
+ status: DcdrServiceTokenStatus;
16
+ /** Token scopes as understood by the gateway (should match or superset payload.scopes policy). */
17
+ scopes: string[];
18
+ /** Optional snapshot-side expiry hint (unix ms). Token exp enforcement remains the token payload exp. */
19
+ exp?: number;
20
+ /** Optional note for operators. */
21
+ note?: string;
22
+ };
23
+ export type DcdrServiceTokensSnapshotContract = {
24
+ cid: string;
25
+ tokens: DcdrServiceTokenSnapshotItem[];
26
+ /** Snapshot issued at (unix ms). Optional. */
27
+ iat?: number;
28
+ };
29
+ //# sourceMappingURL=service-tokens.contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-tokens.contract.d.ts","sourceRoot":"","sources":["../src/service-tokens.contract.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE1D,MAAM,MAAM,4BAA4B,GAAG;IACzC,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IAEX,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IAEf,MAAM,EAAE,sBAAsB,CAAC;IAE/B,kGAAkG;IAClG,MAAM,EAAE,MAAM,EAAE,CAAC;IAEjB,yGAAyG;IACzG,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,4BAA4B,EAAE,CAAC;IACvC,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * Snapshot of allowed (and revoked) customer service tokens.
4
+ *
5
+ * IMPORTANT:
6
+ * - Never include tokens in clear.
7
+ * - Tokens are referenced by sha256(utf8(tokenString)) in hex.
8
+ * - Backend should keep this snapshot stable & ordered for reproducible ETags.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Generic session token payload for dcdr.
3
+ * - id: session id (e.g. missionId)
4
+ * - aid: app id (e.g. scraperId)
5
+ */
6
+ export interface DcdrSessionPayload {
7
+ id: string;
8
+ aid: string;
9
+ cid?: string;
10
+ iat: number;
11
+ exp: number;
12
+ /**
13
+ * Allowed scopes for this session token.
14
+ *
15
+ * Notes
16
+ * - Scopes are treated as opaque strings by the token format; semantics are enforced by the runtime.
17
+ * - The runtime may require execution scopes for `/api/execution/*` endpoints.
18
+ *
19
+ * Common scope forms
20
+ * - `execute:*` to allow executing any intent
21
+ * - `execute:<INTENT>` to allow a specific intent
22
+ * - Legacy (backward compatible): include the intent name itself (e.g. `BANKING_INCIDENT_CLASSIFIER`).
23
+ * - `*` grants full access.
24
+ */
25
+ scopes: string[];
26
+ }
27
+ /**
28
+ * Crypto dependencies injected from Node runtimes (backend/dcdr).
29
+ * This avoids importing "crypto" in shared libs that may be bundled for web.
30
+ */
31
+ export interface HmacDeps {
32
+ createHmac: (alg: "sha256", key: string) => {
33
+ update: (data: string) => any;
34
+ digest: (encoding: "base64") => string;
35
+ };
36
+ timingSafeEqual?: (a: Uint8Array, b: Uint8Array) => boolean;
37
+ }
38
+ /**
39
+ * Token format:
40
+ * token = base64url(JSON(payload)) + "." + base64url(HMAC_SHA256(secret, payloadB64url))
41
+ */
42
+ export declare class DcdrSessionToken {
43
+ static sign(deps: HmacDeps, payload: DcdrSessionPayload, secret: string): string;
44
+ static verify(deps: HmacDeps, token: string, secrets: string[] | string, opts?: {
45
+ clockSkewSeconds?: number;
46
+ revokeBeforeIat?: number;
47
+ }): DcdrSessionPayload;
48
+ static decodeUnverified(token: string): DcdrSessionPayload;
49
+ private static assertValidPayload;
50
+ }
51
+ //# sourceMappingURL=session.contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.contract.d.ts","sourceRoot":"","sources":["../src/session.contract.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;;;;;OAYG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,CACV,GAAG,EAAE,QAAQ,EACb,GAAG,EAAE,MAAM,KACR;QACH,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,CAAC;QAC9B,MAAM,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,MAAM,CAAC;KACxC,CAAC;IACF,eAAe,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,KAAK,OAAO,CAAC;CAC7D;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,MAAM,CAAC,IAAI,CACT,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,kBAAkB,EAC3B,MAAM,EAAE,MAAM,GACb,MAAM;IAeT,MAAM,CAAC,MAAM,CACX,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAC1B,IAAI,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7D,kBAAkB;IAsDrB,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB;IAa1D,OAAO,CAAC,MAAM,CAAC,kBAAkB;CAoBlC"}