opsveritas-sdk 0.3.3 → 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,9 +357,70 @@ 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;
410
+ var TOOL_INPUT_TEXT_FIELDS = ["body", "content", "text", "summary", "message", "output"];
411
+ function extractToolInputText(input) {
412
+ if (!input || typeof input !== "object") return void 0;
413
+ const obj = input;
414
+ for (const field of TOOL_INPUT_TEXT_FIELDS) {
415
+ const v = obj[field];
416
+ if (typeof v === "string" && v.trim()) return v;
417
+ }
418
+ try {
419
+ return JSON.stringify(obj);
420
+ } catch {
421
+ return void 0;
422
+ }
423
+ }
361
424
  function extractOutput(resp) {
362
425
  if (!resp) return void 0;
363
426
  const choices = resp.choices;
@@ -369,6 +432,11 @@ function extractOutput(resp) {
369
432
  if (Array.isArray(blocks)) {
370
433
  const tb = blocks.find((b) => b.type === "text");
371
434
  if (typeof tb?.text === "string" && tb.text.trim()) return tb.text.slice(0, OUTPUT_SUMMARY_MAX);
435
+ const toolBlock = blocks.find((b) => b.type === "tool_use");
436
+ if (toolBlock) {
437
+ const s = extractToolInputText(toolBlock.input);
438
+ if (s) return s.slice(0, OUTPUT_SUMMARY_MAX);
439
+ }
372
440
  }
373
441
  return void 0;
374
442
  }
@@ -376,7 +444,10 @@ function patchOpenAI(client, opts) {
376
444
  const completions = client.chat?.completions;
377
445
  if (!completions || completions[PATCHED]) return;
378
446
  const orig = completions.create.bind(completions);
447
+ startPolling(opts.agentName, "sdk");
379
448
  completions.create = async function(...args) {
449
+ const killState = isKilled(opts.agentName);
450
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
380
451
  const executedAt = (/* @__PURE__ */ new Date()).toISOString();
381
452
  const t0 = Date.now();
382
453
  let status = "success";
@@ -427,7 +498,10 @@ function patchAnthropic(client, opts) {
427
498
  const messages = client.messages;
428
499
  if (!messages || messages[PATCHED]) return;
429
500
  const orig = messages.create.bind(messages);
501
+ startPolling(opts.agentName, "sdk");
430
502
  messages.create = async function(...args) {
503
+ const killState = isKilled(opts.agentName);
504
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
431
505
  const executedAt = (/* @__PURE__ */ new Date()).toISOString();
432
506
  const t0 = Date.now();
433
507
  let status = "success";
@@ -476,7 +550,10 @@ function patchGemini(client, opts) {
476
550
  if (client[PATCHED]) return;
477
551
  const modelName = typeof client.model === "string" ? client.model.replace(/^models\//, "") : void 0;
478
552
  const orig = client.generateContent.bind(client);
553
+ startPolling(opts.agentName, "sdk");
479
554
  client.generateContent = async function(...args) {
555
+ const killState = isKilled(opts.agentName);
556
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
480
557
  const executedAt = (/* @__PURE__ */ new Date()).toISOString();
481
558
  const t0 = Date.now();
482
559
  let status = "success";
@@ -536,6 +613,7 @@ function wrap(client, opts) {
536
613
  }
537
614
 
538
615
  // src/langchain.ts
616
+ var LC_PATCHED = /* @__PURE__ */ Symbol("opsveritas.langchain.patched");
539
617
  function extractUsage2(output) {
540
618
  let model;
541
619
  let inputTokens;
@@ -639,16 +717,41 @@ function langchain(agentName, opts = {}) {
639
717
  }
640
718
  };
641
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
+ }
642
743
 
643
744
  // src/index.ts
644
- var OpsVeritas = { init, run, trace, wrap, langchain };
745
+ var OpsVeritas = { init, run, trace, wrap, langchain, wrapLangChain };
645
746
  var index_default = OpsVeritas;
646
747
  // Annotate the CommonJS export names for ESM import in node:
647
748
  0 && (module.exports = {
648
749
  OpsVeritas,
750
+ OpsVeritasKilledError,
649
751
  init,
650
752
  langchain,
651
753
  run,
652
754
  trace,
653
- wrap
755
+ wrap,
756
+ wrapLangChain
654
757
  });
package/dist/index.mjs CHANGED
@@ -323,9 +323,70 @@ 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;
376
+ var TOOL_INPUT_TEXT_FIELDS = ["body", "content", "text", "summary", "message", "output"];
377
+ function extractToolInputText(input) {
378
+ if (!input || typeof input !== "object") return void 0;
379
+ const obj = input;
380
+ for (const field of TOOL_INPUT_TEXT_FIELDS) {
381
+ const v = obj[field];
382
+ if (typeof v === "string" && v.trim()) return v;
383
+ }
384
+ try {
385
+ return JSON.stringify(obj);
386
+ } catch {
387
+ return void 0;
388
+ }
389
+ }
329
390
  function extractOutput(resp) {
330
391
  if (!resp) return void 0;
331
392
  const choices = resp.choices;
@@ -337,6 +398,11 @@ function extractOutput(resp) {
337
398
  if (Array.isArray(blocks)) {
338
399
  const tb = blocks.find((b) => b.type === "text");
339
400
  if (typeof tb?.text === "string" && tb.text.trim()) return tb.text.slice(0, OUTPUT_SUMMARY_MAX);
401
+ const toolBlock = blocks.find((b) => b.type === "tool_use");
402
+ if (toolBlock) {
403
+ const s = extractToolInputText(toolBlock.input);
404
+ if (s) return s.slice(0, OUTPUT_SUMMARY_MAX);
405
+ }
340
406
  }
341
407
  return void 0;
342
408
  }
@@ -344,7 +410,10 @@ function patchOpenAI(client, opts) {
344
410
  const completions = client.chat?.completions;
345
411
  if (!completions || completions[PATCHED]) return;
346
412
  const orig = completions.create.bind(completions);
413
+ startPolling(opts.agentName, "sdk");
347
414
  completions.create = async function(...args) {
415
+ const killState = isKilled(opts.agentName);
416
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
348
417
  const executedAt = (/* @__PURE__ */ new Date()).toISOString();
349
418
  const t0 = Date.now();
350
419
  let status = "success";
@@ -395,7 +464,10 @@ function patchAnthropic(client, opts) {
395
464
  const messages = client.messages;
396
465
  if (!messages || messages[PATCHED]) return;
397
466
  const orig = messages.create.bind(messages);
467
+ startPolling(opts.agentName, "sdk");
398
468
  messages.create = async function(...args) {
469
+ const killState = isKilled(opts.agentName);
470
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
399
471
  const executedAt = (/* @__PURE__ */ new Date()).toISOString();
400
472
  const t0 = Date.now();
401
473
  let status = "success";
@@ -444,7 +516,10 @@ function patchGemini(client, opts) {
444
516
  if (client[PATCHED]) return;
445
517
  const modelName = typeof client.model === "string" ? client.model.replace(/^models\//, "") : void 0;
446
518
  const orig = client.generateContent.bind(client);
519
+ startPolling(opts.agentName, "sdk");
447
520
  client.generateContent = async function(...args) {
521
+ const killState = isKilled(opts.agentName);
522
+ if (killState.killed) throw new OpsVeritasKilledError(opts.agentName, killState.reason);
448
523
  const executedAt = (/* @__PURE__ */ new Date()).toISOString();
449
524
  const t0 = Date.now();
450
525
  let status = "success";
@@ -504,6 +579,7 @@ function wrap(client, opts) {
504
579
  }
505
580
 
506
581
  // src/langchain.ts
582
+ var LC_PATCHED = /* @__PURE__ */ Symbol("opsveritas.langchain.patched");
507
583
  function extractUsage2(output) {
508
584
  let model;
509
585
  let inputTokens;
@@ -607,16 +683,41 @@ function langchain(agentName, opts = {}) {
607
683
  }
608
684
  };
609
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
+ }
610
709
 
611
710
  // src/index.ts
612
- var OpsVeritas = { init, run, trace, wrap, langchain };
711
+ var OpsVeritas = { init, run, trace, wrap, langchain, wrapLangChain };
613
712
  var index_default = OpsVeritas;
614
713
  export {
615
714
  OpsVeritas,
715
+ OpsVeritasKilledError,
616
716
  index_default as default,
617
717
  init,
618
718
  langchain,
619
719
  run,
620
720
  trace,
621
- wrap
721
+ wrap,
722
+ wrapLangChain
622
723
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opsveritas-sdk",
3
- "version": "0.3.3",
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",