bazis 0.98.33 → 0.99.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/cli/templates/module.ts +4 -64
- package/cli/templates/passport.ts +14 -18
- package/core/agent/AgentContextBuilder.ts +4 -98
- package/core/agent/AgentRegistry.ts +79 -473
- package/core/agent/AgentRuntime.ts +47 -19
- package/core/agent/AgentToolExecutor.ts +3 -3
- package/core/agent/README.md +89 -181
- package/core/agent/index.ts +3 -31
- package/core/agent/internal/AgentExecutionDriver.ts +37 -392
- package/core/agent/internal/AgentExecutionState.ts +1 -9
- package/core/agent/metadata.ts +8 -303
- package/core/agent/semantic.ts +0 -4
- package/core/agent/session/contracts.ts +5 -7
- package/core/agent/session/entities.ts +0 -1
- package/core/cache/COMPATIBILITY.md +4 -4
- package/core/cache/README.md +2 -2
- package/core/cache/SPEC.md +30 -48
- package/core/cache/decorators/Cacheable.ts +4 -3
- package/core/cache/decorators/CacheableRedis.ts +3 -4
- package/core/cache/di/classProviderHook.ts +31 -0
- package/core/cache/distributed/DistributedCache.ts +96 -25
- package/core/cache/distributed/IDistributedCache.ts +9 -0
- package/core/cache/distributed/codecs.ts +49 -2
- package/core/cache/distributed/index.ts +1 -1
- package/core/cache/errors/CacheError.ts +17 -2
- package/core/cache/http/outputRedisCacheMiddleware.ts +11 -2
- package/core/cache/index.ts +2 -6
- package/core/cache/services/cacheProxy.ts +6 -2
- package/core/generatedRuntime.ts +0 -31
- package/core/infra/cache/RedisDistributedCacheBackend.ts +3 -0
- package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
- package/core/infra/connectors/llm.ts +15 -1
- package/core/infra/connectors/llmRouter.ts +4 -0
- package/core/infra/connectors/redis.ts +9 -1
- package/core/scripts/di-generate.ts +28 -42
- package/core/scripts/tool-codegen.ts +122 -0
- package/index.ts +3 -24
- package/package.json +1 -1
- package/core/agent/decorators/Agent.ts +0 -11
- package/core/agent/decorators/Prompt.ts +0 -11
- package/core/agent/decorators/Task.ts +0 -29
- package/core/agent/generated/catalog.ts +0 -26
- package/core/scripts/agent-codegen.ts +0 -1166
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { DiError } from "../../di";
|
|
1
2
|
/** Base error of the cache module. */
|
|
2
3
|
export class CacheError extends Error {
|
|
3
|
-
public constructor(message: string) {
|
|
4
|
-
super(message);
|
|
4
|
+
public constructor(message: string, options?: ErrorOptions) {
|
|
5
|
+
super(message, options);
|
|
5
6
|
this.name = new.target.name;
|
|
6
7
|
}
|
|
7
8
|
}
|
|
@@ -12,5 +13,19 @@ export class CacheKeyError extends CacheError {}
|
|
|
12
13
|
/** Invalid value (size limit exceeded and the like). */
|
|
13
14
|
export class CacheValueError extends CacheError {}
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* The distributed store did not answer in time or failed. On the cache-aside
|
|
18
|
+
* path (`getOrCreateAsync`, `@CacheableRedis`) the value is computed without
|
|
19
|
+
* the cache; explicit operations (`get`, `remove`, `evictByTag`) throw it.
|
|
20
|
+
*/
|
|
21
|
+
export class CacheUnavailableError extends CacheError {}
|
|
22
|
+
|
|
15
23
|
/** The limit of unfinished factories is exhausted; the new factory was not started. */
|
|
16
24
|
export class CacheCapacityError extends CacheError {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A cache declaration that can never work, found while the container is built
|
|
28
|
+
* (a cached scoped method without a key, a cached transient service). A DI
|
|
29
|
+
* error, so the start prints it as a configuration error.
|
|
30
|
+
*/
|
|
31
|
+
export class CacheDeclarationError extends DiError {}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HttpMiddleware } from "../../http";
|
|
2
|
-
import { CacheError } from "../errors/CacheError";
|
|
2
|
+
import { CacheError, CacheUnavailableError } from "../errors/CacheError";
|
|
3
3
|
import { DISTRIBUTED_OUTPUT_CACHE } from "../tokens/DISTRIBUTED_CACHE";
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_OUTPUT_CACHE_METHODS,
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
resolveCachedHttpPayloadReadOptions,
|
|
15
15
|
responseAllowsServerCache,
|
|
16
16
|
responseToCachedPayload,
|
|
17
|
+
type CachedHttpPayload,
|
|
17
18
|
} from "./CachedHttpPayload";
|
|
18
19
|
|
|
19
20
|
export interface OutputRedisCacheMiddlewareOptions {
|
|
@@ -81,7 +82,15 @@ export function outputRedisCacheMiddleware(options: OutputRedisCacheMiddlewareOp
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
const key = buildRedisOutputCacheKey(ctx, routeName, config);
|
|
84
|
-
|
|
85
|
+
let cached: CachedHttpPayload | undefined;
|
|
86
|
+
try {
|
|
87
|
+
cached = await cache.get(key);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// The cache is optional: an unavailable store serves the route uncached.
|
|
90
|
+
if (!(error instanceof CacheUnavailableError)) throw error;
|
|
91
|
+
await next();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
85
94
|
if (cached !== undefined) {
|
|
86
95
|
ctx.response = applyClientCacheHeaders(cachedPayloadToResponse(cached, { hit: true }), clientCache);
|
|
87
96
|
return;
|
package/core/cache/index.ts
CHANGED
|
@@ -51,7 +51,7 @@ export {
|
|
|
51
51
|
type DistributedCacheVersionCheck,
|
|
52
52
|
type DistributedCachePing,
|
|
53
53
|
type DistributedCacheSetOptions,
|
|
54
|
-
|
|
54
|
+
IDistributedCache,
|
|
55
55
|
} from "./distributed";
|
|
56
56
|
export {
|
|
57
57
|
DISTRIBUTED_CACHE_BACKEND,
|
|
@@ -66,7 +66,7 @@ export {
|
|
|
66
66
|
type CacheModuleConfig,
|
|
67
67
|
type CacheOutputCacheOptions,
|
|
68
68
|
} from "./internal/buildCacheProviders";
|
|
69
|
-
export { CacheError, CacheKeyError, CacheValueError, CacheCapacityError } from "./errors/CacheError";
|
|
69
|
+
export { CacheError, CacheKeyError, CacheValueError, CacheCapacityError, CacheDeclarationError, CacheUnavailableError } from "./errors/CacheError";
|
|
70
70
|
|
|
71
71
|
export { OutputCache, type OutputCacheOptions as OutputCacheDecoratorOptions } from "./decorators/OutputCache";
|
|
72
72
|
export {
|
|
@@ -91,7 +91,3 @@ export {
|
|
|
91
91
|
} from "./internal/resolveCachePolicy";
|
|
92
92
|
export { buildOutputCacheKey, OUTPUT_CACHE_PRINCIPAL_STATE_KEY } from "./http/buildOutputCacheKey";
|
|
93
93
|
export { wrapCachedService, type CacheProxyOptions } from "./services/cacheProxy";
|
|
94
|
-
export {
|
|
95
|
-
cachedSingleton,
|
|
96
|
-
cachedScoped,
|
|
97
|
-
} from "./providers/cachedProviders";
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "../decorators/cacheableRedisMetadata";
|
|
12
12
|
import type { IDistributedCache } from "../distributed/IDistributedCache";
|
|
13
13
|
import type { NamedCacheRegistry } from "../distributed/NamedCacheRegistry";
|
|
14
|
-
import { CacheError } from "../errors/CacheError";
|
|
14
|
+
import { CacheError, CacheValueError } from "../errors/CacheError";
|
|
15
15
|
import type { ICache } from "../ICache";
|
|
16
16
|
import { normalizeCacheKey } from "../internal/normalizeCacheKey";
|
|
17
17
|
import { AutomaticCacheKey } from "../internal/AutomaticCacheKey";
|
|
@@ -141,7 +141,11 @@ function wrapDistributedMethod(
|
|
|
141
141
|
return result instanceof Promise ? await result : result;
|
|
142
142
|
},
|
|
143
143
|
{ ttlSeconds: options.seconds, tags: options.tags, lockSeconds: options.lockSeconds },
|
|
144
|
-
)
|
|
144
|
+
).catch((error: unknown) => {
|
|
145
|
+
// Name the method: "value.at is a Date" alone does not say where.
|
|
146
|
+
if (error instanceof CacheValueError) throw new CacheValueError(`${className}.${String(methodName)}: ${error.message}`, { cause: error });
|
|
147
|
+
throw error;
|
|
148
|
+
});
|
|
145
149
|
};
|
|
146
150
|
}
|
|
147
151
|
|
package/core/generatedRuntime.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import type { AgentMetadataIndex } from "./agent/AgentRegistry";
|
|
3
2
|
import { registerGeneratedClassDeps, restoreGeneratedClassDeps, snapshotGeneratedClassDeps, type GeneratedClassDependency } from "./di/module/autoDeps";
|
|
4
3
|
import { registerGeneratedBindings, restoreGeneratedBindings, snapshotGeneratedBindings } from "./http/Binding/autoBindings";
|
|
5
4
|
import { registerRequestModelShape, restoreRequestModelRegistry, snapshotRequestModelRegistry, type RequestModelClass, type RequestModelShape } from "./http/Binding/requestModelRegistry";
|
|
@@ -27,10 +26,6 @@ type GeneratedRuntimeModule = {
|
|
|
27
26
|
readonly registerBazisGeneratedRuntime?: () => void | Promise<void>;
|
|
28
27
|
};
|
|
29
28
|
|
|
30
|
-
type GeneratedAgentCatalogModule = {
|
|
31
|
-
readonly GENERATED_AGENT_METADATA?: AgentMetadataIndex;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
29
|
let runtimeLoad: Promise<void> | undefined;
|
|
35
30
|
|
|
36
31
|
export interface BazisGeneratedTargetDescriptor {
|
|
@@ -42,15 +37,12 @@ export interface BazisGeneratedTargetDescriptor {
|
|
|
42
37
|
readonly listModels: readonly ListModelClass[];
|
|
43
38
|
readonly openApi: GeneratedOpenApiMetadata;
|
|
44
39
|
readonly openApiSchemaModels: readonly (readonly [GeneratedOpenApiSchemaModel, string])[];
|
|
45
|
-
/** Target-local generated Agent/Tool/Prompt metadata. */
|
|
46
|
-
readonly agentMetadata?: AgentMetadataIndex;
|
|
47
40
|
/** Package-internal exact-class metadata attached atomically with this target. */
|
|
48
41
|
readonly providerAttachments?: readonly GeneratedProviderAttachmentV1[];
|
|
49
42
|
}
|
|
50
43
|
|
|
51
44
|
const committedTargetDescriptors = new Set<string>();
|
|
52
45
|
const targetDescriptorLoads = new Map<string, Promise<void>>();
|
|
53
|
-
const committedTargetDescriptorsById = new Map<string, BazisGeneratedTargetDescriptor>();
|
|
54
46
|
const explicitlyActivatedTargetIds = new Set<string>();
|
|
55
47
|
|
|
56
48
|
/**
|
|
@@ -87,7 +79,6 @@ export function registerBazisGeneratedTargetDescriptor(descriptor: BazisGenerate
|
|
|
87
79
|
for (const [model, name] of descriptor.openApiSchemaModels) registerGeneratedOpenApiSchemaModel(model, name);
|
|
88
80
|
commitGeneratedProviderAttachments(preparedAttachments);
|
|
89
81
|
committedTargetDescriptors.add(descriptor.id);
|
|
90
|
-
committedTargetDescriptorsById.set(descriptor.id, descriptor);
|
|
91
82
|
explicitlyActivatedTargetIds.add(descriptor.id);
|
|
92
83
|
} catch (error) {
|
|
93
84
|
restoreGeneratedClassDeps(snapshots.classDeps);
|
|
@@ -136,28 +127,6 @@ export function loadBazisGeneratedRuntime(): Promise<void> {
|
|
|
136
127
|
return runtimeLoad;
|
|
137
128
|
}
|
|
138
129
|
|
|
139
|
-
export async function loadBazisGeneratedAgentMetadata(
|
|
140
|
-
targets: readonly Class<object>[] = [],
|
|
141
|
-
): Promise<AgentMetadataIndex | undefined> {
|
|
142
|
-
const registered = selectRegisteredAgentMetadata(targets);
|
|
143
|
-
if (registered !== undefined || explicitlyActivatedTargetIds.size > 0) return registered;
|
|
144
|
-
const module = await importGeneratedModule<GeneratedAgentCatalogModule>("agentCatalog");
|
|
145
|
-
return module?.GENERATED_AGENT_METADATA;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function selectRegisteredAgentMetadata(targets: readonly Class<object>[]): AgentMetadataIndex | undefined {
|
|
149
|
-
const candidates = [...committedTargetDescriptorsById.values()]
|
|
150
|
-
.map((descriptor) => descriptor.agentMetadata)
|
|
151
|
-
.filter((metadata): metadata is AgentMetadataIndex => metadata !== undefined);
|
|
152
|
-
if (candidates.length === 0) return undefined;
|
|
153
|
-
for (const metadata of candidates) {
|
|
154
|
-
for (const target of targets) {
|
|
155
|
-
if (metadata.agents.has(target) || metadata.tools.has(target) || metadata.prompts.has(target)) return metadata;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return candidates.length === 1 ? candidates[0] : undefined;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
130
|
async function loadRuntimeOnce(): Promise<void> {
|
|
162
131
|
const module = await importGeneratedModule<GeneratedRuntimeModule>("runtime");
|
|
163
132
|
await module?.registerBazisGeneratedRuntime?.();
|
|
@@ -25,6 +25,8 @@ export interface RedisDistributedCacheTuning {
|
|
|
25
25
|
readonly maxValueBytes?: number;
|
|
26
26
|
/** Polling interval (ms) while waiting for another worker's result. */
|
|
27
27
|
readonly pollIntervalMs?: number;
|
|
28
|
+
/** Bound of one Redis operation (ms); on expiry the method runs without the cache. Default 1000. */
|
|
29
|
+
readonly operationTimeoutMs?: number;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
const DEFAULT_KEY_PREFIX = "bazis:cache:";
|
|
@@ -59,6 +61,7 @@ export class RedisDistributedCacheBackend implements DistributedCacheStores {
|
|
|
59
61
|
maxKeyLength: tuning.maxKeyLength,
|
|
60
62
|
maxValueBytes: tuning.maxValueBytes,
|
|
61
63
|
pollIntervalMs: tuning.pollIntervalMs,
|
|
64
|
+
operationTimeoutMs: tuning.operationTimeoutMs,
|
|
62
65
|
};
|
|
63
66
|
|
|
64
67
|
const output = new DistributedCache<CachedHttpPayload>(driver, httpPayloadCacheCodec, {
|
|
@@ -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.
|
|
235
|
+
clientInfo: { name: "bazis", title: "Bazis", version: "0.99.0" }, 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,5 +1,6 @@
|
|
|
1
1
|
import type { AppConfig, ConfigRegistry, Secret } from "../../kernel";
|
|
2
|
-
import { AGENT_MODEL_PROVIDER, type AgentModelProvider } from "../../agent";
|
|
2
|
+
import { AGENT_MODEL_PROVIDER, AGENT_RUNTIME_OPTIONS, AgentRuntime, type AgentModelProvider } from "../../agent";
|
|
3
|
+
import { DI, SERVICE_PROVIDER, type ProviderDefinition } from "../../di";
|
|
3
4
|
import { reader, requireValue } from "../connectorConfig";
|
|
4
5
|
import { InfraError, type InfraConnector } from "../InfraConnector";
|
|
5
6
|
import { identifyConnector } from "../connectorIdentity";
|
|
@@ -54,6 +55,17 @@ export function resolveLlmConnectionOptions<T extends LlmConfigShape>(
|
|
|
54
55
|
};
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* `AgentRuntime` on top of the connector's model provider, injectable by type.
|
|
60
|
+
* Options come from `AGENT_RUNTIME_OPTIONS` when the application registers it.
|
|
61
|
+
*/
|
|
62
|
+
export const agentRuntimeProviders: readonly ProviderDefinition[] = Object.freeze([
|
|
63
|
+
DI.singleton(DI.factoryProviderWithResolver(AgentRuntime, [AGENT_MODEL_PROVIDER], (resolver, model: AgentModelProvider) => {
|
|
64
|
+
const services = resolver.resolve(SERVICE_PROVIDER);
|
|
65
|
+
return new AgentRuntime(services, model, services.has(AGENT_RUNTIME_OPTIONS) ? services.resolve(AGENT_RUNTIME_OPTIONS) : {});
|
|
66
|
+
})),
|
|
67
|
+
]);
|
|
68
|
+
|
|
57
69
|
export function llmConnect<T extends LlmConfigShape>(
|
|
58
70
|
config: AppConfig<T>,
|
|
59
71
|
adapter: LlmProviderAdapter,
|
|
@@ -62,6 +74,8 @@ export function llmConnect<T extends LlmConfigShape>(
|
|
|
62
74
|
return identifyConnector({
|
|
63
75
|
token: AGENT_MODEL_PROVIDER,
|
|
64
76
|
config,
|
|
77
|
+
providers: agentRuntimeProviders,
|
|
78
|
+
exports: [AgentRuntime],
|
|
65
79
|
create(configs) {
|
|
66
80
|
return adapter.create(resolveLlmConnectionOptions(config, options, configs));
|
|
67
81
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AppConfig } from "../../kernel";
|
|
2
2
|
import {
|
|
3
3
|
AGENT_MODEL_PROVIDER,
|
|
4
|
+
AgentRuntime,
|
|
4
5
|
agentModelRequest,
|
|
5
6
|
type AgentModelProvider,
|
|
6
7
|
type AgentModelProviderContext,
|
|
@@ -11,6 +12,7 @@ import { InfraError, type InfraConnector } from "../InfraConnector";
|
|
|
11
12
|
import { redactSensitiveText } from "../../../library/redaction";
|
|
12
13
|
import { awaitAbortable } from "../../kernel/internal/awaitAbortable";
|
|
13
14
|
import {
|
|
15
|
+
agentRuntimeProviders,
|
|
14
16
|
resolveLlmConnectionOptions,
|
|
15
17
|
type LlmConfigShape,
|
|
16
18
|
type LlmConnectionOptions,
|
|
@@ -408,6 +410,8 @@ export function llmRouter(
|
|
|
408
410
|
return {
|
|
409
411
|
token: AGENT_MODEL_PROVIDER,
|
|
410
412
|
config: configs,
|
|
413
|
+
providers: agentRuntimeProviders,
|
|
414
|
+
exports: [AgentRuntime],
|
|
411
415
|
create(configs) {
|
|
412
416
|
// The router must have an owner before adapters allocate resources.
|
|
413
417
|
// Initialization happens inside an awaited operation, so rollback can
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
DISTRIBUTED_CACHE_BACKEND,
|
|
4
4
|
DISTRIBUTED_OUTPUT_CACHE,
|
|
5
5
|
DISTRIBUTED_SERVICE_CACHE,
|
|
6
|
+
IDistributedCache,
|
|
6
7
|
type DistributedCacheStores,
|
|
7
8
|
} from "../../cache";
|
|
8
9
|
import { createToken, DI, type InjectionToken, type ProviderDefinition } from "../../di";
|
|
@@ -60,6 +61,13 @@ function distributedCacheProviders(tuning: RedisDistributedCacheTuning, token: I
|
|
|
60
61
|
backend.serviceCache,
|
|
61
62
|
),
|
|
62
63
|
),
|
|
64
|
+
// This connector's one connection by type, for services that evict tags or
|
|
65
|
+
// read values; its name may be "default" or the configured `connection`.
|
|
66
|
+
DI.singleton(
|
|
67
|
+
DI.factoryProvider(IDistributedCache, [DISTRIBUTED_CACHE_BACKEND], (backend: DistributedCacheStores) =>
|
|
68
|
+
backend.serviceCache.resolve(backend.serviceCache.connectionNames[0]),
|
|
69
|
+
),
|
|
70
|
+
),
|
|
63
71
|
];
|
|
64
72
|
}
|
|
65
73
|
|
|
@@ -91,7 +99,7 @@ export function redisConnect<T extends RedisConfigShape>(
|
|
|
91
99
|
token,
|
|
92
100
|
config,
|
|
93
101
|
providers: cacheProviders,
|
|
94
|
-
exports: cacheProviders ? [DISTRIBUTED_CACHE_BACKEND, DISTRIBUTED_OUTPUT_CACHE, DISTRIBUTED_SERVICE_CACHE] : undefined,
|
|
102
|
+
exports: cacheProviders ? [DISTRIBUTED_CACHE_BACKEND, DISTRIBUTED_OUTPUT_CACHE, DISTRIBUTED_SERVICE_CACHE, IDistributedCache] : undefined,
|
|
95
103
|
create(configs) {
|
|
96
104
|
return new RedisClient(redisUrl(reader(config, configs).get("url")));
|
|
97
105
|
},
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type OpenApiCodegenOperationSpec,
|
|
8
8
|
type OpenApiCodegenSchema,
|
|
9
9
|
} from "../../library/openapi/codegen";
|
|
10
|
-
import {
|
|
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
|
|
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", "
|
|
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,
|
|
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();
|
|
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
|
-
|
|
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
|
|
491
|
-
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
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 +
|
|
509
|
+
candidates: classDeps.size + Object.keys(httpBindings).length + requestModelImports.length + listModelImports.length + tools.toolCount,
|
|
524
510
|
};
|
|
525
511
|
}
|
|
526
512
|
|
|
527
|
-
function
|
|
528
|
-
for (let index = 0; index <
|
|
529
|
-
const warning =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
1352
|
-
function resolveOpenApiSchemaModelImports(
|
|
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
|
|
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
|
|
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(`
|
|
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
|
+
}
|