opsveritas-sdk 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -13,6 +13,12 @@ interface TraceOptions {
13
13
  }
14
14
  declare function trace<T>(agentName: string, fn: () => Promise<T>, options?: TraceOptions): Promise<T>;
15
15
 
16
+ declare class OpsVeritasKilledError extends Error {
17
+ agentName: string;
18
+ reason?: string | undefined;
19
+ constructor(agentName: string, reason?: string | undefined);
20
+ }
21
+
16
22
  interface WrapOptions {
17
23
  agentName: string;
18
24
  platform?: string;
@@ -39,6 +45,28 @@ interface LangChainOptions {
39
45
  userId?: string;
40
46
  }
41
47
  declare function langchain(agentName: string, opts?: LangChainOptions): Record<string, unknown>;
48
+ interface LangChainModel {
49
+ invoke?: (...args: unknown[]) => unknown;
50
+ ainvoke?: (...args: unknown[]) => Promise<unknown>;
51
+ [key: string]: unknown;
52
+ }
53
+ /**
54
+ * Kill-switch enforcement for LangChain models — ADDITIVE, does not replace
55
+ * `langchain()` above. `langchain()` is telemetry-only (a callback handler;
56
+ * LangChain's callback manager swallows exceptions raised inside callbacks by
57
+ * default, so it can never actually block a call). This patches the model's
58
+ * own `invoke`/`ainvoke` directly — the same technique wrap() uses on raw
59
+ * provider clients — so a kill-switch check genuinely runs before the real
60
+ * API call happens.
61
+ *
62
+ * const model = new ChatOpenAI({ callbacks: [opsveritas.langchain('My Agent')] }); // telemetry
63
+ * opsveritas.wrapLangChain(model, { agentName: 'My Agent' }); // enforcement
64
+ *
65
+ * Scoped to invoke/ainvoke only — stream/astream/batch are out of scope for
66
+ * this pass (streaming needs mid-stream cancellation semantics; most LangChain
67
+ * usage goes through invoke/ainvoke).
68
+ */
69
+ declare function wrapLangChain<T extends LangChainModel>(model: T, opts: WrapOptions): T;
42
70
 
43
71
  interface ExecutionPayload {
44
72
  platform: string;
@@ -66,6 +94,7 @@ declare const OpsVeritas: {
66
94
  trace: typeof trace;
67
95
  wrap: typeof wrap;
68
96
  langchain: typeof langchain;
97
+ wrapLangChain: typeof wrapLangChain;
69
98
  };
70
99
 
71
- export { type ExecutionPayload, type LangChainOptions, OpsVeritas, type TraceOptions, type WrapOptions, OpsVeritas as default, init, langchain, run, trace, wrap };
100
+ export { type ExecutionPayload, type LangChainOptions, OpsVeritas, OpsVeritasKilledError, type TraceOptions, type WrapOptions, OpsVeritas as default, init, langchain, run, trace, wrap, wrapLangChain };
package/dist/index.d.ts CHANGED
@@ -13,6 +13,12 @@ interface TraceOptions {
13
13
  }
14
14
  declare function trace<T>(agentName: string, fn: () => Promise<T>, options?: TraceOptions): Promise<T>;
15
15
 
16
+ declare class OpsVeritasKilledError extends Error {
17
+ agentName: string;
18
+ reason?: string | undefined;
19
+ constructor(agentName: string, reason?: string | undefined);
20
+ }
21
+
16
22
  interface WrapOptions {
17
23
  agentName: string;
18
24
  platform?: string;
@@ -39,6 +45,28 @@ interface LangChainOptions {
39
45
  userId?: string;
40
46
  }
41
47
  declare function langchain(agentName: string, opts?: LangChainOptions): Record<string, unknown>;
48
+ interface LangChainModel {
49
+ invoke?: (...args: unknown[]) => unknown;
50
+ ainvoke?: (...args: unknown[]) => Promise<unknown>;
51
+ [key: string]: unknown;
52
+ }
53
+ /**
54
+ * Kill-switch enforcement for LangChain models — ADDITIVE, does not replace
55
+ * `langchain()` above. `langchain()` is telemetry-only (a callback handler;
56
+ * LangChain's callback manager swallows exceptions raised inside callbacks by
57
+ * default, so it can never actually block a call). This patches the model's
58
+ * own `invoke`/`ainvoke` directly — the same technique wrap() uses on raw
59
+ * provider clients — so a kill-switch check genuinely runs before the real
60
+ * API call happens.
61
+ *
62
+ * const model = new ChatOpenAI({ callbacks: [opsveritas.langchain('My Agent')] }); // telemetry
63
+ * opsveritas.wrapLangChain(model, { agentName: 'My Agent' }); // enforcement
64
+ *
65
+ * Scoped to invoke/ainvoke only — stream/astream/batch are out of scope for
66
+ * this pass (streaming needs mid-stream cancellation semantics; most LangChain
67
+ * usage goes through invoke/ainvoke).
68
+ */
69
+ declare function wrapLangChain<T extends LangChainModel>(model: T, opts: WrapOptions): T;
42
70
 
43
71
  interface ExecutionPayload {
44
72
  platform: string;
@@ -66,6 +94,7 @@ declare const OpsVeritas: {
66
94
  trace: typeof trace;
67
95
  wrap: typeof wrap;
68
96
  langchain: typeof langchain;
97
+ wrapLangChain: typeof wrapLangChain;
69
98
  };
70
99
 
71
- export { type ExecutionPayload, type LangChainOptions, OpsVeritas, type TraceOptions, type WrapOptions, OpsVeritas as default, init, langchain, run, trace, wrap };
100
+ export { type ExecutionPayload, type LangChainOptions, OpsVeritas, OpsVeritasKilledError, type TraceOptions, type WrapOptions, OpsVeritas as default, init, langchain, run, trace, wrap, wrapLangChain };
package/dist/index.js CHANGED
@@ -21,12 +21,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  OpsVeritas: () => OpsVeritas,
24
+ OpsVeritasKilledError: () => OpsVeritasKilledError,
24
25
  default: () => index_default,
25
26
  init: () => init,
26
27
  langchain: () => langchain,
27
28
  run: () => run,
28
29
  trace: () => trace,
29
- wrap: () => wrap
30
+ wrap: () => wrap,
31
+ wrapLangChain: () => wrapLangChain
30
32
  });
31
33
  module.exports = __toCommonJS(index_exports);
32
34
 
@@ -355,6 +357,53 @@ async function trace(agentName, fn, options) {
355
357
  return result;
356
358
  }
357
359
 
360
+ // src/killSwitch.ts
361
+ var POLL_INTERVAL_MS = 18e4;
362
+ var OpsVeritasKilledError = class extends Error {
363
+ constructor(agentName, reason) {
364
+ super(`[OpsVeritas] Agent "${agentName}" is paused (kill switch: ${reason ?? "budget exceeded"}). Call opsveritas restart from the dashboard to resume.`);
365
+ this.agentName = agentName;
366
+ this.reason = reason;
367
+ this.name = "OpsVeritasKilledError";
368
+ }
369
+ };
370
+ var cache = /* @__PURE__ */ new Map();
371
+ var polling = /* @__PURE__ */ new Set();
372
+ async function poll(agentName, framework) {
373
+ let cfg;
374
+ try {
375
+ cfg = getConfig();
376
+ } catch {
377
+ return void 0;
378
+ }
379
+ const params = new URLSearchParams({ agent_name: agentName });
380
+ if (framework) params.set("framework", framework);
381
+ try {
382
+ const res = await fetch(`${cfg.endpoint}/webhooks/agent-budget-status?${params}`, {
383
+ headers: { "x-opsveritas-key": cfg.apiKey }
384
+ });
385
+ if (!res.ok) return void 0;
386
+ const data = await res.json();
387
+ cache.set(agentName, { killed: !!data.killed, reason: data.reason });
388
+ return data.kill_switch_enabled;
389
+ } catch {
390
+ return void 0;
391
+ }
392
+ }
393
+ function startPolling(agentName, framework) {
394
+ if (polling.has(agentName)) return;
395
+ polling.add(agentName);
396
+ void poll(agentName, framework).then((enabled) => {
397
+ if (enabled === false) return;
398
+ const timer = setInterval(() => void poll(agentName, framework), POLL_INTERVAL_MS);
399
+ const t = timer;
400
+ if (typeof t.unref === "function") t.unref();
401
+ });
402
+ }
403
+ function isKilled(agentName) {
404
+ return cache.get(agentName) ?? { killed: false };
405
+ }
406
+
358
407
  // src/wrap.ts
359
408
  var PATCHED = /* @__PURE__ */ Symbol("opsveritas.patched");
360
409
  var OUTPUT_SUMMARY_MAX = 3e3;
@@ -395,7 +444,10 @@ function patchOpenAI(client, opts) {
395
444
  const completions = client.chat?.completions;
396
445
  if (!completions || completions[PATCHED]) return;
397
446
  const orig = completions.create.bind(completions);
447
+ startPolling(opts.agentName, "sdk");
398
448
  completions.create = async function(...args) {
449
+ const killState = isKilled(opts.agentName);
450
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
399
451
  const executedAt = (/* @__PURE__ */ new Date()).toISOString();
400
452
  const t0 = Date.now();
401
453
  let status = "success";
@@ -446,7 +498,10 @@ function patchAnthropic(client, opts) {
446
498
  const messages = client.messages;
447
499
  if (!messages || messages[PATCHED]) return;
448
500
  const orig = messages.create.bind(messages);
501
+ startPolling(opts.agentName, "sdk");
449
502
  messages.create = async function(...args) {
503
+ const killState = isKilled(opts.agentName);
504
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
450
505
  const executedAt = (/* @__PURE__ */ new Date()).toISOString();
451
506
  const t0 = Date.now();
452
507
  let status = "success";
@@ -495,7 +550,10 @@ function patchGemini(client, opts) {
495
550
  if (client[PATCHED]) return;
496
551
  const modelName = typeof client.model === "string" ? client.model.replace(/^models\//, "") : void 0;
497
552
  const orig = client.generateContent.bind(client);
553
+ startPolling(opts.agentName, "sdk");
498
554
  client.generateContent = async function(...args) {
555
+ const killState = isKilled(opts.agentName);
556
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
499
557
  const executedAt = (/* @__PURE__ */ new Date()).toISOString();
500
558
  const t0 = Date.now();
501
559
  let status = "success";
@@ -555,6 +613,7 @@ function wrap(client, opts) {
555
613
  }
556
614
 
557
615
  // src/langchain.ts
616
+ var LC_PATCHED = /* @__PURE__ */ Symbol("opsveritas.langchain.patched");
558
617
  function extractUsage2(output) {
559
618
  let model;
560
619
  let inputTokens;
@@ -658,16 +717,41 @@ function langchain(agentName, opts = {}) {
658
717
  }
659
718
  };
660
719
  }
720
+ function wrapLangChain(model, opts) {
721
+ const m = model;
722
+ if (m[LC_PATCHED]) return model;
723
+ startPolling(opts.agentName, "sdk");
724
+ if (typeof m.invoke === "function") {
725
+ const origInvoke = m.invoke.bind(model);
726
+ m.invoke = function(...args) {
727
+ const killState = isKilled(opts.agentName);
728
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
729
+ return origInvoke(...args);
730
+ };
731
+ }
732
+ if (typeof m.ainvoke === "function") {
733
+ const origAinvoke = m.ainvoke.bind(model);
734
+ m.ainvoke = async function(...args) {
735
+ const killState = isKilled(opts.agentName);
736
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
737
+ return origAinvoke(...args);
738
+ };
739
+ }
740
+ m[LC_PATCHED] = true;
741
+ return model;
742
+ }
661
743
 
662
744
  // src/index.ts
663
- var OpsVeritas = { init, run, trace, wrap, langchain };
745
+ var OpsVeritas = { init, run, trace, wrap, langchain, wrapLangChain };
664
746
  var index_default = OpsVeritas;
665
747
  // Annotate the CommonJS export names for ESM import in node:
666
748
  0 && (module.exports = {
667
749
  OpsVeritas,
750
+ OpsVeritasKilledError,
668
751
  init,
669
752
  langchain,
670
753
  run,
671
754
  trace,
672
- wrap
755
+ wrap,
756
+ wrapLangChain
673
757
  });
package/dist/index.mjs CHANGED
@@ -323,6 +323,53 @@ async function trace(agentName, fn, options) {
323
323
  return result;
324
324
  }
325
325
 
326
+ // src/killSwitch.ts
327
+ var POLL_INTERVAL_MS = 18e4;
328
+ var OpsVeritasKilledError = class extends Error {
329
+ constructor(agentName, reason) {
330
+ super(`[OpsVeritas] Agent "${agentName}" is paused (kill switch: ${reason ?? "budget exceeded"}). Call opsveritas restart from the dashboard to resume.`);
331
+ this.agentName = agentName;
332
+ this.reason = reason;
333
+ this.name = "OpsVeritasKilledError";
334
+ }
335
+ };
336
+ var cache = /* @__PURE__ */ new Map();
337
+ var polling = /* @__PURE__ */ new Set();
338
+ async function poll(agentName, framework) {
339
+ let cfg;
340
+ try {
341
+ cfg = getConfig();
342
+ } catch {
343
+ return void 0;
344
+ }
345
+ const params = new URLSearchParams({ agent_name: agentName });
346
+ if (framework) params.set("framework", framework);
347
+ try {
348
+ const res = await fetch(`${cfg.endpoint}/webhooks/agent-budget-status?${params}`, {
349
+ headers: { "x-opsveritas-key": cfg.apiKey }
350
+ });
351
+ if (!res.ok) return void 0;
352
+ const data = await res.json();
353
+ cache.set(agentName, { killed: !!data.killed, reason: data.reason });
354
+ return data.kill_switch_enabled;
355
+ } catch {
356
+ return void 0;
357
+ }
358
+ }
359
+ function startPolling(agentName, framework) {
360
+ if (polling.has(agentName)) return;
361
+ polling.add(agentName);
362
+ void poll(agentName, framework).then((enabled) => {
363
+ if (enabled === false) return;
364
+ const timer = setInterval(() => void poll(agentName, framework), POLL_INTERVAL_MS);
365
+ const t = timer;
366
+ if (typeof t.unref === "function") t.unref();
367
+ });
368
+ }
369
+ function isKilled(agentName) {
370
+ return cache.get(agentName) ?? { killed: false };
371
+ }
372
+
326
373
  // src/wrap.ts
327
374
  var PATCHED = /* @__PURE__ */ Symbol("opsveritas.patched");
328
375
  var OUTPUT_SUMMARY_MAX = 3e3;
@@ -363,7 +410,10 @@ function patchOpenAI(client, opts) {
363
410
  const completions = client.chat?.completions;
364
411
  if (!completions || completions[PATCHED]) return;
365
412
  const orig = completions.create.bind(completions);
413
+ startPolling(opts.agentName, "sdk");
366
414
  completions.create = async function(...args) {
415
+ const killState = isKilled(opts.agentName);
416
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
367
417
  const executedAt = (/* @__PURE__ */ new Date()).toISOString();
368
418
  const t0 = Date.now();
369
419
  let status = "success";
@@ -414,7 +464,10 @@ function patchAnthropic(client, opts) {
414
464
  const messages = client.messages;
415
465
  if (!messages || messages[PATCHED]) return;
416
466
  const orig = messages.create.bind(messages);
467
+ startPolling(opts.agentName, "sdk");
417
468
  messages.create = async function(...args) {
469
+ const killState = isKilled(opts.agentName);
470
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
418
471
  const executedAt = (/* @__PURE__ */ new Date()).toISOString();
419
472
  const t0 = Date.now();
420
473
  let status = "success";
@@ -463,7 +516,10 @@ function patchGemini(client, opts) {
463
516
  if (client[PATCHED]) return;
464
517
  const modelName = typeof client.model === "string" ? client.model.replace(/^models\//, "") : void 0;
465
518
  const orig = client.generateContent.bind(client);
519
+ startPolling(opts.agentName, "sdk");
466
520
  client.generateContent = async function(...args) {
521
+ const killState = isKilled(opts.agentName);
522
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
467
523
  const executedAt = (/* @__PURE__ */ new Date()).toISOString();
468
524
  const t0 = Date.now();
469
525
  let status = "success";
@@ -523,6 +579,7 @@ function wrap(client, opts) {
523
579
  }
524
580
 
525
581
  // src/langchain.ts
582
+ var LC_PATCHED = /* @__PURE__ */ Symbol("opsveritas.langchain.patched");
526
583
  function extractUsage2(output) {
527
584
  let model;
528
585
  let inputTokens;
@@ -626,16 +683,41 @@ function langchain(agentName, opts = {}) {
626
683
  }
627
684
  };
628
685
  }
686
+ function wrapLangChain(model, opts) {
687
+ const m = model;
688
+ if (m[LC_PATCHED]) return model;
689
+ startPolling(opts.agentName, "sdk");
690
+ if (typeof m.invoke === "function") {
691
+ const origInvoke = m.invoke.bind(model);
692
+ m.invoke = function(...args) {
693
+ const killState = isKilled(opts.agentName);
694
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
695
+ return origInvoke(...args);
696
+ };
697
+ }
698
+ if (typeof m.ainvoke === "function") {
699
+ const origAinvoke = m.ainvoke.bind(model);
700
+ m.ainvoke = async function(...args) {
701
+ const killState = isKilled(opts.agentName);
702
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
703
+ return origAinvoke(...args);
704
+ };
705
+ }
706
+ m[LC_PATCHED] = true;
707
+ return model;
708
+ }
629
709
 
630
710
  // src/index.ts
631
- var OpsVeritas = { init, run, trace, wrap, langchain };
711
+ var OpsVeritas = { init, run, trace, wrap, langchain, wrapLangChain };
632
712
  var index_default = OpsVeritas;
633
713
  export {
634
714
  OpsVeritas,
715
+ OpsVeritasKilledError,
635
716
  index_default as default,
636
717
  init,
637
718
  langchain,
638
719
  run,
639
720
  trace,
640
- wrap
721
+ wrap,
722
+ wrapLangChain
641
723
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opsveritas-sdk",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "Monitor your AI agents in 3 lines of code — tokens, cost, latency, and silent failures",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",