bazis 0.98.34 → 0.99.1

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.
@@ -7,7 +7,7 @@ import {
7
7
  type OpenApiCodegenOperationSpec,
8
8
  type OpenApiCodegenSchema,
9
9
  } from "../../library/openapi/codegen";
10
- import { generateAgentMetadataCatalog, type AgentCodegenSource } from "./agent-codegen";
10
+ import { collectToolSchemaModels, type ToolCodegenSource } from "./tool-codegen";
11
11
  import {
12
12
  analyzeRequestModelHydration,
13
13
  type RequestModelHydration,
@@ -30,8 +30,6 @@ const FRAMEWORK_SOURCE = ((): string | null => {
30
30
  try { return realpathSync("src/bazis") === FRAMEWORK_DIR ? "src/bazis" : null; } catch { return null; }
31
31
  })();
32
32
  const FRAMEWORK_ROOT = FRAMEWORK_SOURCE ?? "src/bazis";
33
- const CORE_AGENT_CATALOG_FILE = `${FRAMEWORK_ROOT}/core/agent/generated/catalog.ts`;
34
- const CORE_AGENT_GENERATED_DIR = `${FRAMEWORK_ROOT}/core/agent/generated`;
35
33
  const isFrameworkSourcePath = (file: string): boolean => FRAMEWORK_SOURCE !== null && file.startsWith(`${FRAMEWORK_SOURCE}/`);
36
34
  const APP_GENERATED_DIR = "src/generated/bazis";
37
35
  let activeGeneratedDir = APP_GENERATED_DIR;
@@ -209,10 +207,10 @@ let ambiguousClassNames = new Set<string>();
209
207
  // Exact roots that do not need convention name resolution: @RequestModel()
210
208
  // classes used by generated transport schemas.
211
209
  let explicitRequestModelRoots = new Set<ts.ClassDeclaration>();
212
- let agentCodegenSources: AgentCodegenSource[] = [];
210
+ let toolCodegenSources: ToolCodegenSource[] = [];
213
211
 
214
212
  const ROUTE_DECORATORS = new Set(["Get", "Post", "Put", "Patch", "Delete", "Options", "Head", "All"]);
215
- const CODEGEN_CHANNEL_DECORATORS = new Set(["Controller", "GrpcController", "WebSocketGateway", "Agent", "Tool", "Prompt", "RequestModel", "UiProfile"]);
213
+ const CODEGEN_CHANNEL_DECORATORS = new Set(["Controller", "GrpcController", "WebSocketGateway", "Tool", "RequestModel", "UiProfile"]);
216
214
  const PRIMITIVE_QUERY_TYPES: Record<string, HttpBindingSpec["type"]> = {
217
215
  string: "string",
218
216
  number: "number",
@@ -251,12 +249,7 @@ const BUILTIN_DEPENDENCY_NAMES = new Set([
251
249
  function isScannableFile(filePath: string): boolean {
252
250
  if (filePath.startsWith("../") || path.isAbsolute(filePath)) return false;
253
251
  if (!filePath.endsWith(".ts") || filePath.endsWith(".d.ts") || /\.(test|spec)\.ts$/.test(filePath)) return false;
254
- if (
255
- filePath === CORE_AGENT_CATALOG_FILE ||
256
- filePath.startsWith(`${APP_GENERATED_DIR}/`)
257
- ) {
258
- return false;
259
- }
252
+ if (filePath.startsWith(`${APP_GENERATED_DIR}/`)) return false;
260
253
  return !/(^|\/)(test|tests|__tests__|fixture|fixtures|__fixtures__|generated|node_modules|vendor|dist|build|bin|coverage|examples)(\/|$)/.test(filePath) && !filePath.startsWith("admin-ui/");
261
254
  }
262
255
 
@@ -414,7 +407,7 @@ function sourceHasCodegenCandidate(source: ts.SourceFile): boolean {
414
407
  const decorators = ts.getDecorators(node) ?? [];
415
408
  const constructorDeps = readConstructorTypeNames(node);
416
409
  // Shared all-channel boundary index: any class that feeds DI/HTTP,
417
- // request/list, OpenAPI/UI, Agent/Tool/Prompt analysis must
410
+ // request/list, OpenAPI/UI, Tool analysis must
418
411
  // be owned by exactly one configured target before rendering begins.
419
412
  if (
420
413
  decorators.some((decorator) => {
@@ -456,7 +449,7 @@ function resetTargetAnalysis(files: Set<string>, generatedDir: string): void {
456
449
  httpBindings = {}; httpControllerFiles = {}; openApiSchemas = {}; openApiOperations = {};
457
450
  grpcBindings = new Map();
458
451
  uiProfileResponseModels = new Map(); openApiSchemaModels = new Map(); requestModelClassNames = new Set(); listRequestClassNames = new Set();
459
- classFilesByName = new Map(); classDeclarationsByName = new Map(); ambiguousClassNames = new Set(); explicitRequestModelRoots = new Set(); agentCodegenSources = [];
452
+ classFilesByName = new Map(); classDeclarationsByName = new Map(); ambiguousClassNames = new Set(); explicitRequestModelRoots = new Set(); toolCodegenSources = [];
460
453
  sourceEntries = [...files].sort((a, b) => a.localeCompare(b)).map((filePath) => ({ filePath, source: programFiles.get(filePath) as ts.SourceFile }));
461
454
  sourceFilePathSet = new Set(sourceEntries.map((entry) => entry.filePath));
462
455
  openApiCodegen = createOpenApiCodegenAnalyzer({ checker, sourceFiles: sourceEntries.map((entry) => entry.source), schemaNameForDuplicate: duplicateOpenApiSchemaName });
@@ -478,7 +471,7 @@ async function generateTarget(name: string, reachable: Set<string>, production:
478
471
  if (!FRAMEWORK_INTERNAL_PREFIXES.some((prefix) => filePath.startsWith(prefix))) {
479
472
  collectClassDeps(source);
480
473
  collectGrpcBindings(source);
481
- agentCodegenSources.push({ filePath, sourceFile: source });
474
+ toolCodegenSources.push({ filePath, sourceFile: source });
482
475
  }
483
476
  collectHttpBindings(source); collectOpenApiSchemas(source); collectUiProfileResponseModels(source);
484
477
  }
@@ -487,46 +480,39 @@ async function generateTarget(name: string, reachable: Set<string>, production:
487
480
  const requestModelImports = resolveRequestModelImports();
488
481
  const requestModelHydration = resolveRequestModelHydration(requestModelImports);
489
482
  const listModelImports = resolveListModelImports();
490
- const agent = generateAgentMetadataCatalog(agentCodegenSources, {
491
- generatedDir, frameworkImports: "public", schemaNameForDeclaration: openApiCodegen.schemaNameForDeclaration,
492
- });
493
- const schemaModels = resolveOpenApiSchemaModelImports(agent.schemaModels, agent.schemaNames);
494
- classifyAgentWarnings(agent.warnings); failOnFatalErrors();
483
+ const tools = collectToolSchemaModels(toolCodegenSources, { schemaNameForDeclaration: openApiCodegen.schemaNameForDeclaration });
484
+ const schemaModels = resolveOpenApiSchemaModelImports(tools.schemaModels, tools.schemaNames);
485
+ classifyToolWarnings(tools.warnings); failOnFatalErrors();
495
486
  const analysisMs = performance.now() - startedAt;
496
487
  const renderStartedAt = performance.now();
497
488
  const requestModels = renderRequestModels(requestModelImports, requestModelHydration);
498
489
  const listModels = renderListModels(listModelImports);
499
- const openApi = renderOpenApiMetadata(openApiSchemas, openApiOperations, "app", agent.schemaNames, schemaModels);
490
+ const openApi = renderOpenApiMetadata(openApiSchemas, openApiOperations, "app", tools.schemaNames, schemaModels);
500
491
  stageWrite(`${generatedDir}/deps.ts`, renderTargetDeps());
501
492
  stageWrite(`${generatedDir}/bindings.ts`, renderTargetBindings());
502
493
  stageWrite(`${generatedDir}/httpRequestModels.ts`, requestModels);
503
494
  stageWrite(`${generatedDir}/httpListModels.ts`, listModels);
504
495
  stageWrite(`${generatedDir}/openapi.ts`, openApi);
505
- stageWrite(`${generatedDir}/agentCatalog.ts`, agent.output);
496
+ // Generated before 0.99.0, when agents were classes; nothing imports it now.
497
+ stageRemove(`${generatedDir}/agentCatalog.ts`);
506
498
  stageWrite(`${generatedDir}/runtime.ts`, renderGeneratedRuntime(name));
507
499
  if (production) stageWrite(`${generatedDir}/fingerprint.ts`, renderSourceFingerprint());
508
- // The framework's own empty agent catalog (a fixture of agent.metadata.test)
509
- // is refreshed only when the framework source is part of this project.
510
- if (production) {
511
- if (FRAMEWORK_SOURCE !== null) {
512
- stageWrite(CORE_AGENT_CATALOG_FILE, generateAgentMetadataCatalog([], CORE_AGENT_GENERATED_DIR).output);
513
- }
514
- } else {
500
+ if (!production) {
515
501
  stageWrite(`${generatedDir}/bootstrap.ts`, 'import { registerBazisGeneratedRuntime } from "./runtime";\n\nawait registerBazisGeneratedRuntime();\n');
516
502
  }
517
503
  for (const warning of warnings) console.warn(`[di:generate] WARNING: ${warning}`);
518
- console.log(`[di:generate] target=${name} sources=${sourceEntries.length} deps=${classDeps.size} controllers=${Object.keys(httpBindings).length} requestModels=${requestModelImports.length} listModels=${listModelImports.length} agents=${agent.agentCount} prepassMs=${prepassMs.toFixed(1)} analysisMs=${analysisMs.toFixed(1)} totalMs=${(performance.now() - startedAt).toFixed(1)}`);
504
+ console.log(`[di:generate] target=${name} sources=${sourceEntries.length} deps=${classDeps.size} controllers=${Object.keys(httpBindings).length} requestModels=${requestModelImports.length} listModels=${listModelImports.length} tools=${tools.toolCount} prepassMs=${prepassMs.toFixed(1)} analysisMs=${analysisMs.toFixed(1)} totalMs=${(performance.now() - startedAt).toFixed(1)}`);
519
505
  return {
520
506
  analysisMs,
521
507
  renderMs: performance.now() - renderStartedAt,
522
508
  eligible: sourceEntries.length,
523
- candidates: classDeps.size + Object.keys(httpBindings).length + requestModelImports.length + listModelImports.length + agent.agentCount,
509
+ candidates: classDeps.size + Object.keys(httpBindings).length + requestModelImports.length + listModelImports.length + tools.toolCount,
524
510
  };
525
511
  }
526
512
 
527
- function classifyAgentWarnings(agentWarnings: readonly string[]): void {
528
- for (let index = 0; index < agentWarnings.length; index += 1) {
529
- const warning = agentWarnings[index] as string;
513
+ function classifyToolWarnings(toolWarnings: readonly string[]): void {
514
+ for (let index = 0; index < toolWarnings.length; index += 1) {
515
+ const warning = toolWarnings[index] as string;
530
516
  if (warning.includes("ambiguous") || warning.includes("declared in more than one file")) {
531
517
  fatalErrors.push(warning);
532
518
  } else {
@@ -561,8 +547,8 @@ function renderGeneratedRuntime(targetId: string): string {
561
547
  "}",
562
548
  "",
563
549
  "async function loadTarget(): Promise<void> {",
564
- " const [deps, bindings, requests, lists, openApi, agentCatalog] = await Promise.all([import(\"./deps\"), import(\"./bindings\"), import(\"./httpRequestModels\"), import(\"./httpListModels\"), import(\"./openapi\"), import(\"./agentCatalog\")]);",
565
- ` await registerBazisGeneratedTargetDescriptor({ id: ${JSON.stringify(targetId)}, classDeps: deps.GENERATED_TARGET_CLASS_DEPS, bindings: bindings.GENERATED_TARGET_BINDINGS, providerAttachments: bindings.GENERATED_PROVIDER_ATTACHMENTS, requestModels: requests.GENERATED_REQUEST_MODELS, requestShapes: requests.GENERATED_REQUEST_MODEL_SHAPES, listModels: lists.GENERATED_LIST_MODELS, openApi: openApi.GENERATED_OPENAPI_METADATA, openApiSchemaModels: openApi.GENERATED_OPENAPI_SCHEMA_MODELS, agentMetadata: agentCatalog.GENERATED_AGENT_METADATA });`,
550
+ " const [deps, bindings, requests, lists, openApi] = await Promise.all([import(\"./deps\"), import(\"./bindings\"), import(\"./httpRequestModels\"), import(\"./httpListModels\"), import(\"./openapi\")]);",
551
+ ` await registerBazisGeneratedTargetDescriptor({ id: ${JSON.stringify(targetId)}, classDeps: deps.GENERATED_TARGET_CLASS_DEPS, bindings: bindings.GENERATED_TARGET_BINDINGS, providerAttachments: bindings.GENERATED_PROVIDER_ATTACHMENTS, requestModels: requests.GENERATED_REQUEST_MODELS, requestShapes: requests.GENERATED_REQUEST_MODEL_SHAPES, listModels: lists.GENERATED_LIST_MODELS, openApi: openApi.GENERATED_OPENAPI_METADATA, openApiSchemaModels: openApi.GENERATED_OPENAPI_SCHEMA_MODELS });`,
566
552
  "}",
567
553
  "",
568
554
  ].join("\n");
@@ -582,8 +568,8 @@ function renderGeneratedRuntime(targetId: string): string {
582
568
  " // Test-only owner-private hook: it is inert unless an isolated test installs it before loading this generated module.",
583
569
  ' (globalThis as { __bazisGeneratedRuntimeTestHook?: (target: string) => void }).__bazisGeneratedRuntimeTestHook?.("production");',
584
570
  ' const { registerBazisGeneratedTargetDescriptor, warnIfGeneratedSourcesChanged } = await import("bazis/core/generatedRuntime");',
585
- " const [deps, bindings, requests, lists, openApi, agentCatalog] = await Promise.all([import(\"./deps\"), import(\"./bindings\"), import(\"./httpRequestModels\"), import(\"./httpListModels\"), import(\"./openapi\"), import(\"./agentCatalog\")]);",
586
- ` await registerBazisGeneratedTargetDescriptor({ id: ${JSON.stringify(targetId)}, classDeps: deps.GENERATED_TARGET_CLASS_DEPS, bindings: bindings.GENERATED_TARGET_BINDINGS, providerAttachments: bindings.GENERATED_PROVIDER_ATTACHMENTS, requestModels: requests.GENERATED_REQUEST_MODELS, requestShapes: requests.GENERATED_REQUEST_MODEL_SHAPES, listModels: lists.GENERATED_LIST_MODELS, openApi: openApi.GENERATED_OPENAPI_METADATA, openApiSchemaModels: openApi.GENERATED_OPENAPI_SCHEMA_MODELS, agentMetadata: agentCatalog.GENERATED_AGENT_METADATA });`,
571
+ " const [deps, bindings, requests, lists, openApi] = await Promise.all([import(\"./deps\"), import(\"./bindings\"), import(\"./httpRequestModels\"), import(\"./httpListModels\"), import(\"./openapi\")]);",
572
+ ` await registerBazisGeneratedTargetDescriptor({ id: ${JSON.stringify(targetId)}, classDeps: deps.GENERATED_TARGET_CLASS_DEPS, bindings: bindings.GENERATED_TARGET_BINDINGS, providerAttachments: bindings.GENERATED_PROVIDER_ATTACHMENTS, requestModels: requests.GENERATED_REQUEST_MODELS, requestShapes: requests.GENERATED_REQUEST_MODEL_SHAPES, listModels: lists.GENERATED_LIST_MODELS, openApi: openApi.GENERATED_OPENAPI_METADATA, openApiSchemaModels: openApi.GENERATED_OPENAPI_SCHEMA_MODELS });`,
587
573
  " // Started from sources edited after codegen: say so instead of silently missing routes.",
588
574
  ' warnIfGeneratedSourcesChanged(import.meta.dir, (await import("./fingerprint")).GENERATED_SOURCE_FINGERPRINT);',
589
575
  "}",
@@ -1348,11 +1334,11 @@ function resolveUiProfileResponseModelImports(): OpenApiSchemaModelImport[] {
1348
1334
  || sourcePathForDeclaration(left.declaration).localeCompare(sourcePathForDeclaration(right.declaration)));
1349
1335
  }
1350
1336
 
1351
- /** Agent DTOs and their referenced classes share the existing nominal OpenAPI registry. */
1352
- function resolveOpenApiSchemaModelImports(agentModels: readonly ts.ClassDeclaration[], agentNames: readonly string[]): OpenApiSchemaModelImport[] {
1337
+ /** Tool contracts and their referenced classes share the existing nominal OpenAPI registry. */
1338
+ function resolveOpenApiSchemaModelImports(toolModels: readonly ts.ClassDeclaration[], toolNames: readonly string[]): OpenApiSchemaModelImport[] {
1353
1339
  const models = new Map(resolveUiProfileResponseModelImports().map((model) => [model.declaration, model.schemaName]));
1354
1340
  // Empty class contracts still have a real object schema and must survive filtering.
1355
- for (const declaration of agentModels) {
1341
+ for (const declaration of toolModels) {
1356
1342
  const name = openApiCodegen.schemaNameForDeclaration(declaration.name!.text, declaration);
1357
1343
  openApiSchemas[name] ??= openApiCodegen.schemaFromDeclaration(declaration);
1358
1344
  models.set(declaration, name);
@@ -1363,11 +1349,11 @@ function resolveOpenApiSchemaModelImports(agentModels: readonly ts.ClassDeclarat
1363
1349
  reachable.add(name);
1364
1350
  collectSchemaRefs(openApiSchemas[name], add);
1365
1351
  };
1366
- for (const name of agentNames) add(name);
1352
+ for (const name of toolNames) add(name);
1367
1353
  for (const [declaration, name] of openApiSchemaModels) {
1368
1354
  if (!reachable.has(name)) continue;
1369
1355
  if (!isNamedExportedTopLevelClass(declaration)) {
1370
- fatalErrors.push(`BAZIS_AGENT_SCHEMA_MODEL_UNIMPORTABLE: Agent schema ${name} must be a named exported top-level class (${sourcePathForDeclaration(declaration)}).`);
1356
+ fatalErrors.push(`BAZIS_TOOL_SCHEMA_MODEL_UNIMPORTABLE: Tool schema ${name} must be a named exported top-level class (${sourcePathForDeclaration(declaration)}).`);
1371
1357
  continue;
1372
1358
  }
1373
1359
  openApiSchemas[name] ??= openApiCodegen.schemaFromDeclaration(declaration);
@@ -0,0 +1,122 @@
1
+ import ts from "typescript";
2
+
3
+ export interface ToolCodegenSource {
4
+ readonly filePath: string;
5
+ /** Program-owned AST; codegen never reparses application source. */
6
+ readonly sourceFile?: ts.SourceFile;
7
+ /** Test fixture input; project generation always supplies sourceFile. */
8
+ readonly content?: string;
9
+ }
10
+
11
+ export interface ToolCodegenResult {
12
+ readonly warnings: readonly string[];
13
+ readonly toolCount: number;
14
+ /** `input`/`output` classes of `@Tool` declarations: they get JSON schemas in the OpenAPI registry. */
15
+ readonly schemaNames: readonly string[];
16
+ /** Program-owned declarations of those classes, for nominal schema bindings. */
17
+ readonly schemaModels: readonly ts.ClassDeclaration[];
18
+ }
19
+
20
+ export interface ToolCodegenOptions {
21
+ /** Use the same naming policy as the project's OpenAPI analyzer. */
22
+ readonly schemaNameForDeclaration?: (name: string, declaration: ts.ClassDeclaration) => string;
23
+ }
24
+
25
+ /**
26
+ * Finds `@Tool({ input, output })` classes. Tool metadata itself is read at
27
+ * runtime from the decorator; codegen only makes sure the contract classes
28
+ * have generated JSON schemas for the model and for validation.
29
+ */
30
+ export function collectToolSchemaModels(
31
+ sources: readonly ToolCodegenSource[],
32
+ options: ToolCodegenOptions = {},
33
+ ): ToolCodegenResult {
34
+ const warnings: string[] = [];
35
+ const declarations = new Map<string, ts.ClassDeclaration>();
36
+ const files = new Map<string, string>();
37
+ const ambiguous = new Set<string>();
38
+ const parsed = sources.map((source) =>
39
+ source.sourceFile ?? ts.createSourceFile(source.filePath, source.content ?? "", ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS));
40
+
41
+ for (const source of parsed) {
42
+ visitClasses(source, (node) => {
43
+ const name = node.name!.text;
44
+ const existing = files.get(name);
45
+ if (existing === undefined) files.set(name, source.fileName);
46
+ else if (existing !== source.fileName) ambiguous.add(name);
47
+ declarations.set(name, node);
48
+ });
49
+ }
50
+
51
+ const contractNames = new Set<string>();
52
+ let toolCount = 0;
53
+ for (const source of parsed) {
54
+ visitClasses(source, (node) => {
55
+ const call = toolDecoratorCall(node);
56
+ if (call === undefined) return;
57
+ const className = node.name!.text;
58
+ const where = sourceLocation(source, node);
59
+ if (!isNamedExportedClass(node)) {
60
+ warnings.push(`@Tool class "${className}" (${where}) is not a named export; codegen can only import named-export classes.`);
61
+ return;
62
+ }
63
+ toolCount += 1;
64
+ const options = call.arguments[0];
65
+ if (options === undefined || !ts.isObjectLiteralExpression(options)) return;
66
+ for (const property of options.properties) {
67
+ if (!ts.isPropertyAssignment(property)) continue;
68
+ const key = ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name) ? property.name.text : undefined;
69
+ if (key !== "input" && key !== "output") continue;
70
+ if (!ts.isIdentifier(property.initializer)) {
71
+ warnings.push(`@Tool ${className} (${where}): ${key} must be a class identifier.`);
72
+ continue;
73
+ }
74
+ const contract = property.initializer.text;
75
+ if (ambiguous.has(contract)) {
76
+ warnings.push(`@Tool ${className} (${where}): ${key} class "${contract}" is declared in more than one file.`);
77
+ continue;
78
+ }
79
+ contractNames.add(contract);
80
+ }
81
+ });
82
+ }
83
+
84
+ const schemaModels = [...contractNames]
85
+ .map((name) => declarations.get(name))
86
+ .filter((declaration): declaration is ts.ClassDeclaration => declaration !== undefined);
87
+ return {
88
+ warnings: Object.freeze(warnings),
89
+ toolCount,
90
+ schemaNames: Object.freeze(schemaModels
91
+ .map((declaration) => options.schemaNameForDeclaration?.(declaration.name!.text, declaration) ?? declaration.name!.text)
92
+ .sort((left, right) => left.localeCompare(right))),
93
+ schemaModels: Object.freeze(schemaModels),
94
+ };
95
+ }
96
+
97
+ function visitClasses(source: ts.SourceFile, onClass: (node: ts.ClassDeclaration) => void): void {
98
+ const visit = (node: ts.Node): void => {
99
+ if (ts.isClassDeclaration(node) && node.name) onClass(node);
100
+ ts.forEachChild(node, visit);
101
+ };
102
+ ts.forEachChild(source, visit);
103
+ }
104
+
105
+ function toolDecoratorCall(node: ts.ClassDeclaration): ts.CallExpression | undefined {
106
+ for (const decorator of ts.getDecorators(node) ?? []) {
107
+ const expression = decorator.expression;
108
+ if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "Tool") return expression;
109
+ }
110
+ return undefined;
111
+ }
112
+
113
+ function isNamedExportedClass(node: ts.ClassDeclaration): boolean {
114
+ const modifiers = ts.getModifiers(node) ?? [];
115
+ return modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
116
+ && !modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword);
117
+ }
118
+
119
+ function sourceLocation(source: ts.SourceFile, node: ts.Node): string {
120
+ const position = source.getLineAndCharacterOfPosition(node.getStart(source));
121
+ return `${source.fileName}:${position.line + 1}:${position.character + 1}`;
122
+ }
package/index.ts CHANGED
@@ -78,60 +78,44 @@ export {
78
78
  } from "./core/di";
79
79
  export {
80
80
  AGENT_MODEL_PROVIDER,
81
- Agent,
82
- AgentRegistry,
81
+ AGENT_RUNTIME_OPTIONS,
83
82
  AgentRuntime,
84
83
  AgentRuntimeError,
85
84
  AgentSemanticError,
86
85
  AgentToolExecutionError,
87
- AgentToolExecutor,
88
86
  ApproximateAgentContextTokenEstimator,
89
87
  DefaultAgentContextBuilder,
90
- Prompt,
91
- Task,
92
88
  Tool,
93
89
  agentClassSchema,
94
90
  agentData,
95
- agentTaskMetadataOf,
96
91
  agentInvocation,
97
92
  agentJsonSchema,
98
93
  agentMessage,
99
94
  agentModelCapabilities,
100
95
  agentModelRequest,
101
96
  agentModelResponse,
102
- agentOutput,
103
97
  agentOutputContract,
104
98
  agentText,
105
99
  agentToolCall,
106
100
  agentToolContract,
107
101
  agentToolResult,
108
- collectAgentCatalog,
109
- type AgentCatalog,
110
- type AgentCatalogExtras,
111
102
  type AgentContext,
112
103
  type AgentContextBuildInput,
113
104
  type AgentContextBuilder,
114
105
  type AgentContextLimits,
115
106
  type AgentContextTokenEstimator,
116
107
  type AgentContextTraceEntry,
117
- type AgentDefinition,
108
+ type AgentDataDefinition,
118
109
  type AgentInvocation,
119
110
  type AgentMessage,
120
- type AgentMetadataIndex,
121
- type AgentTaskDefinition,
122
- type AgentTaskMetadata,
123
- type AgentTaskOptions,
124
- type TaskDecoratorOptions,
125
111
  type AgentModelCapabilities,
126
112
  type AgentModelRequest,
127
113
  type AgentModelResponse,
128
- type AgentOptions,
129
114
  type AgentOutputContract,
130
115
  type AgentRuntimeInvokeOptions,
131
116
  type AgentRuntimeOptions,
132
117
  type AgentRuntimeResult,
133
118
  type AgentRuntimeStatus,
134
- type AgentRuntimeTaskInvokeOptions,
135
119
  type AgentRuntimeToolExecutionOptions,
136
120
  type AgentToolCall,
137
121
  type AgentExecutableTool,
@@ -175,10 +159,7 @@ export {
175
159
  type AgentModelProvider,
176
160
  type AgentModelProviderContext,
177
161
  type JsonValue,
178
- type PromptOptions,
179
- type PromptSectionKind,
180
- type PromptSectionMetadata,
181
- type PromptSectionOptions,
162
+ type ToolDefinition,
182
163
  type ToolOptions,
183
164
  } from "./core/agent";
184
165
  export {
@@ -461,12 +461,23 @@ export function createOpenApiCodegenAnalyzer(input: OpenApiCodegenAnalyzerInput)
461
461
  };
462
462
 
463
463
  const schemaFromArrayTsType = (type: ts.Type, seen: Set<string>): OpenApiCodegenSchema => {
464
- if (checker.isTupleType(type)) {
465
- const tupleArgs = typeArgumentsOf(type);
466
- return { type: "array", items: mergeResponseSchemas(tupleArgs.map((item) => schemaFromTsType(item, seen))) ?? {} };
464
+ // A recursive alias can close its loop through an array without an object
465
+ // in between (`type Json = … | readonly Json[]`): the repeat is an untyped array.
466
+ const key = typeIdentity(type);
467
+ if (seen.has(key)) {
468
+ return { type: "array" };
469
+ }
470
+ seen.add(key);
471
+ try {
472
+ if (checker.isTupleType(type)) {
473
+ const tupleArgs = typeArgumentsOf(type);
474
+ return { type: "array", items: mergeResponseSchemas(tupleArgs.map((item) => schemaFromTsType(item, seen))) ?? {} };
475
+ }
476
+ const args = typeArgumentsOf(type);
477
+ return { type: "array", items: args[0] ? schemaFromTsType(args[0], seen) : {} };
478
+ } finally {
479
+ seen.delete(key);
467
480
  }
468
- const args = typeArgumentsOf(type);
469
- return { type: "array", items: args[0] ? schemaFromTsType(args[0], seen) : {} };
470
481
  };
471
482
 
472
483
  const schemaFromObjectTsType = (type: ts.Type, seen: Set<string>): OpenApiCodegenSchema => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.98.34",
3
+ "version": "0.99.1",
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",
@@ -1,11 +0,0 @@
1
- import { defineAgentMetadata, type AgentOptions } from "../metadata";
2
-
3
- /**
4
- * Declares an executable agent class. The class stays a regular DI service;
5
- * this decorator only adds framework metadata for discovery and validation.
6
- */
7
- export function Agent(options: AgentOptions = {}) {
8
- return (value: abstract new (...args: never[]) => unknown, context: ClassDecoratorContext): void => {
9
- defineAgentMetadata(value, context.metadata, options);
10
- };
11
- }
@@ -1,11 +0,0 @@
1
- import { definePromptMetadata, type PromptOptions } from "../metadata";
2
-
3
- /**
4
- * Declares a reusable prompt contract. Prompts are metadata classes so modules
5
- * can own and version them without exposing provider-specific APIs.
6
- */
7
- export function Prompt(options: PromptOptions = {}) {
8
- return (value: abstract new (...args: never[]) => unknown, context: ClassDecoratorContext): void => {
9
- definePromptMetadata(value, context.metadata, options);
10
- };
11
- }
@@ -1,29 +0,0 @@
1
- import { defineAgentTaskMetadata, type AgentTaskOptions } from "../metadata";
2
-
3
- type TaskDecorator = (value: (...args: never[]) => unknown, context: ClassMethodDecoratorContext) => void;
4
-
5
- export type TaskDecoratorOptions = string | AgentTaskOptions;
6
-
7
- function normalizeOptions(options: TaskDecoratorOptions | undefined): AgentTaskOptions {
8
- if (options === undefined) {
9
- return {};
10
- }
11
- if (typeof options === "string") {
12
- return { name: options };
13
- }
14
- return options;
15
- }
16
-
17
- /**
18
- * Declares an executable agent task on an @Agent class. This mirrors
19
- * controller action decorators: the method is a business operation contract,
20
- * while AgentRuntime performs the actual AI execution.
21
- */
22
- export function Task(options?: TaskDecoratorOptions): TaskDecorator {
23
- return (_value, context) => {
24
- if (context.static || context.private) {
25
- throw new Error(`@Task supports public instance methods only ("${String(context.name)}").`);
26
- }
27
- defineAgentTaskMetadata(context.name, context.metadata, normalizeOptions(options));
28
- };
29
- }
@@ -1,26 +0,0 @@
1
- // This file is auto-generated by `bazis codegen`.
2
- // Do not edit manually.
3
-
4
- import type { Class } from "../../di";
5
- import type { AgentMetadataIndex } from "../AgentRegistry";
6
- import type { AgentMetadata, PromptMetadata, ToolMetadata } from "../metadata";
7
-
8
- function readonlyMetadataMap<K, V>(entries: readonly (readonly [K, V])[]): ReadonlyMap<K, V> {
9
- const lookup = new Map<K, V>(entries);
10
- const facade: ReadonlyMap<K, V> = {
11
- get size() { return lookup.size; }, has: (key) => lookup.has(key), get: (key) => lookup.get(key),
12
- entries: () => lookup.entries(), keys: () => lookup.keys(), values: () => lookup.values(),
13
- forEach: (callback, thisArg) => { for (const [key, value] of lookup) callback.call(thisArg, value, key, facade); },
14
- [Symbol.iterator]: () => lookup[Symbol.iterator](),
15
- };
16
- return Object.freeze(facade);
17
- }
18
-
19
- export const GENERATED_AGENT_METADATA: AgentMetadataIndex = Object.freeze({
20
- agents: readonlyMetadataMap<Class<object>, AgentMetadata>([
21
- ]),
22
- tools: readonlyMetadataMap<Class<object>, ToolMetadata>([
23
- ]),
24
- prompts: readonlyMetadataMap<Class<object>, PromptMetadata>([
25
- ]),
26
- });