@rulvar/anthropic 1.5.2 → 1.6.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/README.md CHANGED
@@ -6,7 +6,7 @@ compilation, `pause_turn` continuation, typed refusal outcomes, and
6
6
  usage normalization. Exports the `anthropic` adapter factory; models are
7
7
  addressed as `'anthropic:<model>'` in routing.
8
8
 
9
- Part of [rulvar](https://rulvar.com), an embeddable TypeScript engine
9
+ Part of [Rulvar](https://rulvar.com), an embeddable TypeScript engine
10
10
  for durable, budget-bounded multi-agent LLM workflows, where a completed
11
11
  LLM call is never paid for twice. Full documentation:
12
12
  [docs.rulvar.com](https://docs.rulvar.com).
package/dist/index.d.ts CHANGED
@@ -17,7 +17,12 @@ declare const ANTHROPIC_MODELS: Record<string, AnthropicModelInfo>;
17
17
  /**
18
18
  * Unknown Anthropic models are assumed current-generation: adaptive
19
19
  * thinking, native structured outputs, no sampling parameters. refreshCaps
20
- * corrects window/output figures from the live model list.
20
+ * corrects window/output figures from the live model list. Pricing stays
21
+ * ABSENT for an unknown model: a fabricated row silently misprices every
22
+ * model newer than this table. Hosts price it via a versioned
23
+ * createEngine({ pricing }) row; until then its usage surfaces in
24
+ * CostReport.unpriced and a run ceiling warns that it cannot bound the
25
+ * model.
21
26
  */
22
27
  declare function anthropicModelInfo(model: string): AnthropicModelInfo;
23
28
  //#endregion
@@ -102,23 +107,26 @@ declare function mapStopReason(stopReason: string | null | undefined, stopDetail
102
107
  */
103
108
  declare function normalizeAnthropicUsage(raw: Record<string, unknown> | undefined): Usage;
104
109
  interface TurnMapping {
105
- events: ChatEvent[];
106
110
  /** Assistant content blocks collected verbatim (pause_turn continuation). */
107
111
  assistantContent: Block[];
108
112
  pauseTurn: boolean;
109
113
  finished: boolean;
110
114
  }
111
115
  /**
112
- * Maps one Messages API stream into ChatEvents. Emits an early usage event
113
- * from message_start (the input side is known immediately) and exactly one
114
- * terminal finish unless the turn paused (pause_turn) or errored.
115
- * `carryRetained` holds thinking blocks from earlier pause_turn
116
- * continuations of the same turn so the terminal finish ships the whole
117
- * turn's retention payload (M4-T02).
116
+ * Maps one Messages API stream into ChatEvents, yielding each canonical
117
+ * event AS the corresponding provider event is consumed: the consumer's
118
+ * pull drives the provider read (natural backpressure, no buffering, no
119
+ * detached work). The generator's RETURN value carries the accumulated
120
+ * turn state the adapter needs for pause_turn continuation. Yields an
121
+ * early usage event from message_start (the input side is known
122
+ * immediately) and exactly one terminal finish unless the turn paused
123
+ * (pause_turn) or errored. `carryRetained` holds thinking blocks from
124
+ * earlier pause_turn continuations of the same turn so the terminal
125
+ * finish ships the whole turn's retention payload (M4-T02).
118
126
  */
119
- declare function mapAnthropicStream(stream: AsyncIterable<AnthropicStreamEvent>, ids: IdMap, emit: (event: ChatEvent) => void, options?: {
127
+ declare function mapAnthropicStream(stream: AsyncIterable<AnthropicStreamEvent>, ids: IdMap, options?: {
120
128
  carryRetained?: Block[];
121
- }): Promise<TurnMapping>;
129
+ }): AsyncGenerator<ChatEvent, TurnMapping>;
122
130
  /**
123
131
  * Projects an SDK/API error into the retryable WireError vocabulary:
124
132
  * 429 rate limits surface retryAfterMs and the x-ratelimit-* buckets; 529
package/dist/index.js CHANGED
@@ -17,12 +17,12 @@ function current(contextWindow, maxOutputTokens, pricing, cacheMinTokens) {
17
17
  reasoningEfforts: ALL_EFFORTS,
18
18
  contextWindow,
19
19
  maxOutputTokens,
20
- pricing: {
20
+ ...pricing === void 0 ? {} : { pricing: {
21
21
  inputUsdPerMTok: pricing.in,
22
22
  outputUsdPerMTok: pricing.out,
23
23
  cacheReadUsdPerMTok: pricing.cacheRead,
24
24
  cacheWriteUsdPerMTok: pricing.cacheWrite
25
- }
25
+ } }
26
26
  },
27
27
  thinkingForm: "adaptive",
28
28
  cacheMinTokens
@@ -92,18 +92,23 @@ const ANTHROPIC_MODELS = {
92
92
  /**
93
93
  * Unknown Anthropic models are assumed current-generation: adaptive
94
94
  * thinking, native structured outputs, no sampling parameters. refreshCaps
95
- * corrects window/output figures from the live model list.
95
+ * corrects window/output figures from the live model list. Pricing stays
96
+ * ABSENT for an unknown model: a fabricated row silently misprices every
97
+ * model newer than this table. Hosts price it via a versioned
98
+ * createEngine({ pricing }) row; until then its usage surfaces in
99
+ * CostReport.unpriced and a run ceiling warns that it cannot bound the
100
+ * model.
96
101
  */
97
102
  function anthropicModelInfo(model) {
98
103
  const exact = ANTHROPIC_MODELS[model];
99
104
  if (exact !== void 0) return exact;
100
- for (const [name, info] of Object.entries(ANTHROPIC_MODELS)) if (model.startsWith(`${name}-`)) return info;
101
- return current(4e5, 64e3, {
102
- in: 10,
103
- out: 50,
104
- cacheRead: 1,
105
- cacheWrite: 12.5
106
- }, 4096);
105
+ let best;
106
+ for (const [name, info] of Object.entries(ANTHROPIC_MODELS)) if (model.startsWith(`${name}-`) && (best === void 0 || name.length > best.name.length)) best = {
107
+ name,
108
+ info
109
+ };
110
+ if (best !== void 0) return best.info;
111
+ return current(4e5, 64e3, void 0, 4096);
107
112
  }
108
113
  //#endregion
109
114
  //#region src/wire.ts
@@ -412,24 +417,23 @@ function normalizeAnthropicUsage(raw) {
412
417
  };
413
418
  }
414
419
  /**
415
- * Maps one Messages API stream into ChatEvents. Emits an early usage event
416
- * from message_start (the input side is known immediately) and exactly one
417
- * terminal finish unless the turn paused (pause_turn) or errored.
418
- * `carryRetained` holds thinking blocks from earlier pause_turn
419
- * continuations of the same turn so the terminal finish ships the whole
420
- * turn's retention payload (M4-T02).
420
+ * Maps one Messages API stream into ChatEvents, yielding each canonical
421
+ * event AS the corresponding provider event is consumed: the consumer's
422
+ * pull drives the provider read (natural backpressure, no buffering, no
423
+ * detached work). The generator's RETURN value carries the accumulated
424
+ * turn state the adapter needs for pause_turn continuation. Yields an
425
+ * early usage event from message_start (the input side is known
426
+ * immediately) and exactly one terminal finish unless the turn paused
427
+ * (pause_turn) or errored. `carryRetained` holds thinking blocks from
428
+ * earlier pause_turn continuations of the same turn so the terminal
429
+ * finish ships the whole turn's retention payload (M4-T02).
421
430
  */
422
- async function mapAnthropicStream(stream, ids, emit, options) {
431
+ async function* mapAnthropicStream(stream, ids, options) {
423
432
  const mapping = {
424
- events: [],
425
433
  assistantContent: [],
426
434
  pauseTurn: false,
427
435
  finished: false
428
436
  };
429
- const push = (event) => {
430
- mapping.events.push(event);
431
- emit(event);
432
- };
433
437
  let usage = {
434
438
  inputTokens: 0,
435
439
  outputTokens: 0,
@@ -446,10 +450,10 @@ async function mapAnthropicStream(stream, ids, emit, options) {
446
450
  const message = event.message;
447
451
  responseId = typeof message?.id === "string" ? message.id : void 0;
448
452
  usage = normalizeAnthropicUsage(message?.usage);
449
- push({
453
+ yield {
450
454
  type: "usage",
451
455
  usage: { ...usage }
452
- });
456
+ };
453
457
  break;
454
458
  }
455
459
  case "content_block_start": {
@@ -465,11 +469,11 @@ async function mapAnthropicStream(stream, ids, emit, options) {
465
469
  json: "",
466
470
  block: { ...block }
467
471
  });
468
- push({
472
+ yield {
469
473
  type: "tool-call-start",
470
474
  id: canonicalId,
471
475
  name: block.name
472
- });
476
+ };
473
477
  } else if (type === "thinking" || type === "redacted_thinking") openBlocks.set(index, {
474
478
  kind: "thinking",
475
479
  json: "",
@@ -498,25 +502,25 @@ async function mapAnthropicStream(stream, ids, emit, options) {
498
502
  if (delta.type === "text_delta") {
499
503
  const text = delta.text;
500
504
  open.block.text = `${open.block.text ?? ""}${text}`;
501
- push({
505
+ yield {
502
506
  type: "text-delta",
503
507
  text
504
- });
508
+ };
505
509
  } else if (delta.type === "thinking_delta") {
506
510
  const text = delta.thinking;
507
511
  open.block.thinking = `${open.block.thinking ?? ""}${text}`;
508
- push({
512
+ yield {
509
513
  type: "reasoning-delta",
510
514
  text
511
- });
515
+ };
512
516
  } else if (delta.type === "input_json_delta") {
513
517
  const partial = delta.partial_json;
514
518
  open.json += partial;
515
- if (open.canonicalId !== void 0) push({
519
+ if (open.canonicalId !== void 0) yield {
516
520
  type: "tool-call-delta",
517
521
  id: open.canonicalId,
518
522
  argsTextDelta: partial
519
- });
523
+ };
520
524
  } else if (delta.type === "signature_delta") open.block.signature = `${open.block.signature ?? ""}${delta.signature}`;
521
525
  break;
522
526
  }
@@ -533,11 +537,11 @@ async function mapAnthropicStream(stream, ids, emit, options) {
533
537
  args = { __unparsed: open.json };
534
538
  }
535
539
  open.block.input = args;
536
- push({
540
+ yield {
537
541
  type: "tool-call-end",
538
542
  id: open.canonicalId,
539
543
  args
540
- });
544
+ };
541
545
  }
542
546
  mapping.assistantContent.push(open.block);
543
547
  break;
@@ -566,12 +570,12 @@ async function mapAnthropicStream(stream, ids, emit, options) {
566
570
  if (stopSequence !== void 0) meta.stopSequence = stopSequence;
567
571
  const retained = [...options?.carryRetained ?? [], ...mapping.assistantContent.filter((block) => block.type === "thinking" || block.type === "redacted_thinking")];
568
572
  if (retained.length > 0) meta.retainedParts = retained;
569
- push({
573
+ yield {
570
574
  type: "finish",
571
575
  finish: mapped.finish ?? { reason: "stop" },
572
576
  usage,
573
577
  providerMetadata
574
- });
578
+ };
575
579
  mapping.finished = true;
576
580
  return mapping;
577
581
  }
@@ -696,7 +700,6 @@ function anthropic(options = {}) {
696
700
  maxOutputTokens: info.caps.maxOutputTokens,
697
701
  thinkingForm: info.thinkingForm
698
702
  });
699
- const pending = [];
700
703
  let continuations = 0;
701
704
  const carryRetained = [];
702
705
  while (true) {
@@ -713,9 +716,17 @@ function anthropic(options = {}) {
713
716
  };
714
717
  return;
715
718
  }
719
+ const mapper = mapAnthropicStream(stream, ids, { carryRetained });
716
720
  let mapping;
717
721
  try {
718
- mapping = await mapAnthropicStream(stream, ids, (event) => pending.push(event), { carryRetained });
722
+ while (true) {
723
+ const next = await mapper.next();
724
+ if (next.done === true) {
725
+ mapping = next.value;
726
+ break;
727
+ }
728
+ yield next.value;
729
+ }
719
730
  } catch (thrown) {
720
731
  if (signal?.aborted === true) return;
721
732
  yield {
@@ -724,8 +735,6 @@ function anthropic(options = {}) {
724
735
  };
725
736
  return;
726
737
  }
727
- for (const event of pending) yield event;
728
- pending.length = 0;
729
738
  if (!mapping.pauseTurn) return;
730
739
  carryRetained.push(...mapping.assistantContent.filter((block) => block.type === "thinking" || block.type === "redacted_thinking"));
731
740
  continuations += 1;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rulvar/anthropic",
3
- "version": "1.5.2",
4
- "description": "rulvar first-class provider adapter over @anthropic-ai/sdk.",
3
+ "version": "1.6.0",
4
+ "description": "Rulvar first-class provider adapter over @anthropic-ai/sdk.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "engines": {
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@anthropic-ai/sdk": "^0.110.0",
26
- "@rulvar/core": "1.5.2"
26
+ "@rulvar/core": "1.6.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.20.0",