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.
package/README.md CHANGED
@@ -71,8 +71,11 @@ npm page shows the source commit and the workflow that built it. The release
71
71
  tarball is also attached to each
72
72
  [GitHub Release](https://github.com/asus97178/bazis/releases).
73
73
 
74
- `bazis new` creates a project with `src/index.ts`, a root `AppModule`,
75
- `bazis.config.json`, scripts, a `/health` test and an `.env.example`.
74
+ `bazis new` creates a project with `src/app/App.ts` (the application declared by
75
+ `@App`), `src/index.ts` (`runApp(Application)`, no options), a root `AppModule`,
76
+ `bazis.config.json`, scripts, a `/health` test that starts the declared application
77
+ in the test process and an `.env.example` with `BAZIS_HTTP__HOST` and
78
+ `BAZIS_HTTP__PORT`.
76
79
 
77
80
  ## CLI
78
81
 
@@ -141,20 +144,25 @@ import { dbConfig } from "../config/db.config"; // defineConfig("db", { default:
141
144
  @Infra({ db: ormBazisConnect(dbConfig) })
142
145
  export class AppInfra {}
143
146
 
144
- // src/index.ts
147
+ // src/app/App.ts
145
148
  @App({ modules: [AppModule], infra: AppInfra, http: { health: true } })
146
149
  export class Application {}
150
+
151
+ // src/index.ts
152
+ await registerBazisGeneratedRuntime();
153
+ await runApp(Application);
147
154
  ```
148
155
 
149
156
  A feature module attaches its own `DbContext` and entities with
150
- `ormBazis: { context, entities }`; its services take that context in their
157
+ `ormBazis: { context }` (the entities of its sets are mapped by themselves); its services take that context in their
151
158
  constructors. `bunx bazis g module Task --minimal` generates such a module.
152
159
  Configuration values can be overridden from the environment
153
160
  (`BAZIS_DB__HOST`, `BAZIS_DB__PASSWORD`, ...). Invalid configuration or a
154
161
  missing production secret stops startup with an error that names the key.
155
162
 
156
163
  A unique index violation in `saveChanges()` rejects with
157
- `UniqueViolationError` (with `constraint` and `table`); catch it to answer 409.
164
+ `UniqueViolationError` (with `constraint`, `table` and `properties`); uncaught, it
165
+ answers 409 by itself, as other database errors answer 404, 422 or 503.
158
166
  Request-body fields declared as `string`, `number` or `boolean` are checked
159
167
  against the JSON type automatically (400 on mismatch); other rules come from
160
168
  `@Validator`.
@@ -2,6 +2,8 @@ import type { AgentDefinition } from "./AgentRegistry";
2
2
  import { AgentRuntimeError } from "./errors";
3
3
  import {
4
4
  agentMessage,
5
+ agentToolResult,
6
+ agentToolResultPart,
5
7
  type AgentContentPart,
6
8
  type AgentMessage,
7
9
  type AgentMessageRole,
@@ -9,10 +11,20 @@ import {
9
11
  type JsonValue,
10
12
  } from "./semantic";
11
13
 
14
+ /** A Tool result longer than this reaches the model cut, unless `maxToolResultChars` says otherwise. */
15
+ export const DEFAULT_MAX_TOOL_RESULT_CHARS = 100_000;
16
+
12
17
  export interface AgentContextLimits {
13
18
  readonly maxMessages?: number;
14
19
  readonly maxChars?: number;
15
20
  readonly maxTokens?: number;
21
+ /**
22
+ * The longest Tool output, as JSON text, the model gets in full. A longer
23
+ * one is sent as `{ truncated: true, totalChars, text }` with its beginning,
24
+ * so one large result does not fill the model's window; `result.toolResults`
25
+ * and `result.messages` keep the whole output. 100 000 by default.
26
+ */
27
+ readonly maxToolResultChars?: number;
16
28
  }
17
29
 
18
30
  export interface AgentContextBuildInput {
@@ -21,6 +33,12 @@ export interface AgentContextBuildInput {
21
33
  readonly messages: readonly AgentMessage[];
22
34
  readonly metadata: JsonObject;
23
35
  readonly limits?: AgentContextLimits;
36
+ /**
37
+ * How many real tokens one estimated token turned out to be for this model:
38
+ * the runtime measures it from the usage the provider reports. Estimates are
39
+ * multiplied by it before `maxTokens` is applied. 1 when nothing is known yet.
40
+ */
41
+ readonly tokenScale?: number;
24
42
  }
25
43
 
26
44
  export interface AgentContextTraceEntry {
@@ -55,6 +73,8 @@ interface ContextCandidate {
55
73
  readonly message: AgentMessage;
56
74
  readonly chars: number;
57
75
  readonly tokens: number;
76
+ /** The Tool result was too long and is sent cut. */
77
+ readonly cut?: true;
58
78
  }
59
79
 
60
80
  function errorMessageOf(error: unknown): string {
@@ -83,7 +103,11 @@ export function normalizeAgentContextLimits(
83
103
  return undefined;
84
104
  }
85
105
 
86
- const normalized: { maxMessages?: number; maxChars?: number; maxTokens?: number } = {};
106
+ const normalized: { maxMessages?: number; maxChars?: number; maxTokens?: number; maxToolResultChars?: number } = {};
107
+ const maxToolResultChars = optionalPositiveInteger(limits.maxToolResultChars, `${field}.maxToolResultChars`);
108
+ if (maxToolResultChars !== undefined) {
109
+ normalized.maxToolResultChars = maxToolResultChars;
110
+ }
87
111
  const maxMessages = optionalPositiveInteger(limits.maxMessages, `${field}.maxMessages`);
88
112
  const maxChars = optionalPositiveInteger(limits.maxChars, `${field}.maxChars`);
89
113
  const maxTokens = optionalPositiveInteger(limits.maxTokens, `${field}.maxTokens`);
@@ -129,10 +153,48 @@ function cloneMessage(message: AgentMessage, field: string): AgentMessage {
129
153
  }
130
154
  }
131
155
 
156
+ /** The message the model gets instead of a Tool result that is too long; `undefined` when it fits. */
157
+ function cutToolResult(message: AgentMessage, maxChars: number): AgentMessage | undefined {
158
+ const part = message.content[0];
159
+ if (message.role !== "tool" || part?.kind !== "tool-result" || part.result.status !== "success") return undefined;
160
+ const json = JSON.stringify(part.result.output) ?? "";
161
+ if (json.length <= maxChars) return undefined;
162
+ const result = agentToolResult({
163
+ callId: part.result.callId,
164
+ name: part.result.name,
165
+ status: "success",
166
+ output: { truncated: true, totalChars: json.length, text: json.slice(0, maxChars) },
167
+ durationMs: part.result.durationMs,
168
+ metadata: part.result.metadata,
169
+ });
170
+ return agentMessage("tool", agentToolResultPart(result), {
171
+ id: message.id, name: message.name, toolCallId: message.toolCallId, createdAtUnixMs: message.createdAtUnixMs, metadata: message.metadata,
172
+ });
173
+ }
174
+
175
+ function withoutReasoning(message: AgentMessage): AgentMessage {
176
+ if (!message.content.some((part) => part.kind === "reasoning")) return message;
177
+ const content = message.content.filter((part) => part.kind !== "reasoning");
178
+ if (content.length === 0) return message;
179
+ return agentMessage(message.role, content, { id: message.id, name: message.name, toolCallId: message.toolCallId, createdAtUnixMs: message.createdAtUnixMs, metadata: message.metadata });
180
+ }
181
+
132
182
  function jsonLength(value: unknown): number {
133
183
  return JSON.stringify(value)?.length ?? 0;
134
184
  }
135
185
 
186
+ /** Tokens of an image whatever its size: providers scale images down to about this much. */
187
+ const IMAGE_TOKENS = 1_200;
188
+
189
+ function isDataUri(uri: string): boolean {
190
+ return uri.startsWith("data:");
191
+ }
192
+
193
+ /** A file in the request costs what its content does; an address alone says nothing about it. */
194
+ function fileTokens(uri: string): number {
195
+ return isDataUri(uri) ? Math.min(200_000, Math.max(500, Math.ceil(uri.length / 12))) : 2_000;
196
+ }
197
+
136
198
  function contentPartChars(part: AgentContentPart): number {
137
199
  switch (part.kind) {
138
200
  case "text":
@@ -140,9 +202,12 @@ function contentPartChars(part: AgentContentPart): number {
140
202
  case "data":
141
203
  return jsonLength(part.value) + (part.name?.length ?? 0);
142
204
  case "image":
143
- return part.uri.length + (part.mediaType?.length ?? 0) + part.detail.length;
205
+ // Bytes written into the dialog are not characters of a prompt.
206
+ return (isDataUri(part.uri) ? IMAGE_TOKENS * 4 : part.uri.length) + (part.mediaType?.length ?? 0) + part.detail.length;
144
207
  case "file":
145
- return part.uri.length + (part.mediaType?.length ?? 0) + (part.name?.length ?? 0);
208
+ return (isDataUri(part.uri) ? fileTokens(part.uri) * 4 : part.uri.length) + (part.mediaType?.length ?? 0) + (part.name?.length ?? 0);
209
+ case "reasoning":
210
+ return part.text.length + (part.data === undefined ? 0 : jsonLength(part.data));
146
211
  case "tool-result":
147
212
  return jsonLength(part.result);
148
213
  case "tool-call":
@@ -150,9 +215,35 @@ function contentPartChars(part: AgentContentPart): number {
150
215
  }
151
216
  }
152
217
 
218
+ /**
219
+ * An estimate close to what tokenizers produce, without a tokenizer: Latin
220
+ * letters and digits are about four characters a token, letters of other
221
+ * alphabets about two, a CJK character is a token, punctuation is about two
222
+ * marks a token. The runtime corrects the rest by the usage a provider reports.
223
+ */
153
224
  function textTokenCount(text: string): number {
154
- const matches = text.match(/[\p{L}\p{N}_]+|[^\s]/gu);
155
- return matches?.length ?? 0;
225
+ let latin = 0;
226
+ let letters = 0;
227
+ let marks = 0;
228
+ let whole = 0;
229
+ for (let index = 0; index < text.length; index += 1) {
230
+ const code = text.charCodeAt(index);
231
+ if (code <= 0x20) continue;
232
+ if (code < 0x80) {
233
+ if ((code >= 0x30 && code <= 0x39) || (code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a) || code === 0x5f) latin += 1;
234
+ else marks += 1;
235
+ } else if (code === 0xa0 || (code >= 0x2000 && code <= 0x200b) || code === 0x3000) {
236
+ continue;
237
+ } else if ((code >= 0x2e80 && code <= 0x9fff) || (code >= 0xac00 && code <= 0xd7af) || (code >= 0xf900 && code <= 0xfaff)) {
238
+ whole += 1;
239
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
240
+ // The second half of a surrogate pair: its first half was counted.
241
+ continue;
242
+ } else {
243
+ letters += 1;
244
+ }
245
+ }
246
+ return Math.ceil(latin / 4 + letters / 2 + marks / 2 + whole);
156
247
  }
157
248
 
158
249
  function contentPartTokens(part: AgentContentPart): number {
@@ -162,9 +253,12 @@ function contentPartTokens(part: AgentContentPart): number {
162
253
  case "data":
163
254
  return textTokenCount(JSON.stringify(part.value) ?? "") + (part.name === undefined ? 0 : textTokenCount(part.name));
164
255
  case "image":
165
- return textTokenCount(JSON.stringify({ uri: part.uri, mediaType: part.mediaType ?? null, detail: part.detail }));
256
+ return IMAGE_TOKENS;
166
257
  case "file":
167
- return textTokenCount(JSON.stringify({ uri: part.uri, mediaType: part.mediaType ?? null, name: part.name ?? null }));
258
+ return fileTokens(part.uri);
259
+ case "reasoning":
260
+ // The provider's record of a block is about as long as the reasoning it holds.
261
+ return Math.max(textTokenCount(part.text), part.data === undefined ? 0 : Math.ceil(jsonLength(part.data) / 4));
168
262
  case "tool-result":
169
263
  return textTokenCount(JSON.stringify(part.result) ?? "");
170
264
  case "tool-call":
@@ -387,13 +481,15 @@ export class DefaultAgentContextBuilder implements AgentContextBuilder {
387
481
 
388
482
  build(input: AgentContextBuildInput): AgentContext {
389
483
  const limits = normalizeAgentContextLimits(input.limits);
390
- const candidates = this.candidates(input.agent, input.messages);
484
+ const tokenScale = input.tokenScale !== undefined && Number.isFinite(input.tokenScale) && input.tokenScale > 0 ? input.tokenScale : 1;
485
+ const candidates = this.candidates(input.agent, input.messages, limits?.maxToolResultChars ?? DEFAULT_MAX_TOOL_RESULT_CHARS, tokenScale);
391
486
  if (candidates.length === 0) {
392
487
  throw new AgentRuntimeError("agent context requires at least one message.");
393
488
  }
394
489
 
395
490
  const included = candidates.map(() => true);
396
- const reasons = candidates.map(() => undefined as string | undefined);
491
+ // A result sent cut stays in the context; the trace says why it is shorter.
492
+ const reasons = candidates.map((candidate) => candidate.cut ? "maxToolResultChars" : undefined as string | undefined);
397
493
  const protectedItems = protectedIndexes(candidates);
398
494
  const selection = new ContextSelection(candidates, included, reasons, protectedItems);
399
495
  selection.apply("maxMessages", limits?.maxMessages);
@@ -402,12 +498,15 @@ export class DefaultAgentContextBuilder implements AgentContextBuilder {
402
498
 
403
499
  const messages: AgentMessage[] = [];
404
500
  const trace: AgentContextTraceEntry[] = [];
501
+ // A provider ties reasoning to the dialog it was written in: once older
502
+ // messages are dropped, the reasoning in the rest no longer matches it.
503
+ const trimmed = included.includes(false);
405
504
  for (let index = 0; index < candidates.length; index += 1) {
406
505
  const candidate = candidates[index] as ContextCandidate;
407
506
  const isIncluded = included[index] === true;
408
507
  trace.push(traceEntry(candidate, isIncluded, reasons[index]));
409
508
  if (isIncluded) {
410
- messages.push(candidate.message);
509
+ messages.push(trimmed ? withoutReasoning(candidate.message) : candidate.message);
411
510
  }
412
511
  }
413
512
 
@@ -421,16 +520,17 @@ export class DefaultAgentContextBuilder implements AgentContextBuilder {
421
520
  messageCount: messages.length,
422
521
  charCount,
423
522
  tokenCount,
523
+ ...(tokenScale === 1 ? {} : { tokenScale }),
424
524
  trace: traceToJson(trace),
425
525
  }),
426
526
  });
427
527
  }
428
528
 
429
- private candidates(agent: AgentDefinition, messages: readonly AgentMessage[]): readonly ContextCandidate[] {
529
+ private candidates(agent: AgentDefinition, messages: readonly AgentMessage[], maxToolResultChars: number, tokenScale: number): readonly ContextCandidate[] {
430
530
  const candidates: ContextCandidate[] = [];
431
531
  const prompt = this.promptMessage(agent);
432
532
  if (prompt !== undefined) {
433
- const tokens = this.estimatedTokens(prompt, "prompt");
533
+ const tokens = this.estimatedTokens(prompt, "prompt", tokenScale);
434
534
  candidates.push(Object.freeze({
435
535
  section: "prompt",
436
536
  message: prompt,
@@ -440,19 +540,22 @@ export class DefaultAgentContextBuilder implements AgentContextBuilder {
440
540
  }
441
541
 
442
542
  for (let index = 0; index < messages.length; index += 1) {
443
- const message = cloneMessage(messages[index] as AgentMessage, `agentContext.messages[${index}]`);
444
- const tokens = this.estimatedTokens(message, `conversation[${index}]`);
543
+ const whole = cloneMessage(messages[index] as AgentMessage, `agentContext.messages[${index}]`);
544
+ const cut = cutToolResult(whole, maxToolResultChars);
545
+ const message = cut ?? whole;
546
+ const tokens = this.estimatedTokens(message, `conversation[${index}]`, tokenScale);
445
547
  candidates.push(Object.freeze({
446
548
  section: `conversation[${index}]`,
447
549
  message,
448
550
  chars: messageChars(message),
449
551
  tokens,
552
+ ...(cut === undefined ? {} : { cut: true as const }),
450
553
  }));
451
554
  }
452
555
  return Object.freeze(candidates);
453
556
  }
454
557
 
455
- private estimatedTokens(message: AgentMessage, field: string): number {
558
+ private estimatedTokens(message: AgentMessage, field: string, tokenScale: number): number {
456
559
  let estimate: unknown;
457
560
  try { estimate = this.tokenEstimator.estimateMessage(message); } catch (error) {
458
561
  throw new AgentRuntimeError(`agentContext token estimator failed for ${field}: ${errorMessageOf(error)}`);
@@ -460,7 +563,7 @@ export class DefaultAgentContextBuilder implements AgentContextBuilder {
460
563
  if (!Number.isFinite(estimate) || typeof estimate !== "number" || estimate < 0) {
461
564
  throw new AgentRuntimeError(`agentContext token estimator returned an invalid estimate for ${field}.`);
462
565
  }
463
- return estimate;
566
+ return tokenScale === 1 ? estimate : Math.ceil(estimate * tokenScale);
464
567
  }
465
568
 
466
569
  /** The definition's instructions are the system prompt, sent as written. */
@@ -14,6 +14,8 @@ import {
14
14
  import type { AgentModelProvider } from "./ModelProvider";
15
15
 
16
16
  export {
17
+ type AgentCompactionOptions,
18
+ type AgentModelRetryPolicy,
17
19
  type AgentRuntimeInvokeOptions,
18
20
  type AgentExecutionDriverOptions,
19
21
  type AgentRuntimeOptions,
@@ -58,7 +60,13 @@ export class AgentRuntime {
58
60
  this.options = {
59
61
  ...options,
60
62
  toolExecutorOptions: { schemaValidator: modelValidatorAdapter, ...toolExecutorOptions },
63
+ // What a model's tokens turned out to cost is learned once for every run.
64
+ tokenScales: new Map<string, number>(),
61
65
  };
66
+ const summaryProfile = options.compaction === undefined || options.compaction === false ? undefined : options.compaction.modelProfile;
67
+ if (summaryProfile !== undefined && typeof modelProvider.modelProfiles === "function" && !modelProvider.modelProfiles().includes(summaryProfile)) {
68
+ throw new AgentSetupError(`AgentRuntime.compaction: model profile "${summaryProfile}" is not configured (configured: ${[...modelProvider.modelProfiles()].sort().join(", ")}).`);
69
+ }
62
70
  // Invalid options and hook setup fail here, at the first resolve, not on the first run.
63
71
  new AgentExecutionDriver(services, this.catalog, modelProvider, this.options);
64
72
  }
@@ -903,6 +903,13 @@ export class AgentToolExecutor {
903
903
  return this.finish(call, result, startedAt, metadata, options, audit, tool, options.agentName, undefined, executionState);
904
904
  }
905
905
 
906
+ // The provider could not read the arguments the model wrote: the Tool is
907
+ // not run, and the model gets the reason so it can write the call again.
908
+ if (call.inputError !== undefined) {
909
+ const result = this.errorResult(call, "TOOL_ARGUMENTS_INVALID", `Tool "${tool.metadata.name}" arguments are not valid JSON: ${call.inputError}`, metadata, startedAt, { phase: "pre-execute" });
910
+ return this.finish(call, result, startedAt, metadata, options, audit, tool, visibility.agentName, undefined, executionState);
911
+ }
912
+
906
913
  const schema = this.schemaSettings(options);
907
914
  const schemaGate = this.ensureSchemaValidator(call, tool, schema, metadata, startedAt);
908
915
  if (schemaGate) {
@@ -22,6 +22,12 @@ export interface AgentModelProvider {
22
22
  * the model is called. Without it any profile is passed through.
23
23
  */
24
24
  modelProfiles?(): readonly string[];
25
+ /**
26
+ * The context window of the model behind a profile, in tokens, when infra
27
+ * knows it (`contextTokens` of the LLM connector). The runtime starts
28
+ * compacting a long dialog before it reaches the window.
29
+ */
30
+ contextTokens?(modelProfile?: string): number | undefined;
25
31
  }
26
32
 
27
33
  export const AGENT_MODEL_PROVIDER = createToken<AgentModelProvider>("AgentModelProvider");
@@ -86,11 +86,11 @@ are shared by agents and `ToolRunner`:
86
86
  ```ts
87
87
  @Module({
88
88
  providers: [
89
- DI.singleton(DI.valueProvider(AGENT_RUNTIME_OPTIONS, { maxSteps: 6 })),
90
- DI.singleton(DI.valueProvider(TOOL_EXECUTION_OPTIONS, {
89
+ singletonValue(AGENT_RUNTIME_OPTIONS, { maxSteps: 6 }),
90
+ singletonValue(TOOL_EXECUTION_OPTIONS, {
91
91
  approvalPolicy: (request) => request.tool.metadata.sideEffect === "read",
92
92
  auditSink: (entry) => auditStore.write(entry),
93
- })),
93
+ }),
94
94
  ],
95
95
  })
96
96
  export class AgentSettingsModule {}
@@ -144,6 +144,38 @@ await this.agents.run(support, {
144
144
  conversation message when trimming old history. Provider requests include
145
145
  `metadata.agentContext.trace` so context assembly is inspectable.
146
146
 
147
+ The runtime recovers before it fails a run. A model request the provider
148
+ cannot serve right now (`AgentModelUnavailableError`: HTTP 429, 5xx, a broken
149
+ connection) is repeated, three attempts by default (`modelRetry`). A text
150
+ answer cut by the output limit is continued (`maxOutputContinuations`, 3), a
151
+ structured answer that is not valid is sent back to the model with the issues
152
+ (`maxOutputRepairs`, 2); the finished dialog keeps one answer. Tool arguments
153
+ the provider could not read (`AgentToolCall.inputError`) give the model
154
+ `TOOL_ARGUMENTS_INVALID` instead of failing the run. Tools of one step that
155
+ change nothing (`none`, `read`) and stand next to each other run together
156
+ (`maxParallelToolCalls`, 8); `write` and `external` run one by one.
157
+ `runTimeoutMs` bounds a whole run (`AGENT_RUN_TIMEOUT`), `result.usage` sums
158
+ the tokens of every model call, and a Tool output longer than
159
+ `contextLimits.maxToolResultChars` (100 000) reaches the model cut.
160
+
161
+ A long dialog is compacted: its older part is replaced by a summary the
162
+ model writes (a user message with `metadata.runtime: "summary"`), the newest
163
+ messages stay as written and a Tool call is never separated from its results.
164
+ It starts at 80% of the model's window when the LLM connector states
165
+ `contextTokens`, at `compaction.maxTokens`, or after the provider refuses a
166
+ request as too long (`AgentModelContextOverflowError`); `result.compactions`
167
+ says it happened and `compaction: false` turns it off. Token estimates are
168
+ corrected by the usage a provider reports (`metadata.agentContext.tokenScale`).
169
+
170
+ A model's reasoning is a `reasoning` content part of its assistant message
171
+ (`agentReasoning(text, { data })`): `data` is the provider's own record of the
172
+ block and goes back to it unchanged with the dialog. The runtime drops
173
+ reasoning wherever it rewrites a dialog (compaction, trimming by
174
+ `contextLimits`, dropped drafts). `AgentUsage` also reports `cacheReadTokens`,
175
+ `cacheWriteTokens` and `reasoningTokens`; `inputTokens` is the whole request,
176
+ cached tokens included. Claude is served by `anthropicAdapter` from
177
+ `bazis/core/infra/anthropic`, on the official SDK.
178
+
147
179
  With `llmRouter`, `check` and `run` reject a `modelProfile` the router does
148
180
  not serve; a fallback to another profile is logged as `LLM model profile
149
181
  failed, using fallback`. A model provider that gives up after its own deadline
@@ -69,6 +69,38 @@ export class AgentModelTimeoutError extends Error {
69
69
  }
70
70
  }
71
71
 
72
+ /**
73
+ * A model provider could not serve the request right now: it is overloaded,
74
+ * limits the rate (HTTP 429), answered 5xx or the connection broke. The runtime
75
+ * repeats such a call (`AgentRuntimeOptions.modelRetry`); any other provider
76
+ * error fails the run at once.
77
+ */
78
+ export class AgentModelUnavailableError extends Error {
79
+ /** How long the provider asked to wait (`Retry-After`), when it said so. */
80
+ readonly retryAfterMs?: number;
81
+
82
+ constructor(message: string, options: { readonly retryAfterMs?: number } = {}) {
83
+ super(message);
84
+ this.name = "AgentModelUnavailableError";
85
+ if (options.retryAfterMs !== undefined && Number.isFinite(options.retryAfterMs) && options.retryAfterMs >= 0) {
86
+ this.retryAfterMs = Math.floor(options.retryAfterMs);
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * The request does not fit the model's context window. The runtime replaces
93
+ * the older part of the dialog with a summary and sends the request again
94
+ * (`AgentRuntimeOptions.compaction`); a run that still does not fit fails
95
+ * with AGENT_CONTEXT_OVERFLOW.
96
+ */
97
+ export class AgentModelContextOverflowError extends Error {
98
+ constructor(message: string) {
99
+ super(message);
100
+ this.name = "AgentModelContextOverflowError";
101
+ }
102
+ }
103
+
72
104
  /** Error thrown when the agent runtime is configured incorrectly. */
73
105
  export class AgentRuntimeError extends Error {
74
106
  constructor(message: string) {
@@ -25,6 +25,8 @@ export {
25
25
  AgentSetupError,
26
26
  AgentToolExecutionError,
27
27
  AgentModelTimeoutError,
28
+ AgentModelUnavailableError,
29
+ AgentModelContextOverflowError,
28
30
  AgentToolPreCommitError,
29
31
  ToolError,
30
32
  } from "./errors";
@@ -33,6 +35,8 @@ export {
33
35
  AgentRuntime,
34
36
  type AgentRuntimeInvokeOptions,
35
37
  type AgentRuntimeOptions,
38
+ type AgentModelRetryPolicy,
39
+ type AgentCompactionOptions,
36
40
  type AgentRuntimeResult,
37
41
  type AgentRuntimeStatus,
38
42
  type AgentRuntimeToolCallContext,
@@ -102,6 +106,7 @@ export {
102
106
  agentInvocation,
103
107
  agentJsonSchema,
104
108
  agentMessage,
109
+ agentMessageText,
105
110
  agentModelCapabilities,
106
111
  agentModelRequest,
107
112
  agentModelResponse,
@@ -112,12 +117,14 @@ export {
112
117
  agentToolContract,
113
118
  agentToolResult,
114
119
  agentToolResultPart,
120
+ agentReasoning,
115
121
  normalizeJsonValue,
116
122
  type AgentClassSchemaContract,
117
123
  type AgentContentInput,
118
124
  type AgentContentPart,
119
125
  type AgentDataPart,
120
126
  type AgentFilePart,
127
+ type AgentReasoningPart,
121
128
  type AgentFinishReason,
122
129
  type AgentImagePart,
123
130
  type AgentInvocation,