bazis 0.99.4 → 0.99.5

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,5 +1,6 @@
1
1
  import { createToken, type ServiceProvider } from "../di";
2
2
  import { modelValidatorAdapter } from "../../library/validation";
3
+ import { AgentSetupError } from "./errors";
3
4
  import type { AgentDataDefinition } from "./AgentDataDefinition";
4
5
  import { AgentRegistry, type ToolDefinition } from "./AgentRegistry";
5
6
  import {
@@ -62,6 +63,15 @@ export class AgentRuntime {
62
63
  return this.catalog.listTools();
63
64
  }
64
65
 
66
+ /** A profile the model provider does not serve fails before the model is called. */
67
+ private assertModelProfile(agentName: string, profile: string | undefined): void {
68
+ if (profile === undefined || typeof this.modelProvider.modelProfiles !== "function") return;
69
+ const configured = this.modelProvider.modelProfiles();
70
+ if (!configured.includes(profile)) {
71
+ throw new AgentSetupError(`Agent "${agentName}": model profile "${profile}" is not configured (configured: ${[...configured].sort().join(", ")}).`);
72
+ }
73
+ }
74
+
65
75
  /**
66
76
  * Checks a definition the way `run` does, without calling the model: an
67
77
  * invalid field or a tool name not registered in `@Module.tools` throws
@@ -69,6 +79,7 @@ export class AgentRuntime {
69
79
  */
70
80
  check(definition: AgentDataDefinition): void {
71
81
  AgentRegistry.fromDefinition(definition, this.catalog.listTools());
82
+ this.assertModelProfile(definition.name, definition.modelProfile);
72
83
  }
73
84
 
74
85
  /**
@@ -78,6 +89,7 @@ export class AgentRuntime {
78
89
  */
79
90
  async run(definition: AgentDataDefinition, options: AgentRuntimeInvokeOptions = {}): Promise<AgentRuntimeResult> {
80
91
  const registry = AgentRegistry.fromDefinition(definition, this.catalog.listTools());
92
+ this.assertModelProfile(definition.name, options.modelProfile ?? definition.modelProfile);
81
93
  const driver = new AgentExecutionDriver(this.services, registry, this.modelProvider, this.options);
82
94
  return driver.invoke(definition.name, options);
83
95
  }
@@ -16,6 +16,12 @@ export interface AgentModelProvider {
16
16
  request: AgentModelRequest,
17
17
  context: AgentModelProviderContext,
18
18
  ): AgentModelResponse | Promise<AgentModelResponse>;
19
+ /**
20
+ * Model profiles this provider serves, when it routes by profile
21
+ * (`llmRouter`). AgentRuntime rejects an agent naming another profile before
22
+ * the model is called. Without it any profile is passed through.
23
+ */
24
+ modelProfiles?(): readonly string[];
19
25
  }
20
26
 
21
27
  export const AGENT_MODEL_PROVIDER = createToken<AgentModelProvider>("AgentModelProvider");
@@ -143,6 +143,11 @@ await this.agents.run(support, {
143
143
  conversation message when trimming old history. Provider requests include
144
144
  `metadata.agentContext.trace` so context assembly is inspectable.
145
145
 
146
+ With `llmRouter`, `check` and `run` reject a `modelProfile` the router does
147
+ not serve; a fallback to another profile is logged as `LLM model profile
148
+ failed, using fallback`. A model provider that gives up after its own deadline
149
+ throws `AgentModelTimeoutError`, and the run reports `AGENT_PROVIDER_TIMEOUT`.
150
+
146
151
  LLM clients are registered by infra connectors. Agents name a model profile;
147
152
  infra owns provider URLs, model ids, limits and fallback:
148
153
 
@@ -47,6 +47,17 @@ export class AgentToolPreCommitError extends ToolError {
47
47
  }
48
48
  }
49
49
 
50
+ /**
51
+ * A model provider gave up on a request after its own deadline (for example
52
+ * the connector's `timeoutMs`). The run reports it as AGENT_PROVIDER_TIMEOUT.
53
+ */
54
+ export class AgentModelTimeoutError extends Error {
55
+ constructor(message: string) {
56
+ super(message);
57
+ this.name = "AgentModelTimeoutError";
58
+ }
59
+ }
60
+
50
61
  /** Error thrown when the agent runtime is configured incorrectly. */
51
62
  export class AgentRuntimeError extends Error {
52
63
  constructor(message: string) {
@@ -14,6 +14,7 @@ export {
14
14
  AgentSemanticError,
15
15
  AgentSetupError,
16
16
  AgentToolExecutionError,
17
+ AgentModelTimeoutError,
17
18
  AgentToolPreCommitError,
18
19
  ToolError,
19
20
  } from "./errors";
@@ -4,7 +4,7 @@ import type { ServiceProvider } from "../../di";
4
4
  import { getGeneratedOpenApiMetadata } from "../../http/OpenApi/generatedOpenApiRegistry";
5
5
  import { redactSensitive, redactSensitiveText } from "../../../library/redaction";
6
6
  import type { Logger } from "../../kernel/logging/Logger";
7
- import { AgentRuntimeError } from "../errors";
7
+ import { AgentModelTimeoutError, AgentRuntimeError } from "../errors";
8
8
  import { validateAgentJsonSchema } from "./AgentJsonSchema";
9
9
  import type { AgentDefinition, AgentRegistry } from "../AgentRegistry";
10
10
  import {
@@ -1137,7 +1137,7 @@ export class AgentExecutionDriver {
1137
1137
  };
1138
1138
  }
1139
1139
  } catch (error) {
1140
- const code = error instanceof AgentRuntimeTimeoutError
1140
+ const code = error instanceof AgentRuntimeTimeoutError || error instanceof AgentModelTimeoutError
1141
1141
  ? "AGENT_PROVIDER_TIMEOUT"
1142
1142
  : error instanceof AgentRuntimeAbortError
1143
1143
  ? "AGENT_ABORTED"
@@ -1,4 +1,4 @@
1
- import type { HostedService } from "../di";
1
+ import type { HostedService, HostedServiceDiagnostics } from "../di";
2
2
  import { InfraError, type InfraConnector } from "./InfraConnector";
3
3
  import { bindConnectorLifecycle } from "./connectorIdentity";
4
4
  import { awaitAbortable } from "../kernel/internal/awaitAbortable";
@@ -22,6 +22,7 @@ export class InfraLifecycle<TClient> implements HostedService {
22
22
  private disposed = false;
23
23
  private startPromise?: Promise<void>;
24
24
  private disposePromise?: Promise<void>;
25
+ private diagnostics?: HostedServiceDiagnostics;
25
26
 
26
27
  public constructor(
27
28
  private readonly name: string,
@@ -40,10 +41,22 @@ export class InfraLifecycle<TClient> implements HostedService {
40
41
  if (!this.created) {
41
42
  this.client = this.connector.create(this.configs);
42
43
  this.created = true;
44
+ this.forwardDiagnostics();
43
45
  }
44
46
  return this.client as TClient;
45
47
  }
46
48
 
49
+ /** The application logger reaches a client that reports on its own, such as `llmRouter` falling back. */
50
+ public useDiagnostics(diagnostics: HostedServiceDiagnostics): void {
51
+ this.diagnostics = diagnostics;
52
+ if (this.created) this.forwardDiagnostics();
53
+ }
54
+
55
+ private forwardDiagnostics(): void {
56
+ const client = this.client as { useDiagnostics?: (diagnostics: HostedServiceDiagnostics) => void } | undefined;
57
+ if (this.diagnostics !== undefined && typeof client?.useDiagnostics === "function") client.useDiagnostics(this.diagnostics);
58
+ }
59
+
47
60
  public start(signal?: AbortSignal): Promise<void> {
48
61
  this.startPromise ??= Promise.resolve().then(() => this.connect(signal));
49
62
  return this.startPromise;
@@ -232,7 +232,7 @@ export class CodexAppServerClient implements CodexClient {
232
232
  if (method === "account/updated") this.modelCache = undefined;
233
233
  }, () => { this.clearLogin(); this.modelCache = undefined; });
234
234
  const initialized = await client.request("initialize", {
235
- clientInfo: { name: "bazis", title: "Bazis", version: "0.99.4" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.99.5" }, capabilities: { experimentalApi: true },
236
236
  }, 15_000, signal);
237
237
  if (resolve(text(initialized.codexHome, 4096)) !== this.paths.home) throw new CodexError("PROTOCOL_ERROR");
238
238
  client.notify("initialized");
@@ -1,6 +1,9 @@
1
1
  import type { AppConfig } from "../../kernel";
2
+ import type { HostedServiceDiagnostics } from "../../di";
3
+ import { redactSensitive } from "../../../library/redaction";
2
4
  import {
3
5
  AGENT_MODEL_PROVIDER,
6
+ AgentModelTimeoutError,
4
7
  AgentRuntime,
5
8
  agentModelRequest,
6
9
  type AgentModelProvider,
@@ -194,11 +197,13 @@ export class LlmModelRouter implements AgentModelProvider {
194
197
  private initializePromise?: Promise<void>;
195
198
  private disposePromise?: Promise<void>;
196
199
  private disposed = false;
200
+ private diagnostics?: HostedServiceDiagnostics;
197
201
 
198
202
  public constructor(
199
203
  profiles: readonly RuntimeProfile[],
200
204
  private readonly defaultProfile: string,
201
205
  private readonly factories: readonly (() => RuntimeProfile)[] = [],
206
+ private readonly profileNames: readonly string[] = profiles.map((profile) => profile.name),
202
207
  ) {
203
208
  for (let index = 0; index < profiles.length; index += 1) {
204
209
  const profile = profiles[index] as RuntimeProfile;
@@ -213,6 +218,16 @@ export class LlmModelRouter implements AgentModelProvider {
213
218
  return this.completeWithProfile(request, context, requested, new Set<string>());
214
219
  }
215
220
 
221
+ /** Profile names an agent may use in `modelProfile`. */
222
+ public modelProfiles(): readonly string[] {
223
+ return this.profileNames;
224
+ }
225
+
226
+ /** The application logger: a fallback is reported, not silent. */
227
+ public useDiagnostics(diagnostics: HostedServiceDiagnostics): void {
228
+ this.diagnostics = diagnostics;
229
+ }
230
+
216
231
  public connect(signal?: AbortSignal): Promise<void> {
217
232
  if (this.disposed) {
218
233
  return Promise.reject(new InfraError("LLM model router is already disposed."));
@@ -363,11 +378,15 @@ export class LlmModelRouter implements AgentModelProvider {
363
378
  // A second model must not silently append a new answer to already visible text.
364
379
  if (fallback !== undefined && !emittedText) {
365
380
  if (context.signal.aborted) throw new LlmModelRouterError("LLM model request was cancelled before provider dispatch.");
381
+ this.diagnostics?.warn("LLM model profile failed, using fallback", {
382
+ profile: profileName, fallback, agent: context.agentName, invocationId: context.invocationId, error: redactSensitive(error),
383
+ });
366
384
  return this.completeWithProfile(request, context, fallback, visited);
367
385
  }
368
- throw new LlmModelRouterError(
369
- `LLM model profile "${profileName}" failed: ${errorMessageOf(error)}`,
370
- );
386
+ const message = `LLM model profile "${profileName}" failed: ${errorMessageOf(error)}`;
387
+ // Keep a timeout a timeout: the run reports it as AGENT_PROVIDER_TIMEOUT.
388
+ if (error instanceof AgentModelTimeoutError) throw new AgentModelTimeoutError(message);
389
+ throw new LlmModelRouterError(message);
371
390
  } finally {
372
391
  acceptingText = false;
373
392
  }
@@ -432,7 +451,7 @@ export function llmRouter(
432
451
  options: profile.options,
433
452
  });
434
453
  });
435
- return new LlmModelRouter([], defaultProfile, factories);
454
+ return new LlmModelRouter([], defaultProfile, factories, entries.map(([name]) => name));
436
455
  },
437
456
  connect(client, signal) {
438
457
  return client instanceof LlmModelRouter ? client.connect(signal) : undefined;
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AgentModelTimeoutError,
2
3
  agentMessage,
3
4
  agentModelResponse,
4
5
  agentToolCall,
@@ -490,7 +491,7 @@ export class OpenAiCompatibleModelProvider implements AgentModelProvider {
490
491
  const detach = attachAbortForwarding(signal ? AbortSignal.any([signal, this.lifetime.signal]) : this.lifetime.signal, abort);
491
492
  const timeout = this.requestTimeoutMs === undefined
492
493
  ? undefined
493
- : setTimeout(() => abort.abort(new Error("OpenAI-compatible request timed out.")), this.requestTimeoutMs);
494
+ : setTimeout(() => abort.abort(new AgentModelTimeoutError("OpenAI-compatible request timed out.")), this.requestTimeoutMs);
494
495
  try {
495
496
  abort.signal.throwIfAborted();
496
497
  const response = await this.fetcher(`${this.baseUrl}${path}`, {
package/index.ts CHANGED
@@ -82,6 +82,7 @@ export {
82
82
  AgentRuntime,
83
83
  AgentRuntimeError,
84
84
  AgentSemanticError,
85
+ AgentModelTimeoutError,
85
86
  AgentToolExecutionError,
86
87
  ToolError,
87
88
  ApproximateAgentContextTokenEstimator,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.99.4",
3
+ "version": "0.99.5",
4
4
  "description": "Modular backend framework for Bun in TypeScript: DI wired by codegen, HTTP, a PostgreSQL ORM, JWT, WebSocket, agents and a CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Alexander Alenin",