bazis 0.136.0 → 0.138.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 +7 -4
- package/cli/generateProject.ts +11 -18
- package/cli/main.ts +9 -1
- package/cli/parseCli.ts +9 -0
- package/cli/templates/module.ts +16 -19
- package/cli/templates/passport.ts +2 -2
- package/cli/upgrade/markColumns.ts +151 -0
- package/cli/upgrade/runUpgrade.ts +57 -0
- package/core/access/Access.module.ts +1 -1
- package/core/access/catalog/moduleFields.ts +41 -4
- package/core/access/catalog/permissions.ts +10 -0
- package/core/access/check/Access.ts +5 -5
- package/core/access/check/can.ts +2 -1
- package/core/access/index.ts +11 -1
- package/core/access/roles/roles.ts +4 -2
- package/core/access/testing/index.ts +26 -4
- package/core/agent/ToolRunner.ts +7 -1
- package/core/agent/defineTool.ts +109 -0
- package/core/agent/index.ts +8 -3
- package/core/agent/moduleContributions-v1.ts +3 -10
- package/core/agent/session/entities.ts +45 -45
- package/core/app/index.ts +1 -1
- package/core/app/messages.ts +3 -3
- package/core/app/runApp.ts +90 -26
- package/core/cache/ICache.ts +1 -1
- package/core/cache/README.md +4 -2
- package/core/cache/SPEC.md +16 -9
- package/core/cache/http/missingCacheModuleCheck.ts +5 -4
- package/core/cache/internal/buildCacheProviders.ts +4 -5
- package/core/cache/providers/cachedProviders.ts +1 -1
- package/core/di/index.ts +0 -4
- package/core/di/lowLevel.ts +12 -0
- package/core/di/module/index.ts +0 -3
- package/core/di/module/shortcuts.ts +31 -86
- package/core/generatedRuntime.ts +11 -0
- package/core/grpc/Grpc.module.ts +2 -1
- package/core/grpc/grpcClients.ts +2 -1
- package/core/http/Authorization/metadata.ts +1 -1
- package/core/http/Decorators/metadata.ts +3 -0
- package/core/http/Decorators/routes.ts +12 -0
- package/core/http/Errors/HttpError.ts +16 -2
- package/core/http/Http.config.ts +22 -0
- package/core/http/HttpContext/HttpContext.ts +1 -1
- package/core/http/HttpServer.ts +29 -9
- package/core/http/Middleware/errorHandler.ts +16 -1
- package/core/http/OpenApi/openApiDocument.ts +44 -5
- package/core/http/Results/normalize.ts +38 -1
- package/core/http/Results/results.ts +5 -2
- package/core/http/Routing/RouterBuilder.ts +27 -2
- package/core/http/httpModule.ts +10 -8
- package/core/http/index.ts +4 -0
- package/core/http/middlewareTokens.ts +12 -0
- package/core/http/options.ts +5 -1
- package/core/http-client/httpClients.ts +2 -1
- package/core/infra/Infra.ts +3 -3
- package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
- package/core/infra/connectors/codex/contracts.ts +3 -3
- package/core/infra/connectors/llm.ts +2 -1
- package/core/infra/connectors/postgres.ts +4 -2
- package/core/infra/connectors/redis.ts +2 -1
- package/core/infra/index.ts +1 -1
- package/core/kernel/KernelBuilder.ts +2 -1
- package/core/kernel/config/ConfigRegistry.ts +2 -1
- package/core/kernel/config/addConfigOptions.ts +2 -1
- package/core/kernel/config/defineConfig.ts +56 -20
- package/core/kernel/events/EventBus.ts +1 -0
- package/core/kernel/events/eventSubscribers.ts +1 -1
- package/core/kernel/events/onEvent.ts +2 -1
- package/core/kernel/index.ts +3 -0
- package/core/orm/buildOrmProviders.ts +4 -2
- package/core/orm/contextEntities.ts +36 -0
- package/core/orm/databaseConnector.ts +9 -3
- package/core/orm/databaseHttpError.ts +21 -0
- package/core/orm/encryptedWith.ts +1 -1
- package/core/orm/index.ts +12 -1
- package/core/orm/ormModule.ts +19 -8
- package/core/orm/ownedStoreContributions.ts +2 -1
- package/core/orm/registerRepositories.ts +2 -1
- package/core/scripts/di-generate.ts +113 -12
- package/core/testing/index.ts +51 -11
- package/core/tools/StandardTools.module.ts +1 -1
- package/core/tools/contracts/Tools.config.ts +13 -7
- package/core/tools/generated/deps.ts +3 -3
- package/core/tools/http/HttpRequest.tool.ts +1 -2
- package/core/tools/http/publicAddress.ts +2 -2
- package/core/tools/index.ts +1 -1
- package/core/tools/orm/OrmEntity.references.ts +7 -8
- package/core/tools/orm/ormEntityTools.ts +54 -28
- package/core/websocket/DESIGN.md +5 -3
- package/core/websocket/middleware.ts +1 -1
- package/core/websocket/websocketModule.ts +3 -3
- package/core/workflow/contracts/Workflow.config.ts +5 -5
- package/core/workflow/definitions_module/WorkflowDefinitions.module.ts +3 -4
- package/core/workflow/definitions_module/code/defineProcess.ts +213 -11
- package/core/workflow/definitions_module/code/inspectProcesses.ts +12 -2
- package/core/workflow/definitions_module/http/Processes.controller.ts +2 -2
- package/core/workflow/definitions_module/internal/PublishedVersions.ts +39 -0
- package/core/workflow/definitions_module/model/WfProcess.model.ts +24 -11
- package/core/workflow/definitions_module/model/WfVersion.model.ts +7 -1
- package/core/workflow/definitions_module/services/IWorkflowDefinitions.service.ts +4 -1
- package/core/workflow/definitions_module/services/WorkflowDefinitions.service.ts +17 -7
- package/core/workflow/generated/deps.ts +2 -1
- package/core/workflow/index.ts +23 -1
- package/core/workflow/internal/WorkflowLanguages.ts +1 -1
- package/core/workflow/internal/http.ts +2 -2
- package/core/workflow/runtime_module/background/WorkflowWorker.ts +6 -2
- package/core/workflow/runtime_module/http/Instances.controller.ts +1 -1
- package/core/workflow/runtime_module/internal/WorkflowJobs.ts +43 -1
- package/core/workflow/runtime_module/model/WfHistory.model.ts +13 -6
- package/core/workflow/runtime_module/model/WfInstance.model.ts +30 -10
- package/core/workflow/runtime_module/model/WfJob.model.ts +22 -10
- package/core/workflow/runtime_module/model/WfSubscription.model.ts +8 -3
- package/core/workflow/runtime_module/model/WfToken.model.ts +16 -7
- package/core/workflow/runtime_module/services/IWorkflowRuntime.service.ts +10 -6
- package/core/workflow/runtime_module/services/WorkflowRuntime.service.ts +5 -2
- package/core/workflow/tasks_module/model/WfTask.model.ts +25 -9
- package/core/workflow/testing/index.ts +46 -4
- package/index.ts +4 -3
- package/library/openapi/codegen.ts +2 -2
- package/library/orm/DbContext.ts +37 -1
- package/library/orm/Metadata/ModelBuilder.ts +23 -7
- package/library/orm/Metadata/decorators.ts +23 -10
- package/library/orm/Metadata/types.ts +2 -0
- package/library/orm/Transactions/TransactionScopeCoordinator.ts +3 -3
- package/library/orm/errors.ts +39 -1
- package/library/orm/index.ts +1 -0
- package/library/terminal/messages.ts +6 -6
- package/library/validation/README.md +1 -1
- package/library/validation/SPEC.md +1 -1
- package/library/workflow/index.ts +1 -0
- package/library/workflow/nodeConfigs.ts +130 -0
- package/package.json +2 -1
package/core/access/check/can.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AuthorizeCheck, AuthorizeContext } from "../../http";
|
|
2
|
+
import type { PermissionKey } from "../catalog/permissions";
|
|
2
3
|
import type { AccessObject } from "../contracts/types";
|
|
3
4
|
import { IAccess } from "./IAccess.service";
|
|
4
5
|
|
|
@@ -13,7 +14,7 @@ import { IAccess } from "./IAccess.service";
|
|
|
13
14
|
* @Post() create(ctx: HttpContext) { … }
|
|
14
15
|
* ```
|
|
15
16
|
*/
|
|
16
|
-
export function can(permission:
|
|
17
|
+
export function can(permission: PermissionKey, objectOf?: (ctx: AuthorizeContext) => AccessObject | Promise<AccessObject>): AuthorizeCheck {
|
|
17
18
|
return async (ctx) => {
|
|
18
19
|
const access = await ctx.services.resolve(IAccess).for(ctx);
|
|
19
20
|
return await access.can(permission, objectOf === undefined ? undefined : await objectOf(ctx));
|
package/core/access/index.ts
CHANGED
|
@@ -5,9 +5,19 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import "./catalog/moduleFields";
|
|
7
7
|
import "./generated/deps";
|
|
8
|
+
import type { accessPermissions } from "./Access.module";
|
|
9
|
+
import type { PermissionsOf } from "./catalog/permissions";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The permissions the application declares, by key: what `can`, `access.can`,
|
|
13
|
+
* `access.filter` and `defineRoles` accept. Nobody writes it by hand: `bazis codegen`
|
|
14
|
+
* fills it from `permissions` of the application's modules
|
|
15
|
+
* (`src/generated/bazis/permissions.ts`), a framework pack from its own.
|
|
16
|
+
*/
|
|
17
|
+
export interface BazisPermissions extends PermissionsOf<typeof accessPermissions> {}
|
|
8
18
|
|
|
9
19
|
export { AccessModule } from "./Access.module";
|
|
10
|
-
export { definePermissions, type AttributeSpec, type PermissionDeclarations, type PermissionSpec, type ScopeSource } from "./catalog/permissions";
|
|
20
|
+
export { definePermissions, type AttributeSpec, type OwnPermissionKey, type PermissionDeclarations, type PermissionKey, type PermissionsOf, type PermissionSpec, type ScopeSource } from "./catalog/permissions";
|
|
11
21
|
export { IAccessCatalog } from "./catalog/IAccessCatalog.service";
|
|
12
22
|
export type { DeclaredRole, OwnedPermission, OwnedReferenceKind } from "./catalog/AccessCatalog";
|
|
13
23
|
export { IAccess } from "./check/IAccess.service";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { OwnPermissionKey, PermissionKey } from "../catalog/permissions";
|
|
1
2
|
import type { Condition, Grant, GrantFields, Scope } from "../contracts/types";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -5,9 +6,10 @@ import type { Condition, Grant, GrantFields, Scope } from "../contracts/types";
|
|
|
5
6
|
* grant with conditions.
|
|
6
7
|
*/
|
|
7
8
|
export type RoleGrant =
|
|
8
|
-
|
|
|
9
|
+
| PermissionKey
|
|
10
|
+
| `${OwnPermissionKey}:own`
|
|
9
11
|
| {
|
|
10
|
-
readonly permission:
|
|
12
|
+
readonly permission: PermissionKey;
|
|
11
13
|
readonly scopes?: "all" | readonly Scope[];
|
|
12
14
|
readonly where?: Condition;
|
|
13
15
|
readonly fields?: GrantFields;
|
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
* Test helpers of access (ACCESS-001 §8): a rights system whose grants are set
|
|
3
3
|
* right in the test.
|
|
4
4
|
*/
|
|
5
|
+
import { singletonValue, type ProviderDefinition } from "../../di";
|
|
5
6
|
import { PRINCIPAL_STATE_KEY, type AuthorizeContext, type RequestPrincipal } from "../../http";
|
|
6
7
|
import { AccessPolicy, type ScopePrincipals } from "../contracts/AccessPolicy";
|
|
7
8
|
import type { AccessSubject, Grants, Scope } from "../contracts/types";
|
|
8
9
|
|
|
9
10
|
export interface StaticAccessOptions {
|
|
10
|
-
/** Subject → permission → grants; a permission not listed gives
|
|
11
|
-
readonly grants
|
|
11
|
+
/** Subject → permission → grants; a permission not listed gives {@link otherwise}. */
|
|
12
|
+
readonly grants?: Readonly<Record<string, Readonly<Record<string, Grants>>>>;
|
|
13
|
+
/** What a permission not listed in `grants` gives: nothing by default, `"all"` to allow everything. */
|
|
14
|
+
readonly otherwise?: Grants;
|
|
15
|
+
/** Who a request without a signed-in principal is; by default nobody, and the request is refused. */
|
|
16
|
+
readonly anonymous?: string;
|
|
12
17
|
/** Subject → the owner ids meaning it; by default `sub` after the last `:`. */
|
|
13
18
|
readonly owners?: Readonly<Record<string, readonly string[]>>;
|
|
14
19
|
/** Subject → its addressees in every partition; by default `user:<owner id>`. */
|
|
@@ -32,11 +37,12 @@ export class StaticAccessPolicy extends AccessPolicy {
|
|
|
32
37
|
|
|
33
38
|
subject(ctx: AuthorizeContext): AccessSubject | undefined {
|
|
34
39
|
const sub = (ctx.state.get(PRINCIPAL_STATE_KEY) as RequestPrincipal | undefined)?.subject;
|
|
35
|
-
|
|
40
|
+
if (sub !== undefined && sub !== "") return { sub };
|
|
41
|
+
return this.options.anonymous === undefined ? undefined : { sub: this.options.anonymous };
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
grants(subject: AccessSubject, permission: string): Grants {
|
|
39
|
-
return this.options.grants[subject.sub]?.[permission] ?? [];
|
|
45
|
+
return this.options.grants?.[subject.sub]?.[permission] ?? this.options.otherwise ?? [];
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
ownerIds(subject: AccessSubject): readonly string[] {
|
|
@@ -49,6 +55,22 @@ export class StaticAccessPolicy extends AccessPolicy {
|
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
|
|
58
|
+
/**
|
|
59
|
+
* A rights system that allows everything, for a test that is not about rights:
|
|
60
|
+
* every subject has every permission in every partition.
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* const app = await startTestApp(OrdersApp, { overrides: [allowAll()] });
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* Requests still need a signed-in principal; `allowAll({ anonymous: "test" })`
|
|
67
|
+
* also lets in a request without one, as the subject `test`. `grants` narrows
|
|
68
|
+
* single subjects: `allowAll({ grants: { "user:2": { "orders.delete": [] } } })`.
|
|
69
|
+
*/
|
|
70
|
+
export function allowAll(options: Omit<StaticAccessOptions, "otherwise"> = {}): ProviderDefinition {
|
|
71
|
+
return singletonValue(AccessPolicy, new StaticAccessPolicy({ ...options, otherwise: "all" }));
|
|
72
|
+
}
|
|
73
|
+
|
|
52
74
|
function ownerId(sub: string): string {
|
|
53
75
|
return sub.slice(sub.lastIndexOf(":") + 1);
|
|
54
76
|
}
|
package/core/agent/ToolRunner.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DiError, Global, HOSTED_SERVICE, Module, registerModuleMetadataExpander, SERVICE_PROVIDER, type BazisModuleMetadata, type ServiceProvider } from "../di";
|
|
2
|
+
import { DI } from "../di/lowLevel";
|
|
2
3
|
import { LOGGER } from "../kernel/logging/Logger";
|
|
3
4
|
import { modelValidatorAdapter } from "../../library/validation";
|
|
4
5
|
import { AgentRegistry, type ToolDefinition } from "./AgentRegistry";
|
|
@@ -42,6 +43,11 @@ export class ToolRunner {
|
|
|
42
43
|
this.executor = new AgentToolExecutor(services, this.catalog, { schemaValidator: modelValidatorAdapter, ...executorOptions }, logger);
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
/** Every Tool of the application, whoever may call it. */
|
|
47
|
+
listTools(): readonly ToolDefinition[] {
|
|
48
|
+
return this.catalog.listTools();
|
|
49
|
+
}
|
|
50
|
+
|
|
45
51
|
/** A view of the Tools this caller may run. `"agent"` is refused: agents run through `AgentRuntime`. */
|
|
46
52
|
as(caller: string): BoundToolRunner {
|
|
47
53
|
let id: string;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ServiceProvider, type Class } from "../di";
|
|
2
|
+
import { registerFrameworkClassDeps } from "../di/module/autoDeps";
|
|
3
|
+
import { Tool } from "./decorators/Tool";
|
|
4
|
+
import { AgentSetupError } from "./errors";
|
|
5
|
+
import { toolMetadataOf, type ToolOptions } from "./metadata";
|
|
6
|
+
import type { AgentToolExecutionContext } from "./AgentToolExecutor";
|
|
7
|
+
|
|
8
|
+
declare const TOOL_TYPES: unique symbol;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A Tool made by {@link defineTool}. It stands in `@Module({ tools })` next to
|
|
12
|
+
* the classes with `@Tool`, and a process names it by reference.
|
|
13
|
+
*/
|
|
14
|
+
export type DefinedTool<I = unknown, O = unknown> = Class<object> & {
|
|
15
|
+
/** The name it is called by: `orm.order.find`. */
|
|
16
|
+
readonly toolName: string;
|
|
17
|
+
/** Only for the types: what `execute` takes and gives back. */
|
|
18
|
+
readonly [TOOL_TYPES]?: { readonly input: I; readonly output: O };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Several made Tools under the names their factory gave them: `orderTools.find`. */
|
|
22
|
+
export type ToolSet<K extends string = string> = { readonly [P in K]: DefinedTool };
|
|
23
|
+
|
|
24
|
+
/** What `defineTool` takes: what `@Tool` takes, without the classes of input and output, and the work itself. */
|
|
25
|
+
export interface DefinedToolOptions<I = unknown, O = unknown> extends Omit<ToolOptions, "input" | "output"> {
|
|
26
|
+
/**
|
|
27
|
+
* Does the work. `services` is the scope of the call: `services.resolve(OrdersDbContext)`.
|
|
28
|
+
* The input comes as the caller sent it: check it here.
|
|
29
|
+
*/
|
|
30
|
+
execute(input: I, context: AgentToolExecutionContext, services: ServiceProvider): O | Promise<O>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const DEFINED = new WeakSet<object>();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A Tool without a class: for Tools made by a function, when their number and
|
|
37
|
+
* names are known only from its arguments. A Tool written by hand is a class
|
|
38
|
+
* with `@Tool`.
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* export function exportTools(formats: readonly string[]): ToolSet {
|
|
42
|
+
* return Object.fromEntries(formats.map((format) => [format, defineTool({
|
|
43
|
+
* name: `reports.export.${format}`,
|
|
44
|
+
* description: `Exports a report as ${format}.`,
|
|
45
|
+
* sideEffect: "read",
|
|
46
|
+
* callers: ["workflow"],
|
|
47
|
+
* inputSchema: { type: "object", properties: { reportId: { type: "string" } }, required: ["reportId"] },
|
|
48
|
+
* execute: (input: { reportId: string }, _context, services) => services.resolve(ReportService).export(input.reportId, format),
|
|
49
|
+
* })]));
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* @Module({ tools: [SendReportTool, exportTools(["pdf", "csv"])] })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function defineTool<I = unknown, O = unknown>(definition: DefinedToolOptions<I, O>): DefinedTool<I, O> {
|
|
56
|
+
if (definition === null || typeof definition !== "object" || typeof definition.execute !== "function") {
|
|
57
|
+
throw new AgentSetupError("defineTool takes the description of a Tool with its execute(input, context, services).");
|
|
58
|
+
}
|
|
59
|
+
const { execute, ...options } = definition;
|
|
60
|
+
|
|
61
|
+
@Tool(options)
|
|
62
|
+
class MadeTool {
|
|
63
|
+
static readonly toolName = String(options.name).trim();
|
|
64
|
+
|
|
65
|
+
constructor(private readonly services: ServiceProvider) {}
|
|
66
|
+
|
|
67
|
+
execute(input: unknown, context: AgentToolExecutionContext): unknown {
|
|
68
|
+
return execute(input as I, context, this.services);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The class is made here, so codegen never sees its constructor.
|
|
73
|
+
registerFrameworkClassDeps([[MadeTool, [ServiceProvider]]]);
|
|
74
|
+
DEFINED.add(MadeTool);
|
|
75
|
+
Object.defineProperty(MadeTool, "name", { value: `Tool(${MadeTool.toolName})` });
|
|
76
|
+
return MadeTool as unknown as DefinedTool<I, O>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Whether a value came from {@link defineTool}. */
|
|
80
|
+
export function isDefinedTool(value: unknown): value is DefinedTool {
|
|
81
|
+
return typeof value === "function" && DEFINED.has(value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @internal The Tools a module lists in `tools`: classes with `@Tool`, made Tools
|
|
86
|
+
* and sets of them, as one list of classes.
|
|
87
|
+
*/
|
|
88
|
+
export function moduleToolsOf(value: unknown): readonly Class<object>[] {
|
|
89
|
+
if (value === undefined) return [];
|
|
90
|
+
if (!Array.isArray(value)) throw new AgentSetupError("tools must be an array of classes with @Tool, Tools made by defineTool and sets of them.");
|
|
91
|
+
const out: Class<object>[] = [];
|
|
92
|
+
for (const entry of value) {
|
|
93
|
+
if (typeof entry === "function") {
|
|
94
|
+
out.push(entry as Class<object>);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const members = typeof entry === "object" && entry !== null && !Array.isArray(entry) ? Object.entries(entry) : undefined;
|
|
98
|
+
if (members === undefined || members.length === 0 || members.some(([, member]) => !isDefinedTool(member))) {
|
|
99
|
+
throw new AgentSetupError("tools must list classes with @Tool, Tools made by defineTool and sets of them: an object in the list must hold only Tools made by defineTool.");
|
|
100
|
+
}
|
|
101
|
+
for (const [, member] of members) out.push(member as Class<object>);
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The name a class with `@Tool` or a made Tool is called by; `undefined` for anything else. */
|
|
107
|
+
export function toolNameOf(tool: unknown): string | undefined {
|
|
108
|
+
return typeof tool === "function" ? toolMetadataOf(tool as Class<object>)?.name : undefined;
|
|
109
|
+
}
|
package/core/agent/index.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import type { Class } from "../di";
|
|
2
2
|
import { registerModuleDescriber } from "../kernel/report/moduleTree";
|
|
3
|
+
import { moduleToolsOf, type DefinedTool, type ToolSet } from "./defineTool";
|
|
3
4
|
import { toolMetadataOf } from "./metadata";
|
|
4
5
|
|
|
5
6
|
// The module tree of the startup screen: `orders.summary · read`.
|
|
6
|
-
registerModuleDescriber((metadata) => (metadata.tools
|
|
7
|
+
registerModuleDescriber((metadata) => moduleToolsOf(metadata.tools).map((tool) => {
|
|
7
8
|
const meta = toolMetadataOf(tool);
|
|
8
9
|
return { kind: "tool" as const, text: meta === undefined ? tool.name : `${meta.name} · ${meta.sideEffect}` };
|
|
9
10
|
}));
|
|
10
11
|
|
|
11
12
|
export { Tool } from "./decorators/Tool";
|
|
13
|
+
export { defineTool, isDefinedTool, toolNameOf, type DefinedTool, type DefinedToolOptions, type ToolSet } from "./defineTool";
|
|
12
14
|
export { describeTool } from "./Tool.contract";
|
|
13
15
|
export type {
|
|
14
16
|
AgentToolHookKindV1, AgentToolHookRefV1, AgentToolRequiredHookRefV1, AgentToolHookRegistrationBaseV1,
|
|
@@ -149,8 +151,11 @@ export {
|
|
|
149
151
|
// depend on program file order and silently stop merging in some projects.
|
|
150
152
|
declare module "../di/module/types/BazisModule" {
|
|
151
153
|
interface BazisModuleMetadata {
|
|
152
|
-
/**
|
|
153
|
-
|
|
154
|
+
/**
|
|
155
|
+
* The Tools of the module: classes with `@Tool`, Tools made by `defineTool`
|
|
156
|
+
* and sets of them (`ormEntityTools(...)`). An agent definition names them in `toolNames`.
|
|
157
|
+
*/
|
|
158
|
+
readonly tools?: readonly (Class<object> | DefinedTool | ToolSet)[];
|
|
154
159
|
readonly agentToolHooks?: readonly import("./AgentToolHooks").AgentToolHookRegistrationV1[];
|
|
155
160
|
}
|
|
156
161
|
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type BazisModuleRef,
|
|
12
12
|
} from "../di";
|
|
13
13
|
import { AgentSetupError } from "./errors";
|
|
14
|
+
import { moduleToolsOf } from "./defineTool";
|
|
14
15
|
import { toolMetadataOf } from "./metadata";
|
|
15
16
|
import type { AgentToolHookRegistrationV1 } from "./AgentToolHooks";
|
|
16
17
|
|
|
@@ -25,18 +26,10 @@ const HOOK_CHANNEL = createModuleOwnedProviderChannel<AgentOwnedHookContribution
|
|
|
25
26
|
// Internal imports (including generatedRuntime's type-only AgentRegistry import)
|
|
26
27
|
// must not depend on the public barrel having augmented DI's metadata interface.
|
|
27
28
|
interface AgentModuleFields {
|
|
28
|
-
readonly tools?: readonly
|
|
29
|
+
readonly tools?: readonly unknown[];
|
|
29
30
|
readonly agentToolHooks?: readonly AgentToolHookRegistrationV1[];
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
function classValues(value: unknown, field: string): readonly Class<object>[] {
|
|
33
|
-
if (value === undefined) return [];
|
|
34
|
-
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "function")) {
|
|
35
|
-
throw new AgentSetupError(`${field} must be an array of classes.`);
|
|
36
|
-
}
|
|
37
|
-
return value as readonly Class<object>[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
33
|
export function assertAgentToolHookIdV1(id: unknown): asserts id is string {
|
|
41
34
|
if (typeof id !== "string" || !/^[\x20-\x7e]{1,128}$/.test(id)) {
|
|
42
35
|
throw new AgentSetupError("Agent Tool hook id must contain 1 to 128 printable ASCII characters.");
|
|
@@ -59,7 +52,7 @@ function assertHook(registration: AgentToolHookRegistrationV1): void {
|
|
|
59
52
|
registerModuleOwnedProviderContributor((metadata: BazisModuleMetadata, context) => {
|
|
60
53
|
const owner = metadata as BazisModuleRef;
|
|
61
54
|
const fields = metadata as BazisModuleMetadata & AgentModuleFields;
|
|
62
|
-
for (const target of
|
|
55
|
+
for (const target of moduleToolsOf(fields.tools)) {
|
|
63
56
|
const tool = toolMetadataOf(target);
|
|
64
57
|
if (!tool) throw new AgentSetupError(`${target.name || "<anonymous class>"} is listed as a tool but is not decorated with @Tool(...).`);
|
|
65
58
|
if (tool.callers.length === 0) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Column, DbContext, Entity, Index, Key } from "../../../library/orm";
|
|
1
|
+
import { Column, DbContext, Entity, Index, Key, Nullable } from "../../../library/orm";
|
|
2
2
|
|
|
3
3
|
/** Immutable v1 schema. These tables are owned exclusively by AgentSessionDbContext. */
|
|
4
4
|
@Entity({ table: "bazis_agent_sessions_v1" })
|
|
@@ -6,42 +6,42 @@ import { Column, DbContext, Entity, Index, Key } from "../../../library/orm";
|
|
|
6
6
|
@Index(["sessionId"], { unique: true })
|
|
7
7
|
export class AgentSessionRowV1 {
|
|
8
8
|
@Key({ generated: false }) @Column({ type: "uuid" }) id = "";
|
|
9
|
-
@Column({ type: "uuid" }) sessionId = "";
|
|
10
|
-
@Column({ type: "uuid" }) invocationId = "";
|
|
11
|
-
@Column({ type: "text" }) createKey = "";
|
|
12
|
-
@Column({ type: "text" }) requestBinding = "";
|
|
13
|
-
@Column({ type: "text" }) ownerSubject = "";
|
|
14
|
-
@Column({ type: "text"
|
|
15
|
-
@Column({ type: "text" }) agentName = "";
|
|
16
|
-
@Column({ type: "text" }) implementationVersion = "";
|
|
17
|
-
@Column({ type: "text" }) bindingHash = "";
|
|
18
|
-
@Column({ type: "text" }) state = "created";
|
|
19
|
-
@Column({ type: "integer" }) revision = 0;
|
|
20
|
-
@Column({ type: "integer" }) steps = 0;
|
|
21
|
-
@Column({ type: "boolean" }) stopRequested = false;
|
|
22
|
-
@Column({ type: "integer" }) checkpointRevision = 0;
|
|
23
|
-
@Column({ type: "text"
|
|
24
|
-
@Column({ type: "integer" }) ownerEpoch = 0;
|
|
25
|
-
@Column({ type: "datetime"
|
|
26
|
-
@Column({ type: "text"
|
|
27
|
-
@Column({ type: "datetime"
|
|
28
|
-
@Column({ type: "integer" }) nextSequence = 1;
|
|
29
|
-
@Column({ type: "integer"
|
|
30
|
-
@Column({ type: "boolean" }) admissionReserved = false;
|
|
31
|
-
@Column({ type: "datetime" }) createdAt = new Date();
|
|
32
|
-
@Column({ type: "datetime" }) updatedAt = new Date();
|
|
9
|
+
@Nullable() @Column({ type: "uuid" }) sessionId = "";
|
|
10
|
+
@Nullable() @Column({ type: "uuid" }) invocationId = "";
|
|
11
|
+
@Nullable() @Column({ type: "text" }) createKey = "";
|
|
12
|
+
@Nullable() @Column({ type: "text" }) requestBinding = "";
|
|
13
|
+
@Nullable() @Column({ type: "text" }) ownerSubject = "";
|
|
14
|
+
@Nullable() @Column({ type: "text" }) tenant?: string;
|
|
15
|
+
@Nullable() @Column({ type: "text" }) agentName = "";
|
|
16
|
+
@Nullable() @Column({ type: "text" }) implementationVersion = "";
|
|
17
|
+
@Nullable() @Column({ type: "text" }) bindingHash = "";
|
|
18
|
+
@Nullable() @Column({ type: "text" }) state = "created";
|
|
19
|
+
@Nullable() @Column({ type: "integer" }) revision = 0;
|
|
20
|
+
@Nullable() @Column({ type: "integer" }) steps = 0;
|
|
21
|
+
@Nullable() @Column({ type: "boolean" }) stopRequested = false;
|
|
22
|
+
@Nullable() @Column({ type: "integer" }) checkpointRevision = 0;
|
|
23
|
+
@Nullable() @Column({ type: "text" }) ownerId?: string;
|
|
24
|
+
@Nullable() @Column({ type: "integer" }) ownerEpoch = 0;
|
|
25
|
+
@Nullable() @Column({ type: "datetime" }) ownerLeaseUntil?: Date;
|
|
26
|
+
@Nullable() @Column({ type: "text" }) runCommandId?: string;
|
|
27
|
+
@Nullable() @Column({ type: "datetime" }) runDeadline?: Date;
|
|
28
|
+
@Nullable() @Column({ type: "integer" }) nextSequence = 1;
|
|
29
|
+
@Nullable() @Column({ type: "integer" }) pendingIntentSequence?: number;
|
|
30
|
+
@Nullable() @Column({ type: "boolean" }) admissionReserved = false;
|
|
31
|
+
@Nullable() @Column({ type: "datetime" }) createdAt = new Date();
|
|
32
|
+
@Nullable() @Column({ type: "datetime" }) updatedAt = new Date();
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
@Entity({ table: "bazis_agent_session_checkpoints_v1" })
|
|
36
36
|
@Index(["sessionId", "checkpointRevision"], { unique: true })
|
|
37
37
|
export class AgentSessionCheckpointRowV1 {
|
|
38
38
|
@Key({ generated: false }) @Column({ type: "uuid" }) id = "";
|
|
39
|
-
@Column({ type: "uuid" }) sessionId = "";
|
|
40
|
-
@Column({ type: "integer" }) checkpointRevision = 0;
|
|
41
|
-
@Column({ type: "text" }) bindingHash = "";
|
|
42
|
-
@Column({ type: "text" }) keyId = "";
|
|
43
|
-
@Column({ type: "text" }) ciphertext = "";
|
|
44
|
-
@Column({ type: "datetime" }) createdAt = new Date();
|
|
39
|
+
@Nullable() @Column({ type: "uuid" }) sessionId = "";
|
|
40
|
+
@Nullable() @Column({ type: "integer" }) checkpointRevision = 0;
|
|
41
|
+
@Nullable() @Column({ type: "text" }) bindingHash = "";
|
|
42
|
+
@Nullable() @Column({ type: "text" }) keyId = "";
|
|
43
|
+
@Nullable() @Column({ type: "text" }) ciphertext = "";
|
|
44
|
+
@Nullable() @Column({ type: "datetime" }) createdAt = new Date();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
@Entity({ table: "bazis_agent_session_journal_v1" })
|
|
@@ -49,26 +49,26 @@ export class AgentSessionCheckpointRowV1 {
|
|
|
49
49
|
@Index(["sessionId", "commandId"], { unique: false })
|
|
50
50
|
export class AgentSessionJournalRowV1 {
|
|
51
51
|
@Key({ generated: false }) @Column({ type: "uuid" }) id = "";
|
|
52
|
-
@Column({ type: "uuid" }) eventId = "";
|
|
53
|
-
@Column({ type: "uuid" }) sessionId = "";
|
|
54
|
-
@Column({ type: "integer" }) sequence = 0;
|
|
55
|
-
@Column({ type: "integer" }) aggregateRevision = 0;
|
|
56
|
-
@Column({ type: "text" }) kind = "";
|
|
57
|
-
@Column({ type: "text"
|
|
58
|
-
@Column({ type: "text" }) projectionJson = "{}";
|
|
59
|
-
@Column({ type: "text"
|
|
60
|
-
@Column({ type: "datetime" }) occurredAt = new Date();
|
|
52
|
+
@Nullable() @Column({ type: "uuid" }) eventId = "";
|
|
53
|
+
@Nullable() @Column({ type: "uuid" }) sessionId = "";
|
|
54
|
+
@Nullable() @Column({ type: "integer" }) sequence = 0;
|
|
55
|
+
@Nullable() @Column({ type: "integer" }) aggregateRevision = 0;
|
|
56
|
+
@Nullable() @Column({ type: "text" }) kind = "";
|
|
57
|
+
@Nullable() @Column({ type: "text" }) commandId?: string;
|
|
58
|
+
@Nullable() @Column({ type: "text" }) projectionJson = "{}";
|
|
59
|
+
@Nullable() @Column({ type: "text" }) binding?: string;
|
|
60
|
+
@Nullable() @Column({ type: "datetime" }) occurredAt = new Date();
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
@Entity({ table: "bazis_agent_session_admission_v1" })
|
|
64
64
|
@Index(["bucket"], { unique: true })
|
|
65
65
|
export class AgentSessionAdmissionRowV1 {
|
|
66
66
|
@Key({ generated: false }) @Column({ type: "uuid" }) id = "";
|
|
67
|
-
@Column({ type: "text" }) bucket = "";
|
|
68
|
-
@Column({ type: "text" }) configHash = "";
|
|
69
|
-
@Column({ type: "integer" }) maxActiveRuns = 8;
|
|
70
|
-
@Column({ type: "integer" }) activeReservations = 0;
|
|
71
|
-
@Column({ type: "datetime" }) updatedAt = new Date();
|
|
67
|
+
@Nullable() @Column({ type: "text" }) bucket = "";
|
|
68
|
+
@Nullable() @Column({ type: "text" }) configHash = "";
|
|
69
|
+
@Nullable() @Column({ type: "integer" }) maxActiveRuns = 8;
|
|
70
|
+
@Nullable() @Column({ type: "integer" }) activeReservations = 0;
|
|
71
|
+
@Nullable() @Column({ type: "datetime" }) updatedAt = new Date();
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export class AgentSessionDbContext extends DbContext {
|
package/core/app/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Composition layer: the single application entry point. Depends on the kernel, HTTP, gRPC and
|
|
2
2
|
// the validation library (it is the composition root, so it may use everything), hiding
|
|
3
3
|
// infrastructure wiring from application code.
|
|
4
|
-
export { runApp, type
|
|
4
|
+
export { App, runApp, type AppClass, type AppOptions, type AppSettings } from "./runApp";
|
|
5
5
|
export type { AppLanguage, AppMessages } from "./messages";
|
|
6
6
|
export * from "./ui";
|
package/core/app/messages.ts
CHANGED
|
@@ -35,7 +35,7 @@ const CATALOGS: Readonly<Record<AppLanguage, Readonly<Record<string, string>>>>
|
|
|
35
35
|
*/
|
|
36
36
|
export function createLocalize(language: AppLanguage = "en", messages: AppMessages = {}): Localize {
|
|
37
37
|
if (!Object.prototype.hasOwnProperty.call(CATALOGS, language)) {
|
|
38
|
-
throw new KernelError(
|
|
38
|
+
throw new KernelError(`@App: language "${String(language)}" is not supported. Use one of: ${Object.keys(CATALOGS).join(", ")}.`);
|
|
39
39
|
}
|
|
40
40
|
const catalog = CATALOGS[language];
|
|
41
41
|
const own = messages as Readonly<Record<string, string | undefined>>;
|
|
@@ -62,9 +62,9 @@ export function createLanguages(option: AppLanguage | AppLanguageOptions = "en",
|
|
|
62
62
|
return { negotiated: false, select: () => single };
|
|
63
63
|
}
|
|
64
64
|
const supported = [...new Set(option.supported)];
|
|
65
|
-
if (supported.length === 0) throw new KernelError("
|
|
65
|
+
if (supported.length === 0) throw new KernelError("@App: language.supported needs at least one language.");
|
|
66
66
|
if (!supported.includes(option.default)) {
|
|
67
|
-
throw new KernelError(
|
|
67
|
+
throw new KernelError(`@App: language.default "${String(option.default)}" must be one of language.supported (${supported.join(", ")}).`);
|
|
68
68
|
}
|
|
69
69
|
const selected = new Map<string, SelectedLanguage>(supported.map((language) => [language, { language, localize: createLocalize(language, messages) }]));
|
|
70
70
|
const fallback = selected.get(option.default)!;
|