bazis 0.138.0 → 0.139.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.
@@ -9,6 +9,7 @@ import {
9
9
  agentData,
10
10
  agentMessage,
11
11
  agentModelResponse,
12
+ agentReasoning,
12
13
  agentText,
13
14
  agentToolCall,
14
15
  agentToolCallPart,
@@ -340,13 +341,21 @@ function parseResponse(value: BoundaryJsonValue): AgentExecutionResponse {
340
341
  }
341
342
 
342
343
  function parseUsage(value: BoundaryJsonValue): NonNullable<AgentModelResponse["usage"]> {
343
- const record = closed(value, ["inputTokens", "outputTokens", "totalTokens", "latencyMs", "cost", "cacheHit"]);
344
+ // Checkpoints written before the cache and reasoning counts have six fields.
345
+ const extended = Object.prototype.hasOwnProperty.call(object(value), "cacheReadTokens");
346
+ const record: { readonly [Key in "inputTokens" | "outputTokens" | "totalTokens" | "latencyMs" | "cost" | "cacheHit"]: BoundaryJsonValue } & { readonly [Key in "cacheReadTokens" | "cacheWriteTokens" | "reasoningTokens"]?: BoundaryJsonValue } = extended
347
+ ? closed(value, ["inputTokens", "outputTokens", "totalTokens", "cacheReadTokens", "cacheWriteTokens", "reasoningTokens", "latencyMs", "cost", "cacheHit"])
348
+ : closed(value, ["inputTokens", "outputTokens", "totalTokens", "latencyMs", "cost", "cacheHit"]);
344
349
  if (record.cacheHit !== null && typeof record.cacheHit !== "boolean") throw invalid();
350
+ const optionalCount = (count: BoundaryJsonValue | undefined): number | undefined => count === undefined || count === null ? undefined : nonNegativeInteger(count);
345
351
  const cost = record.cost === null ? undefined : parseCost(record.cost);
346
352
  return freezeWithoutUndefined({
347
353
  inputTokens: record.inputTokens === null ? undefined : nonNegativeInteger(record.inputTokens),
348
354
  outputTokens: record.outputTokens === null ? undefined : nonNegativeInteger(record.outputTokens),
349
355
  totalTokens: record.totalTokens === null ? undefined : nonNegativeInteger(record.totalTokens),
356
+ cacheReadTokens: optionalCount(record.cacheReadTokens),
357
+ cacheWriteTokens: optionalCount(record.cacheWriteTokens),
358
+ reasoningTokens: optionalCount(record.reasoningTokens),
350
359
  latencyMs: record.latencyMs === null ? undefined : nonNegativeInteger(record.latencyMs),
351
360
  cost,
352
361
  cacheHit: record.cacheHit === null ? undefined : record.cacheHit,
@@ -385,6 +394,11 @@ function parseContentPart(value: BoundaryJsonValue): AgentContentPart {
385
394
  const call = closed(record, ["kind", "call"]);
386
395
  return agentToolCallPart(parseToolCall(call.call));
387
396
  }
397
+ if (record.kind === "reasoning") {
398
+ const reasoning = closed(record, ["kind", "text", "data"]);
399
+ if (typeof reasoning.text !== "string") throw invalid();
400
+ return agentReasoning(reasoning.text, { data: reasoning.data === null ? undefined : asJsonObject(reasoning.data) });
401
+ }
388
402
  if (record.kind === "tool-result") {
389
403
  const result = closed(record, ["kind", "result"]);
390
404
  return agentToolResultPart(parseToolResult(result.result));
@@ -393,11 +407,16 @@ function parseContentPart(value: BoundaryJsonValue): AgentContentPart {
393
407
  }
394
408
 
395
409
  function parseToolCall(value: BoundaryJsonValue): AgentToolCall {
396
- const record = closed(value, ["id", "name", "input", "metadata"]);
410
+ // `inputError` is written only for a call whose arguments could not be read.
411
+ const withError = Object.prototype.hasOwnProperty.call(object(value), "inputError");
412
+ const record: { readonly id: BoundaryJsonValue; readonly name: BoundaryJsonValue; readonly input: BoundaryJsonValue; readonly metadata: BoundaryJsonValue; readonly inputError?: BoundaryJsonValue } = withError
413
+ ? closed(value, ["id", "name", "input", "inputError", "metadata"])
414
+ : closed(value, ["id", "name", "input", "metadata"]);
397
415
  return agentToolCall({
398
416
  id: requiredString(record.id),
399
417
  name: requiredString(record.name),
400
418
  input: asJsonValue(record.input),
419
+ ...(record.inputError === undefined ? {} : { inputError: requiredString(record.inputError) }),
401
420
  metadata: asJsonObject(record.metadata),
402
421
  });
403
422
  }
@@ -631,6 +650,9 @@ function responseRecord(response: AgentExecutionResponse): Record<string, unknow
631
650
  inputTokens: response.usage.inputTokens ?? null,
632
651
  outputTokens: response.usage.outputTokens ?? null,
633
652
  totalTokens: response.usage.totalTokens ?? null,
653
+ cacheReadTokens: response.usage.cacheReadTokens ?? null,
654
+ cacheWriteTokens: response.usage.cacheWriteTokens ?? null,
655
+ reasoningTokens: response.usage.reasoningTokens ?? null,
634
656
  latencyMs: response.usage.latencyMs ?? null,
635
657
  cost: response.usage.cost ?? null,
636
658
  cacheHit: response.usage.cacheHit ?? null,
@@ -655,12 +677,13 @@ function contentPartRecord(part: AgentContentPart): Record<string, unknown> {
655
677
  if (part.kind === "text") return { kind: "text", text: part.text };
656
678
  if (part.kind === "data") return { kind: "data", value: part.value, name: part.name ?? null };
657
679
  if (part.kind === "tool-call") return { kind: "tool-call", call: toolCallRecord(part.call) };
680
+ if (part.kind === "reasoning") return { kind: "reasoning", text: part.text, data: part.data ?? null };
658
681
  if (part.kind === "tool-result") return { kind: "tool-result", result: toolResultRecord(part.result) };
659
682
  throw invalid();
660
683
  }
661
684
 
662
685
  function toolCallRecord(call: AgentToolCall): Record<string, unknown> {
663
- return { id: call.id, name: call.name, input: call.input, metadata: call.metadata };
686
+ return { id: call.id, name: call.name, input: call.input, ...(call.inputError === undefined ? {} : { inputError: call.inputError }), metadata: call.metadata };
664
687
  }
665
688
 
666
689
  function toolResultRecord(result: AgentToolResult): Record<string, unknown> {
@@ -15,6 +15,7 @@ export type AgentContentPart =
15
15
  | AgentDataPart
16
16
  | AgentImagePart
17
17
  | AgentFilePart
18
+ | AgentReasoningPart
18
19
  | AgentToolCallPart
19
20
  | AgentToolResultPart;
20
21
  export type AgentContentInput = string | AgentContentPart | readonly AgentContentPart[];
@@ -44,6 +45,19 @@ export interface AgentFilePart {
44
45
  readonly name?: string;
45
46
  }
46
47
 
48
+ /**
49
+ * What a model thought before it answered or called a Tool. It stands in the
50
+ * assistant message it belongs to and goes back to the same provider with the
51
+ * dialog, unchanged: a provider may refuse reasoning it did not write.
52
+ */
53
+ export interface AgentReasoningPart {
54
+ readonly kind: "reasoning";
55
+ /** What the provider lets an application see of the reasoning; may be empty. */
56
+ readonly text: string;
57
+ /** The provider's own record of the block (a signature, encrypted reasoning); opaque to everything else. */
58
+ readonly data?: JsonObject;
59
+ }
60
+
47
61
  export interface AgentToolResultPart {
48
62
  readonly kind: "tool-result";
49
63
  readonly result: AgentToolResult;
@@ -77,6 +91,12 @@ export interface AgentToolCall {
77
91
  readonly id: string;
78
92
  readonly name: string;
79
93
  readonly input: JsonValue;
94
+ /**
95
+ * Why the arguments the model wrote could not be read (broken or cut-off
96
+ * JSON). Such a call is never executed: the model gets this text as the
97
+ * Tool error `TOOL_ARGUMENTS_INVALID` and can write the call again.
98
+ */
99
+ readonly inputError?: string;
80
100
  readonly metadata: JsonObject;
81
101
  }
82
102
 
@@ -84,6 +104,8 @@ export interface AgentToolCallOptions {
84
104
  readonly id: string;
85
105
  readonly name: string;
86
106
  readonly input?: unknown;
107
+ /** Set by a model provider instead of throwing when the arguments are not valid JSON. */
108
+ readonly inputError?: string;
87
109
  readonly metadata?: unknown;
88
110
  }
89
111
 
@@ -247,9 +269,16 @@ export interface AgentModelRequestOptions {
247
269
  export type AgentFinishReason = "stop" | "tool-calls" | "length" | "content-filter" | "error";
248
270
 
249
271
  export interface AgentUsage {
272
+ /** The whole request as the provider counted it, tokens read from its prompt cache included. */
250
273
  readonly inputTokens?: number;
251
274
  readonly outputTokens?: number;
252
275
  readonly totalTokens?: number;
276
+ /** The part of `inputTokens` the provider read from its prompt cache, at a lower price. */
277
+ readonly cacheReadTokens?: number;
278
+ /** The part of `inputTokens` the provider wrote to its prompt cache. */
279
+ readonly cacheWriteTokens?: number;
280
+ /** The part of `outputTokens` the model spent on reasoning. */
281
+ readonly reasoningTokens?: number;
253
282
  readonly latencyMs?: number;
254
283
  readonly cost?: AgentUsageCost;
255
284
  readonly cacheHit?: boolean;
@@ -264,6 +293,9 @@ export interface AgentUsageInput {
264
293
  readonly inputTokens?: number;
265
294
  readonly outputTokens?: number;
266
295
  readonly totalTokens?: number;
296
+ readonly cacheReadTokens?: number;
297
+ readonly cacheWriteTokens?: number;
298
+ readonly reasoningTokens?: number;
267
299
  readonly latencyMs?: number;
268
300
  readonly cost?: {
269
301
  readonly amount: number;
@@ -409,6 +441,15 @@ export function agentFile(uri: string, options: { readonly mediaType?: string; r
409
441
  });
410
442
  }
411
443
 
444
+ export function agentReasoning(text: string, options: { readonly data?: unknown } = {}): AgentReasoningPart {
445
+ if (typeof text !== "string") throw new AgentSemanticError("reasoning.text must be a string.");
446
+ return freezeWithoutUndefined({
447
+ kind: "reasoning" as const,
448
+ text,
449
+ data: options.data === undefined ? undefined : normalizeJsonObject(options.data, "reasoning.data"),
450
+ });
451
+ }
452
+
412
453
  export function agentToolResultPart(result: AgentToolResult): AgentToolResultPart {
413
454
  return Object.freeze({ kind: "tool-result", result: normalizeToolResult(result) });
414
455
  }
@@ -431,6 +472,9 @@ export function agentMessage(role: AgentMessageRole, content: AgentContentInput,
431
472
  if (role !== "assistant" && parts.some((part) => part.kind === "tool-call")) {
432
473
  throw new AgentSemanticError("tool-call content parts require an assistant message.");
433
474
  }
475
+ if (role !== "assistant" && parts.some((part) => part.kind === "reasoning")) {
476
+ throw new AgentSemanticError("reasoning content parts require an assistant message.");
477
+ }
434
478
  const resultParts = parts.filter((part) => part.kind === "tool-result");
435
479
  if (role === "tool") {
436
480
  if (parts.length !== 1 || resultParts.length !== 1) {
@@ -454,11 +498,21 @@ export function agentMessage(role: AgentMessageRole, content: AgentContentInput,
454
498
  });
455
499
  }
456
500
 
501
+ /**
502
+ * The words of a message: its text parts joined, without reasoning, data and
503
+ * Tool calls. An empty string for a message that has no text or is absent.
504
+ */
505
+ export function agentMessageText(message: AgentMessage | undefined): string {
506
+ if (message === undefined) return "";
507
+ return message.content.map((part) => part.kind === "text" ? part.text : "").join("");
508
+ }
509
+
457
510
  export function agentToolCall(options: AgentToolCallOptions): AgentToolCall {
458
- return Object.freeze({
511
+ return freezeWithoutUndefined({
459
512
  id: requiredName(options.id, "toolCall.id"),
460
513
  name: requiredName(options.name, "toolCall.name"),
461
514
  input: normalizeJsonValue(options.input ?? {}, "toolCall.input"),
515
+ inputError: optionalContentText(options.inputError, "toolCall.inputError"),
462
516
  metadata: normalizeJsonObject(options.metadata ?? {}, "toolCall.metadata"),
463
517
  });
464
518
  }
@@ -661,6 +715,8 @@ function normalizeContentPart(part: AgentContentPart, field: string): AgentConte
661
715
  return agentImage(part.uri, { mediaType: part.mediaType, detail: part.detail });
662
716
  case "file":
663
717
  return agentFile(part.uri, { mediaType: part.mediaType, name: part.name });
718
+ case "reasoning":
719
+ return agentReasoning(part.text, { data: part.data });
664
720
  case "tool-result":
665
721
  return agentToolResultPart(part.result);
666
722
  case "tool-call":
@@ -715,6 +771,9 @@ function normalizeUsage(usage: AgentUsageInput): AgentUsage {
715
771
  inputTokens: optionalNonNegativeInteger(usage.inputTokens, "usage.inputTokens"),
716
772
  outputTokens: optionalNonNegativeInteger(usage.outputTokens, "usage.outputTokens"),
717
773
  totalTokens: optionalNonNegativeInteger(usage.totalTokens, "usage.totalTokens"),
774
+ cacheReadTokens: optionalNonNegativeInteger(usage.cacheReadTokens, "usage.cacheReadTokens"),
775
+ cacheWriteTokens: optionalNonNegativeInteger(usage.cacheWriteTokens, "usage.cacheWriteTokens"),
776
+ reasoningTokens: optionalNonNegativeInteger(usage.reasoningTokens, "usage.reasoningTokens"),
718
777
  latencyMs: optionalNonNegativeInteger(usage.latencyMs, "usage.latencyMs"),
719
778
  cost: usage.cost
720
779
  ? Object.freeze({
@@ -161,7 +161,7 @@ flowchart TB
161
161
 
162
162
  ```ts
163
163
  import { Cacheable, OutputCache, memory } from "@/core/cache";
164
- import { runApp } from "@/core/app";
164
+ import { App } from "@/core/app";
165
165
 
166
166
  @App({
167
167
  modules: [AppModule],
@@ -196,7 +196,7 @@ class UserService implements IUserStore {
196
196
 
197
197
  // UsersModule:
198
198
  providers: [
199
- scoped(IUserStore, UserService, [repositoryFor(User)]),
199
+ scoped(IUserStore, UserService), // codegen reads the constructor
200
200
  ],
201
201
  ```
202
202
 
@@ -207,7 +207,7 @@ The backend is connected from `@/core/infra/*`; the framework stays backend-agno
207
207
  ```ts
208
208
  import { memory } from "@/core/cache";
209
209
  import { Infra, redisConnect } from "@/core/infra";
210
- import { runApp } from "@/core/app";
210
+ import { App } from "@/core/app";
211
211
  import { redisConfig } from "@/app/config/redis.config";
212
212
  import { AppModule } from "@/app/modules/App.module";
213
213
 
package/core/di/README.md CHANGED
@@ -5,7 +5,10 @@ contracts are described in [MODULE.md](MODULE.md).
5
5
 
6
6
  ## Public API
7
7
 
8
- - `index.ts`: the single public DI entry point.
8
+ - `index.ts`: the public DI entry point for applications (`bazis/core/di`): only the agreed
9
+ provider forms, tokens, `@Module` and the container.
10
+ - `lowLevel.ts`: `bazis/core/di/lowLevel` (0.138.0): the low-level provider forms (`DI`,
11
+ factories, dependency lists), for the framework's own modules and packages that extend it.
9
12
  - `MODULE.md`: the contracts, boundaries and checks of the DI core.
10
13
 
11
14
  ## Container core
@@ -33,7 +36,7 @@ contracts are described in [MODULE.md](MODULE.md).
33
36
 
34
37
  ## Module layer
35
38
 
36
- - `module/DI.ts`: low-level provider constructors.
39
+ - `module/DI.ts`: low-level provider constructors, exported only through `lowLevel.ts`.
37
40
  - `module/shortcuts.ts`: the short API (`singleton/scoped/transient`).
38
41
  - `module/createContainer.ts`: builds a container from modules.
39
42
  - `module/encapsulation.ts`: build-time check of module isolation (`exports`).
@@ -81,7 +84,11 @@ contracts are described in [MODULE.md](MODULE.md).
81
84
  (or the class alone), `singletonValue(TOKEN, value)`, `singletonAsyncFactory(TOKEN, [deps], async factory)`
82
85
  and `...withEventSubscribers(Class)`. Codegen refuses anything else (`BAZIS_DI_PROVIDER_FORM`).
83
86
  - The low-level path (`DI.classProvider(...)`, `DI.factoryProvider(...)`) stays for the kernel's own
84
- plumbing and test `overrides`, never in a module's `providers`.
87
+ plumbing and packages that extend the framework, never in a module's `providers`. Since 0.138.0
88
+ it is imported from `bazis/core/di/lowLevel`; `bazis/core/di` no longer exports `DI`, the
89
+ dependency-list argument of `scoped`/`singleton`/`transient`, `singletonFactory`,
90
+ `singletonFactoryWithResolver` or `singletonAsyncFactoryWithResolver`. Test `overrides` use
91
+ the agreed forms: `singletonValue(IStore, fake)`, `scoped(IStore, FakeStore)`.
85
92
 
86
93
  ## Contributing Checklist
87
94
 
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The Claude adapter lives in its own entry: it imports `@anthropic-ai/sdk`,
3
+ * which only an application that uses Claude installs.
4
+ */
5
+ export {
6
+ AnthropicModelProvider,
7
+ AnthropicProviderError,
8
+ anthropicAdapter,
9
+ type AnthropicAdapterOptions,
10
+ type AnthropicClient,
11
+ } from "./connectors/anthropic";