opsveritas-sdk 0.3.4 → 0.4.1

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,34 @@ 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
+ stream?: (...args: unknown[]) => unknown;
52
+ astream?: (...args: unknown[]) => unknown;
53
+ batch?: (...args: unknown[]) => unknown;
54
+ [key: string]: unknown;
55
+ }
56
+ /**
57
+ * Kill-switch enforcement for LangChain models — ADDITIVE, does not replace
58
+ * `langchain()` above. `langchain()` is telemetry-only (a callback handler;
59
+ * LangChain's callback manager swallows exceptions raised inside callbacks by
60
+ * default, so it can never actually block a call). This patches the model's
61
+ * own `invoke`/`ainvoke`/`stream`/`astream`/`batch` directly — the same
62
+ * technique wrap() uses on raw provider clients — so a kill-switch check
63
+ * genuinely runs before the real API call happens.
64
+ *
65
+ * const model = new ChatOpenAI({ callbacks: [opsveritas.langchain('My Agent')] }); // telemetry
66
+ * opsveritas.wrapLangChain(model, { agentName: 'My Agent' }); // enforcement
67
+ *
68
+ * `stream`/`astream` are gated at call-start only (check-then-call, same as
69
+ * invoke/ainvoke) — NOT mid-stream cancellation. If the kill switch fires
70
+ * after a stream has already started, that in-flight stream still completes;
71
+ * only the *next* call is blocked. This is a deliberate, simpler scope than
72
+ * killing a stream already in progress, and matches the same bounded-delay
73
+ * philosophy as the kill switch's poll interval.
74
+ */
75
+ declare function wrapLangChain<T extends LangChainModel>(model: T, opts: WrapOptions): T;
42
76
 
43
77
  interface ExecutionPayload {
44
78
  platform: string;
@@ -66,6 +100,7 @@ declare const OpsVeritas: {
66
100
  trace: typeof trace;
67
101
  wrap: typeof wrap;
68
102
  langchain: typeof langchain;
103
+ wrapLangChain: typeof wrapLangChain;
69
104
  };
70
105
 
71
- export { type ExecutionPayload, type LangChainOptions, OpsVeritas, type TraceOptions, type WrapOptions, OpsVeritas as default, init, langchain, run, trace, wrap };
106
+ 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,34 @@ 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
+ stream?: (...args: unknown[]) => unknown;
52
+ astream?: (...args: unknown[]) => unknown;
53
+ batch?: (...args: unknown[]) => unknown;
54
+ [key: string]: unknown;
55
+ }
56
+ /**
57
+ * Kill-switch enforcement for LangChain models — ADDITIVE, does not replace
58
+ * `langchain()` above. `langchain()` is telemetry-only (a callback handler;
59
+ * LangChain's callback manager swallows exceptions raised inside callbacks by
60
+ * default, so it can never actually block a call). This patches the model's
61
+ * own `invoke`/`ainvoke`/`stream`/`astream`/`batch` directly — the same
62
+ * technique wrap() uses on raw provider clients — so a kill-switch check
63
+ * genuinely runs before the real API call happens.
64
+ *
65
+ * const model = new ChatOpenAI({ callbacks: [opsveritas.langchain('My Agent')] }); // telemetry
66
+ * opsveritas.wrapLangChain(model, { agentName: 'My Agent' }); // enforcement
67
+ *
68
+ * `stream`/`astream` are gated at call-start only (check-then-call, same as
69
+ * invoke/ainvoke) — NOT mid-stream cancellation. If the kill switch fires
70
+ * after a stream has already started, that in-flight stream still completes;
71
+ * only the *next* call is blocked. This is a deliberate, simpler scope than
72
+ * killing a stream already in progress, and matches the same bounded-delay
73
+ * philosophy as the kill switch's poll interval.
74
+ */
75
+ declare function wrapLangChain<T extends LangChainModel>(model: T, opts: WrapOptions): T;
42
76
 
43
77
  interface ExecutionPayload {
44
78
  platform: string;
@@ -66,6 +100,7 @@ declare const OpsVeritas: {
66
100
  trace: typeof trace;
67
101
  wrap: typeof wrap;
68
102
  langchain: typeof langchain;
103
+ wrapLangChain: typeof wrapLangChain;
69
104
  };
70
105
 
71
- export { type ExecutionPayload, type LangChainOptions, OpsVeritas, type TraceOptions, type WrapOptions, OpsVeritas as default, init, langchain, run, trace, wrap };
106
+ 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,64 @@ 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
+ const guard = () => {
725
+ const killState = isKilled(opts.agentName);
726
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
727
+ };
728
+ if (typeof m.invoke === "function") {
729
+ const origInvoke = m.invoke.bind(model);
730
+ m.invoke = function(...args) {
731
+ guard();
732
+ return origInvoke(...args);
733
+ };
734
+ }
735
+ if (typeof m.ainvoke === "function") {
736
+ const origAinvoke = m.ainvoke.bind(model);
737
+ m.ainvoke = async function(...args) {
738
+ guard();
739
+ return origAinvoke(...args);
740
+ };
741
+ }
742
+ if (typeof m.stream === "function") {
743
+ const origStream = m.stream.bind(model);
744
+ m.stream = function(...args) {
745
+ guard();
746
+ return origStream(...args);
747
+ };
748
+ }
749
+ if (typeof m.astream === "function") {
750
+ const origAstream = m.astream.bind(model);
751
+ m.astream = function(...args) {
752
+ guard();
753
+ return origAstream(...args);
754
+ };
755
+ }
756
+ if (typeof m.batch === "function") {
757
+ const origBatch = m.batch.bind(model);
758
+ m.batch = async function(...args) {
759
+ guard();
760
+ return origBatch(...args);
761
+ };
762
+ }
763
+ m[LC_PATCHED] = true;
764
+ return model;
765
+ }
661
766
 
662
767
  // src/index.ts
663
- var OpsVeritas = { init, run, trace, wrap, langchain };
768
+ var OpsVeritas = { init, run, trace, wrap, langchain, wrapLangChain };
664
769
  var index_default = OpsVeritas;
665
770
  // Annotate the CommonJS export names for ESM import in node:
666
771
  0 && (module.exports = {
667
772
  OpsVeritas,
773
+ OpsVeritasKilledError,
668
774
  init,
669
775
  langchain,
670
776
  run,
671
777
  trace,
672
- wrap
778
+ wrap,
779
+ wrapLangChain
673
780
  });
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,64 @@ 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
+ const guard = () => {
691
+ const killState = isKilled(opts.agentName);
692
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
693
+ };
694
+ if (typeof m.invoke === "function") {
695
+ const origInvoke = m.invoke.bind(model);
696
+ m.invoke = function(...args) {
697
+ guard();
698
+ return origInvoke(...args);
699
+ };
700
+ }
701
+ if (typeof m.ainvoke === "function") {
702
+ const origAinvoke = m.ainvoke.bind(model);
703
+ m.ainvoke = async function(...args) {
704
+ guard();
705
+ return origAinvoke(...args);
706
+ };
707
+ }
708
+ if (typeof m.stream === "function") {
709
+ const origStream = m.stream.bind(model);
710
+ m.stream = function(...args) {
711
+ guard();
712
+ return origStream(...args);
713
+ };
714
+ }
715
+ if (typeof m.astream === "function") {
716
+ const origAstream = m.astream.bind(model);
717
+ m.astream = function(...args) {
718
+ guard();
719
+ return origAstream(...args);
720
+ };
721
+ }
722
+ if (typeof m.batch === "function") {
723
+ const origBatch = m.batch.bind(model);
724
+ m.batch = async function(...args) {
725
+ guard();
726
+ return origBatch(...args);
727
+ };
728
+ }
729
+ m[LC_PATCHED] = true;
730
+ return model;
731
+ }
629
732
 
630
733
  // src/index.ts
631
- var OpsVeritas = { init, run, trace, wrap, langchain };
734
+ var OpsVeritas = { init, run, trace, wrap, langchain, wrapLangChain };
632
735
  var index_default = OpsVeritas;
633
736
  export {
634
737
  OpsVeritas,
738
+ OpsVeritasKilledError,
635
739
  index_default as default,
636
740
  init,
637
741
  langchain,
638
742
  run,
639
743
  trace,
640
- wrap
744
+ wrap,
745
+ wrapLangChain
641
746
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opsveritas-sdk",
3
- "version": "0.3.4",
3
+ "version": "0.4.1",
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",