@signalvaultio/node 0.5.1 → 0.5.2

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.ts CHANGED
@@ -2,7 +2,7 @@ import type OpenAI from 'openai';
2
2
  import { ToolsAPI } from './tools';
3
3
  export type { ToolRecordOptions, ToolsAPI } from './tools';
4
4
  /** Kept in sync with package.json by a unit test. */
5
- export declare const SDK_VERSION = "0.5.1";
5
+ export declare const SDK_VERSION = "0.5.2";
6
6
  /**
7
7
  * Normalizes the base URL and refuses plaintext HTTP to anything but the local
8
8
  * machine. The API key and full prompts travel in every request, so an
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ exports.normalizeBaseUrl = normalizeBaseUrl;
5
5
  const crypto_1 = require("crypto");
6
6
  const tools_1 = require("./tools");
7
7
  /** Kept in sync with package.json by a unit test. */
8
- exports.SDK_VERSION = '0.5.1';
8
+ exports.SDK_VERSION = '0.5.2';
9
9
  const DEFAULT_BASE_URL = 'https://api.signalvault.io';
10
10
  const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
11
11
  /** Longest Retry-After the SDK will wait out before retrying a background event once. */
@@ -164,6 +164,39 @@ function anthropicOutputText(response) {
164
164
  .map((b) => b.text)
165
165
  .join('');
166
166
  }
167
+ const ANTHROPIC_USAGE_KEYS = [
168
+ 'input_tokens',
169
+ 'cache_creation_input_tokens',
170
+ 'cache_read_input_tokens',
171
+ 'output_tokens',
172
+ ];
173
+ /** `usage.cache_creation`: the cache writes by cache lifetime, priced differently. */
174
+ const CACHE_CREATION_KEYS = ['ephemeral_5m_input_tokens', 'ephemeral_1h_input_tokens'];
175
+ function copyCounts(target, source, keys) {
176
+ for (const key of keys) {
177
+ const n = source?.[key];
178
+ if (typeof n === 'number' && Number.isFinite(n) && n >= 0)
179
+ target[key] = n;
180
+ }
181
+ return target;
182
+ }
183
+ /**
184
+ * Anthropic usage under Anthropic's own names, as the proxy records it. The
185
+ * server adds the cache counts to `input_tokens`; renamed to `prompt_tokens`,
186
+ * which for OpenAI already includes cached tokens, they would be lost. Later
187
+ * sources override earlier ones, but only with a count.
188
+ */
189
+ function anthropicUsage(...sources) {
190
+ const usage = { input_tokens: 0, output_tokens: 0 };
191
+ let cacheCreation = {};
192
+ for (const source of sources) {
193
+ copyCounts(usage, source, ANTHROPIC_USAGE_KEYS);
194
+ cacheCreation = copyCounts(cacheCreation, source?.cache_creation, CACHE_CREATION_KEYS);
195
+ }
196
+ if (Object.keys(cacheCreation).length)
197
+ usage.cache_creation = cacheCreation;
198
+ return usage;
199
+ }
167
200
  function parseRetryAfterMs(response) {
168
201
  const raw = response?.headers?.get?.('retry-after');
169
202
  if (!raw)
@@ -505,10 +538,13 @@ class SignalVaultClient {
505
538
  const model = params.model;
506
539
  const messages = params.messages;
507
540
  if (params.stream) {
508
- return this.wrapStream(response, usage.injected, (output, promptTokens, completionTokens) => this.sendAuditEvents(requestId, model, messages, output, promptTokens, completionTokens, metadata, 'openai'));
541
+ return this.wrapStream(response, usage.injected, (output, promptTokens, completionTokens) => this.sendAuditEvents(requestId, model, messages, output, { prompt_tokens: promptTokens, completion_tokens: completionTokens }, metadata, 'openai'));
509
542
  }
510
543
  const completion = response;
511
- this.track(this.sendAuditEvents(requestId, model, messages, completion.choices[0]?.message?.content || '', completion.usage?.prompt_tokens || 0, completion.usage?.completion_tokens || 0, metadata, 'openai'));
544
+ this.track(this.sendAuditEvents(requestId, model, messages, completion.choices[0]?.message?.content || '', {
545
+ prompt_tokens: completion.usage?.prompt_tokens || 0,
546
+ completion_tokens: completion.usage?.completion_tokens || 0,
547
+ }, metadata, 'openai'));
512
548
  return completion;
513
549
  }
514
550
  // ---------------------------------------------------------------------------
@@ -522,11 +558,14 @@ class SignalVaultClient {
522
558
  const usage = withStreamUsage(params);
523
559
  const response = await this.openai.chat.completions.create(usage.params);
524
560
  if (params.stream) {
525
- return this.wrapStream(response, usage.injected, (output, promptTokens, completionTokens) => this.sendResponseEvent(requestId, model, output, promptTokens, completionTokens, metadata, 'openai', fallback));
561
+ return this.wrapStream(response, usage.injected, (output, promptTokens, completionTokens) => this.sendResponseEvent(requestId, model, output, { prompt_tokens: promptTokens, completion_tokens: completionTokens }, metadata, 'openai', fallback));
526
562
  }
527
563
  const completion = response;
528
564
  // Background: the audit of the response must not delay returning it.
529
- this.track(this.sendResponseEvent(requestId, model, completion.choices[0]?.message?.content || '', completion.usage?.prompt_tokens || 0, completion.usage?.completion_tokens || 0, metadata, 'openai', fallback));
565
+ this.track(this.sendResponseEvent(requestId, model, completion.choices[0]?.message?.content || '', {
566
+ prompt_tokens: completion.usage?.prompt_tokens || 0,
567
+ completion_tokens: completion.usage?.completion_tokens || 0,
568
+ }, metadata, 'openai', fallback));
530
569
  return completion;
531
570
  }
532
571
  // ---------------------------------------------------------------------------
@@ -536,9 +575,9 @@ class SignalVaultClient {
536
575
  const response = await this.anthropic.messages.create(params);
537
576
  const messages = anthropicMessagesForAudit(params);
538
577
  if (params.stream) {
539
- return this.wrapAnthropicStream(response, (output, inputTokens, outputTokens) => this.sendAuditEvents(requestId, params.model, messages, output, inputTokens, outputTokens, metadata, 'anthropic'));
578
+ return this.wrapAnthropicStream(response, (output, usage) => this.sendAuditEvents(requestId, params.model, messages, output, usage, metadata, 'anthropic'));
540
579
  }
541
- this.track(this.sendAuditEvents(requestId, params.model, messages, anthropicOutputText(response), response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, metadata, 'anthropic'));
580
+ this.track(this.sendAuditEvents(requestId, params.model, messages, anthropicOutputText(response), anthropicUsage(response.usage), metadata, 'anthropic'));
542
581
  return response;
543
582
  }
544
583
  // ---------------------------------------------------------------------------
@@ -550,9 +589,9 @@ class SignalVaultClient {
550
589
  const fallback = this.fallbackRequest(decision, requestId, params.model, messages, metadata, 'anthropic');
551
590
  const response = await this.anthropic.messages.create(params);
552
591
  if (params.stream) {
553
- return this.wrapAnthropicStream(response, (output, inputTokens, outputTokens) => this.sendResponseEvent(requestId, params.model, output, inputTokens, outputTokens, metadata, 'anthropic', fallback));
592
+ return this.wrapAnthropicStream(response, (output, usage) => this.sendResponseEvent(requestId, params.model, output, usage, metadata, 'anthropic', fallback));
554
593
  }
555
- this.track(this.sendResponseEvent(requestId, params.model, anthropicOutputText(response), response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, metadata, 'anthropic', fallback));
594
+ this.track(this.sendResponseEvent(requestId, params.model, anthropicOutputText(response), anthropicUsage(response.usage), metadata, 'anthropic', fallback));
556
595
  return response;
557
596
  }
558
597
  // ---------------------------------------------------------------------------
@@ -590,24 +629,24 @@ class SignalVaultClient {
590
629
  }
591
630
  async *wrapAnthropicStream(stream, onComplete) {
592
631
  const chunks = [];
593
- let inputTokens = 0;
594
- let outputTokens = 0;
632
+ let usage = anthropicUsage();
595
633
  try {
596
634
  for await (const event of stream) {
597
635
  if (event.type === 'content_block_delta' && event.delta?.type === 'text_delta') {
598
636
  chunks.push(event.delta.text || '');
599
637
  }
638
+ // message_delta may repeat the input and cache counts; its values win.
600
639
  if (event.type === 'message_start' && event.message?.usage) {
601
- inputTokens = event.message.usage.input_tokens || 0;
640
+ usage = anthropicUsage(usage, event.message.usage);
602
641
  }
603
642
  if (event.type === 'message_delta' && event.usage) {
604
- outputTokens = event.usage.output_tokens || 0;
643
+ usage = anthropicUsage(usage, event.usage);
605
644
  }
606
645
  yield event;
607
646
  }
608
647
  }
609
648
  finally {
610
- this.track(onComplete(chunks.join(''), inputTokens, outputTokens));
649
+ this.track(onComplete(chunks.join(''), usage));
611
650
  }
612
651
  }
613
652
  // ---------------------------------------------------------------------------
@@ -812,7 +851,7 @@ class SignalVaultClient {
812
851
  dashboard_url: typeof data.dashboard_url === 'string' ? data.dashboard_url : undefined,
813
852
  };
814
853
  }
815
- async sendResponseEvent(requestId, model, output, promptTokens, completionTokens, metadata, provider, fallbackRequest) {
854
+ async sendResponseEvent(requestId, model, output, usage, metadata, provider, fallbackRequest) {
816
855
  if (fallbackRequest === null)
817
856
  return;
818
857
  if (fallbackRequest) {
@@ -829,7 +868,7 @@ class SignalVaultClient {
829
868
  metadata,
830
869
  payload: {
831
870
  output,
832
- usage: { prompt_tokens: promptTokens, completion_tokens: completionTokens },
871
+ usage,
833
872
  },
834
873
  });
835
874
  }
@@ -838,7 +877,7 @@ class SignalVaultClient {
838
877
  * purpose — the server rejects an ai.response whose ai.request it has not
839
878
  * stored yet, so sending both at once can lose the response.
840
879
  */
841
- async sendAuditEvents(requestId, model, messages, output, promptTokens, completionTokens, metadata, provider) {
880
+ async sendAuditEvents(requestId, model, messages, output, usage, metadata, provider) {
842
881
  const base = {
843
882
  request_id: requestId,
844
883
  environment: this.svEnvironment,
@@ -856,7 +895,7 @@ class SignalVaultClient {
856
895
  type: 'ai.response',
857
896
  payload: {
858
897
  output,
859
- usage: { prompt_tokens: promptTokens, completion_tokens: completionTokens },
898
+ usage,
860
899
  monitor_mode: true,
861
900
  },
862
901
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signalvaultio/node",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "SignalVault SDK - AI audit logs and guardrails for safe production AI",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",