@stigg/agent-sdk-js 1.0.0 → 1.1.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.
@@ -1,10 +1,6 @@
1
- import { type Measurement, type Metrics } from '../../../../types';
1
+ import type { Measurement } from '../../../../types';
2
2
  import type { MetricsFor, MetricsOf } from '../../core/types';
3
- /**
4
- * Accumulates usage across raw stream events: Anthropic sends the input counts on
5
- * `message_start` and the output count on `message_delta`, so one completed stream produces one
6
- * measurement rather than one per event.
7
- */
3
+ /** `message_delta` restates the whole message's running totals, input included, so its counters replace `message_start`'s rather than add to them. */
8
4
  declare function observeStream(stream: any, ingest: (measurement: Measurement) => void): void;
9
5
  export declare const anthropicRecipe: {
10
6
  vendor: string;
@@ -14,7 +10,7 @@ export declare const anthropicRecipe: {
14
10
  methods: {
15
11
  path: string[];
16
12
  extractUsage: (result: any) => {
17
- metrics: Metrics;
13
+ metrics: import("../../../../types").Metrics;
18
14
  usage: import("../../../../types").TokenUsage;
19
15
  raw: any;
20
16
  } | undefined;
@@ -11,50 +11,38 @@ exports.estimateFromMaxTokens = exports.anthropicRecipe = void 0;
11
11
  * already handles failure.
12
12
  */
13
13
  const token_usage_1 = require("../../../shared/token-usage");
14
- const token_usage_2 = require("../../../shared/token-usage");
15
- const types_1 = require("../../../../types");
16
14
  const estimate_1 = require("../../../shared/estimate");
17
15
  const registry_1 = require("../../core/registry");
18
- /**
19
- * Accumulates usage across raw stream events: Anthropic sends the input counts on
20
- * `message_start` and the output count on `message_delta`, so one completed stream produces one
21
- * measurement rather than one per event.
22
- */
16
+ /** `message_delta` restates the whole message's running totals, input included, so its counters replace `message_start`'s rather than add to them. */
23
17
  function observeStream(stream, ingest) {
24
18
  if (!stream || typeof stream[Symbol.asyncIterator] !== 'function')
25
19
  return;
26
20
  const iterate = stream[Symbol.asyncIterator].bind(stream);
27
21
  stream[Symbol.asyncIterator] = async function* gatedIterator() {
28
- let total = (0, types_1.emptyTokenUsage)();
29
- // Accumulated beside the normalized counters: a stream states its own names across two
30
- // events, and what the run reports by default is those names.
31
- let metrics = {};
22
+ // A counter an event leaves out keeps its last value.
23
+ let usage = {};
32
24
  try {
33
25
  for await (const event of iterate()) {
34
- if (event?.type === 'message_start') {
35
- total = (0, token_usage_2.addTokenUsage)(total, (0, token_usage_1.fromFlatTokenUsage)(event.message?.usage));
36
- metrics = (0, token_usage_1.addMetrics)(metrics, (0, token_usage_1.pickMetrics)(event.message?.usage, token_usage_1.ANTHROPIC_COMPONENT_METRICS));
37
- }
38
- if (event?.type === 'message_delta') {
39
- total = (0, token_usage_2.addTokenUsage)(total, (0, token_usage_1.fromFlatTokenUsage)(event.usage));
40
- metrics = (0, token_usage_1.addMetrics)(metrics, (0, token_usage_1.pickMetrics)(event.usage, token_usage_1.ANTHROPIC_COMPONENT_METRICS));
41
- }
26
+ if (event?.type === 'message_start')
27
+ usage = { ...usage, ...stated(event.message?.usage) };
28
+ if (event?.type === 'message_delta')
29
+ usage = { ...usage, ...stated(event.usage) };
42
30
  yield event;
43
31
  }
44
32
  }
45
33
  finally {
46
- // Meter what was actually produced, even if the consumer broke out of the loop early.
47
- //
48
- // No `raw` here, deliberately, and this is the one vendor that cannot have one: the counters
49
- // arrive split across `message_start` and `message_delta`, so there is no single usage block
50
- // on the wire to carry. The Gemini and OpenAI streams each report one whole block and do
51
- // pass it on; anything passed here would be an object this stream never actually sent.
52
- // The total is added here rather than per event: these counters arrive across two of
53
- // them, so summing a half-read call would report a total that was never true of it.
54
- ingest({ metrics: (0, token_usage_1.withComputedTotal)(metrics, token_usage_1.COMPUTED_TOTALS.anthropic), usage: total });
34
+ // Meter what was actually produced, even if the consumer broke out of the loop early. No `raw`,
35
+ // because the usage is assembled from two events rather than sent as one block.
36
+ ingest({
37
+ metrics: (0, token_usage_1.withComputedTotal)((0, token_usage_1.pickMetrics)(usage, token_usage_1.ANTHROPIC_COMPONENT_METRICS), token_usage_1.COMPUTED_TOTALS.anthropic),
38
+ usage: (0, token_usage_1.fromFlatTokenUsage)(usage),
39
+ });
55
40
  }
56
41
  };
57
42
  }
43
+ /** Keeps only counts, so a missing, null or malformed counter can't erase one. */
44
+ const stated = (usage) => Object.fromEntries(Object.entries(usage ?? {}).filter(([, value]) => isCount(value)));
45
+ const isCount = (value) => typeof value === 'number' && Number.isInteger(value) && value >= 0;
58
46
  exports.anthropicRecipe = {
59
47
  vendor: 'anthropic',
60
48
  metrics: token_usage_1.ANTHROPIC_METRICS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigg/agent-sdk-js",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Stigg agent SDK (JS) - run-lifecycle governance for agentic stacks, with examples per layer",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -57,7 +57,7 @@
57
57
  "node": ">=22"
58
58
  },
59
59
  "dependencies": {
60
- "@stigg/node-server-sdk": "6.0.0",
60
+ "@stigg/node-server-sdk": "6.1.0",
61
61
  "@stigg/typescript": "0.1.0-beta.55",
62
62
  "zod": "^4.6.1"
63
63
  },