@jangts/agent-core 0.2.1 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +384 -50
  3. package/development/docs/api/README.en.md +2 -0
  4. package/development/docs/api/README.md +2 -0
  5. package/development/docs/api/decorators.en.md +83 -0
  6. package/development/docs/api/decorators.md +108 -0
  7. package/development/docs/api/reference.md +322 -257
  8. package/development/docs/api/workflow.en.md +10 -0
  9. package/development/docs/api/workflow.md +22 -0
  10. package/development/skills/agent-core-app/SKILL.md +8 -0
  11. package/dist/cjs/execution/cache.js +162 -0
  12. package/dist/cjs/execution/engine.js +10 -0
  13. package/dist/cjs/kits/vector-search.js +4 -7
  14. package/dist/cjs/structs/execution/cache.js +6 -0
  15. package/dist/cjs/support/decorators/definition.js +42 -0
  16. package/dist/cjs/support/decorators/index.js +63 -0
  17. package/dist/cjs/support/decorators/pool.js +87 -0
  18. package/dist/cjs/support/decorators/types.js +2 -0
  19. package/dist/esm/execution/cache.d.ts +20 -0
  20. package/dist/esm/execution/cache.js +156 -0
  21. package/dist/esm/execution/engine.d.ts +5 -0
  22. package/dist/esm/execution/engine.js +10 -0
  23. package/dist/esm/kits/vector-search.js +4 -7
  24. package/dist/esm/structs/execution/cache.d.ts +54 -0
  25. package/dist/esm/structs/execution/cache.js +5 -0
  26. package/dist/esm/structs/execution/engine.d.ts +6 -1
  27. package/dist/esm/structs/execution/workflow.d.ts +2 -1
  28. package/dist/esm/structs/index.d.ts +1 -0
  29. package/dist/esm/support/decorators/definition.d.ts +19 -0
  30. package/dist/esm/support/decorators/definition.js +38 -0
  31. package/dist/esm/support/decorators/index.d.ts +39 -0
  32. package/dist/esm/support/decorators/index.js +48 -0
  33. package/dist/esm/support/decorators/pool.d.ts +97 -0
  34. package/dist/esm/support/decorators/pool.js +83 -0
  35. package/dist/esm/support/decorators/types.d.ts +22 -0
  36. package/dist/esm/support/decorators/types.js +1 -0
  37. package/package.json +11 -3
@@ -0,0 +1,156 @@
1
+ import { awaitWithSignal } from '../utils/cancellation.js';
2
+ /** Validate the small cache-specific boundary; business permissions remain App-owned. */
3
+ function textKey(value) {
4
+ if (typeof value !== 'string' || !value.length)
5
+ throw new TypeError('Cache keys must be nonempty strings');
6
+ return value;
7
+ }
8
+ /** Reject accidental cross-partition writes and unknown options instead of ignoring them. */
9
+ function optionsOf(value, allowed) {
10
+ if (value !== undefined &&
11
+ (!value ||
12
+ typeof value !== 'object' ||
13
+ Reflect.ownKeys(value).some((key) => typeof key !== 'string' || !allowed.includes(key))))
14
+ throw new TypeError('Invalid cache options');
15
+ }
16
+ /** Bounded LRU for one Engine; forks get new memory, and values are deliberately not cloned. */
17
+ export class EngineCache {
18
+ entries = new Map();
19
+ limit;
20
+ ttl;
21
+ constructor(options = {}) {
22
+ this.limit = options.maxEntries ?? 1024;
23
+ this.ttl = options.ttlMs ?? Infinity;
24
+ if (!Number.isSafeInteger(this.limit) ||
25
+ this.limit < 1 ||
26
+ (this.ttl !== Infinity && (!Number.isSafeInteger(this.ttl) || this.ttl < 1)))
27
+ throw new TypeError('Invalid Engine cache bounds');
28
+ }
29
+ /** Remove expired entries lazily, without timers or background tasks. */
30
+ prune() {
31
+ const now = Date.now();
32
+ for (const [key, item] of this.entries)
33
+ if (item.expires <= now)
34
+ this.entries.delete(key);
35
+ }
36
+ /** Capture an immutable partition and visit signal; no per-run cache is introduced. */
37
+ view(nodeId, signal) {
38
+ textKey(nodeId);
39
+ const prefix = JSON.stringify([nodeId]).slice(0, -1) + ',';
40
+ const address = (key, node = nodeId) => JSON.stringify([textKey(node), textKey(key)]);
41
+ const read = (key, node = nodeId) => {
42
+ signal.throwIfAborted();
43
+ const id = address(key, node);
44
+ const item = this.entries.get(id);
45
+ if (item && item.expires <= Date.now()) {
46
+ this.entries.delete(id);
47
+ return undefined;
48
+ }
49
+ if (item) {
50
+ this.entries.delete(id);
51
+ this.entries.set(id, item);
52
+ }
53
+ return item;
54
+ };
55
+ return Object.freeze({
56
+ get: (key, options) => {
57
+ optionsOf(options, ['nodeId']);
58
+ return read(key, options?.nodeId)?.value;
59
+ },
60
+ has: (key) => read(key) !== undefined,
61
+ set: (key, value) => {
62
+ signal.throwIfAborted();
63
+ const id = address(key);
64
+ this.prune();
65
+ this.entries.delete(id);
66
+ this.entries.set(id, { value, expires: Date.now() + this.ttl });
67
+ if (this.entries.size > this.limit)
68
+ this.entries.delete(this.entries.keys().next().value);
69
+ },
70
+ delete: (key) => {
71
+ signal.throwIfAborted();
72
+ return this.entries.delete(address(key));
73
+ },
74
+ clear: () => {
75
+ signal.throwIfAborted();
76
+ for (const key of this.entries.keys())
77
+ if (key.startsWith(prefix))
78
+ this.entries.delete(key);
79
+ },
80
+ });
81
+ }
82
+ }
83
+ /** Snapshot port callbacks with their receiver, rejecting incomplete App storage early. */
84
+ export function cachePort(port) {
85
+ if (port === undefined)
86
+ return undefined;
87
+ const handlers = ['get', 'has', 'set', 'delete', 'clear'];
88
+ if (!port ||
89
+ handlers.some((key) => typeof port[key] !== 'function') ||
90
+ (port.supportsPersistence !== undefined && typeof port.supportsPersistence !== 'boolean'))
91
+ throw new TypeError('Invalid Agent cache port');
92
+ return Object.freeze({
93
+ supportsPersistence: port.supportsPersistence === true,
94
+ get: port.get.bind(port),
95
+ has: port.has.bind(port),
96
+ set: port.set.bind(port),
97
+ delete: port.delete.bind(port),
98
+ clear: port.clear.bind(port),
99
+ });
100
+ }
101
+ /** Build visit-local views. Persistent writes remain external side effects owned by the App. */
102
+ export function cacheContext(memory, port, nodeId, signal) {
103
+ const partition = (options, crossRead = false) => {
104
+ signal.throwIfAborted();
105
+ optionsOf(options, crossRead ? ['nodeId', 'persistence'] : ['persistence']);
106
+ const persistence = options?.persistence ?? false;
107
+ if (typeof persistence !== 'boolean')
108
+ throw new TypeError('Invalid cache persistence');
109
+ if (persistence && !port?.supportsPersistence)
110
+ throw new TypeError('Agent cache persistence is unsupported');
111
+ return Object.freeze({
112
+ nodeId: textKey(crossRead ? (options?.nodeId ?? nodeId) : nodeId),
113
+ persistence,
114
+ signal,
115
+ });
116
+ };
117
+ const address = (key, options, crossRead = false) => Object.freeze({ ...partition(options, crossRead), key: textKey(key) });
118
+ const call = async (invoke) => {
119
+ signal.throwIfAborted();
120
+ const value = await awaitWithSignal(invoke(), signal);
121
+ signal.throwIfAborted();
122
+ return value;
123
+ };
124
+ return {
125
+ cache: memory.view(nodeId, signal),
126
+ ...(port
127
+ ? {
128
+ agentCache: Object.freeze({
129
+ get: (key, options) => call(() => port.get(address(key, options, true))),
130
+ has: (key, options) => call(async () => {
131
+ const result = await port.has(address(key, options));
132
+ if (typeof result !== 'boolean')
133
+ throw new TypeError('Agent cache has must return boolean');
134
+ return result;
135
+ }),
136
+ set: (key, value, options) => call(async () => {
137
+ const result = await port.set(address(key, options), value);
138
+ if (result !== undefined)
139
+ throw new TypeError('Agent cache set must return void');
140
+ }),
141
+ delete: (key, options) => call(async () => {
142
+ const result = await port.delete(address(key, options));
143
+ if (typeof result !== 'boolean')
144
+ throw new TypeError('Agent cache delete must return boolean');
145
+ return result;
146
+ }),
147
+ clear: (options) => call(async () => {
148
+ const result = await port.clear(partition(options));
149
+ if (result !== undefined)
150
+ throw new TypeError('Agent cache clear must return void');
151
+ }),
152
+ }),
153
+ }
154
+ : {}),
155
+ };
156
+ }
@@ -1,3 +1,4 @@
1
+ import type { AgentCachePort } from '../structs/execution/cache.js';
1
2
  import { ModelRunner } from '../model/runner.js';
2
3
  import type { ModelRunnerOptions } from '../structs/model/runtime.js';
3
4
  import type { ToolRegistry } from '../capabilities/tools.js';
@@ -5,6 +6,7 @@ import type { AgentCoreEngineOptions, CoreStep } from '../structs/execution/engi
5
6
  import { AgentWorkflow, AttemptBudget, defineWorkflow } from './workflow.js';
6
7
  import type { WorkflowExtension } from '../structs/execution/workflow.js';
7
8
  export * from './workflow.js';
9
+ export type * from '../structs/execution/cache.js';
8
10
  type InvokeTool = (request: import('../structs/capabilities/tools.js').ToolInvocationRequest) => Promise<import('../structs/invocation.js').ToolResult<unknown>>;
9
11
  type ToolBinding = {
10
12
  invoke: InvokeTool;
@@ -109,6 +111,8 @@ export declare class AgentCoreEngine<Result, State = unknown, App = undefined> {
109
111
  static readonly AttemptBudget: typeof AttemptBudget;
110
112
  /** Factory for a validated workflow definition. */
111
113
  static readonly createWorkflow: typeof defineWorkflow;
114
+ private readonly cacheMemory;
115
+ private readonly serviceCache?;
112
116
  private readonly maxRounds;
113
117
  private readonly exhausted;
114
118
  private readonly options;
@@ -264,6 +268,7 @@ export declare class AgentCoreEngine<Result, State = unknown, App = undefined> {
264
268
  app?: App;
265
269
  tools?: ToolBinding;
266
270
  model?: AgentModelRuntime;
271
+ agentCache?: AgentCachePort;
267
272
  };
268
273
  }): AgentCoreEngine<Result, State, App>;
269
274
  /**
@@ -9,6 +9,7 @@
9
9
  *
10
10
  * Exports: `AgentCoreEngine` and the engine option/identity types.
11
11
  */
12
+ import { EngineCache, cacheContext, cachePort } from './cache.js';
12
13
  import { ModelCallBudget, parentModelBudget } from '../model/call-budget.js';
13
14
  import { ModelRunner } from '../model/runner.js';
14
15
  import { compiledModelBinding, modelBindingId, snapshotModelBinding, } from '../model/binding.js';
@@ -39,6 +40,8 @@ export class AgentCoreEngine {
39
40
  static AttemptBudget = AttemptBudget;
40
41
  /** Factory for a validated workflow definition. */
41
42
  static createWorkflow = defineWorkflow;
43
+ cacheMemory;
44
+ serviceCache;
42
45
  maxRounds;
43
46
  exhausted;
44
47
  options;
@@ -289,6 +292,8 @@ export class AgentCoreEngine {
289
292
  breakWhile(!!this.invokeTool && !dependencies?.tools, 'Fork requires a restored tool runtime');
290
293
  const engine = new AgentCoreEngine({
291
294
  maxRounds: this.maxRounds,
295
+ cache: this.options.cache,
296
+ agentCache: dependencies?.agentCache,
292
297
  errors: { exhausted: this.exhausted },
293
298
  execution: {
294
299
  diagnostics: this.options.execution?.diagnostics,
@@ -333,6 +338,8 @@ export class AgentCoreEngine {
333
338
  ['maxSteps', 'exhausted', 'signal', 'diagnostics', 'onWorkflowEvent', 'observe'].some((key) => key in settings)) {
334
339
  throw new TypeError('Use maxRounds, execution (including observe) and errors');
335
340
  }
341
+ this.cacheMemory = new EngineCache(settings.cache);
342
+ this.serviceCache = cachePort(settings.agentCache);
336
343
  const execution = settings.execution;
337
344
  if (settings.branch !== undefined)
338
345
  this.branchIdentity = snapshotBranch(settings.branch);
@@ -342,6 +349,7 @@ export class AgentCoreEngine {
342
349
  settings.errors?.exhausted?.bind(settings.errors) ??
343
350
  (() => new BudgetError('Agent round budget exhausted'));
344
351
  this.options = {
352
+ cache: { ...settings.cache },
345
353
  execution: {
346
354
  signal: execution?.signal,
347
355
  diagnostics: execution?.diagnostics,
@@ -461,6 +469,7 @@ export class AgentCoreEngine {
461
469
  phase = 'run';
462
470
  signal.throwIfAborted();
463
471
  const result = await awaitWithSignal(step(index, {
472
+ ...cacheContext(this.cacheMemory, this.serviceCache, '$step', signal),
464
473
  signal,
465
474
  diagnostics,
466
475
  report: reports.report.bind(reports),
@@ -656,6 +665,7 @@ export class AgentCoreEngine {
656
665
  });
657
666
  const reports = new NodeReports(nodeDiagnostics, signal);
658
667
  const context = {
668
+ ...cacheContext(this.cacheMemory, this.serviceCache, node.id, signal),
659
669
  report: reports.report.bind(reports),
660
670
  createSubFlowLogger: reports.createSubFlowLogger.bind(reports),
661
671
  roundIndex: round,
@@ -77,11 +77,8 @@ export class VectorSearch {
77
77
  (!Number.isFinite(options.minScore) || options.minScore < -1 || options.minScore > 1)), 'Invalid vector query');
78
78
  const query = normalize(vector, this.space.dimensions), groups = new Map();
79
79
  for (const [id, { document, vector: value }] of this.entries) {
80
- // One isolated snapshot per candidate serves filter, grouping and the
81
- // returned hit; filters and groupers are typed readonly, so sharing it
82
- // removes the redundant clones without exposing the index.
83
- const candidate = structuredClone(document);
84
- const allowed = booleanResult(options.filter(candidate), 'Vector filters must be synchronous', 'Vector filters must return boolean');
80
+ // Each callback receives its own snapshot; readonly types cannot prevent runtime mutation.
81
+ const allowed = booleanResult(options.filter(structuredClone(document)), 'Vector filters must be synchronous', 'Vector filters must return boolean');
85
82
  if (!allowed)
86
83
  continue;
87
84
  let score = 0;
@@ -90,12 +87,12 @@ export class VectorSearch {
90
87
  score = Math.max(-1, Math.min(1, score));
91
88
  if (score < (options.minScore ?? -1))
92
89
  continue;
93
- const group = synchronous(options.groupBy?.(candidate), 'Vector grouping must be synchronous') ?? id;
90
+ const group = synchronous(options.groupBy?.(structuredClone(document)), 'Vector grouping must be synchronous') ?? id;
94
91
  if (typeof group !== 'string')
95
92
  throw new TypeError('Vector groups must be strings');
96
93
  const prior = groups.get(group);
97
94
  if (!prior || score > prior.score || (score === prior.score && id < prior.id))
98
- groups.set(group, { id, score, document: candidate });
95
+ groups.set(group, { id, score, document: structuredClone(document) });
99
96
  }
100
97
  return [...groups.values()]
101
98
  .sort((a, b) => b.score - a.score || a.id.localeCompare(b.id))
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Node-partitioned cache contracts. Engine memory is synchronous; service-owned storage is
3
+ * asynchronous and explicitly selects volatile or persistent space. Neither is workflow state.
4
+ */
5
+ /** Optional Engine memory bounds; TTL is measured from the latest write. */
6
+ export interface EngineCacheOptions {
7
+ readonly maxEntries?: number;
8
+ readonly ttlMs?: number;
9
+ }
10
+ /** Only reads may name another node. Partition names are not authorization boundaries. */
11
+ export interface CacheReadOptions {
12
+ readonly nodeId?: string;
13
+ }
14
+ /** Synchronous view of the current node's Engine-owned memory. Values retain identity. */
15
+ export interface NodeCache {
16
+ get(key: string, options?: CacheReadOptions): unknown;
17
+ has(key: string): boolean;
18
+ set(key: string, value: unknown): void;
19
+ delete(key: string): boolean;
20
+ clear(): void;
21
+ }
22
+ /** Persistent and volatile storage are independent; there is no implicit fallback or mirroring. */
23
+ export interface AgentCacheOptions {
24
+ readonly persistence?: boolean;
25
+ }
26
+ /** Exact immutable address passed to the App port, including cancellation for this visit. */
27
+ export interface AgentCacheAddress {
28
+ readonly nodeId: string;
29
+ readonly key: string;
30
+ readonly persistence: boolean;
31
+ readonly signal: AbortSignal;
32
+ }
33
+ /** App-owned storage. Capacity, expiry, serialization and user isolation belong to the App. */
34
+ export interface AgentCachePort {
35
+ readonly supportsPersistence?: boolean;
36
+ get(address: AgentCacheAddress): Promise<unknown>;
37
+ has(address: AgentCacheAddress): Promise<boolean>;
38
+ set(address: AgentCacheAddress, value: unknown): Promise<void>;
39
+ delete(address: AgentCacheAddress): Promise<boolean>;
40
+ clear(partition: Omit<AgentCacheAddress, 'key'>): Promise<void>;
41
+ }
42
+ /** Async node view. Writes and clear can only address the current partition. */
43
+ export interface NodeAgentCache {
44
+ get(key: string, options?: AgentCacheOptions & CacheReadOptions): Promise<unknown>;
45
+ has(key: string, options?: AgentCacheOptions): Promise<boolean>;
46
+ set(key: string, value: unknown, options?: AgentCacheOptions): Promise<void>;
47
+ delete(key: string, options?: AgentCacheOptions): Promise<boolean>;
48
+ clear(options?: AgentCacheOptions): Promise<void>;
49
+ }
50
+ /** Cache access shared by workflow visits and the low-level $step loop. */
51
+ export interface CacheContext {
52
+ readonly cache: NodeCache;
53
+ readonly agentCache?: NodeAgentCache;
54
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Node-partitioned cache contracts. Engine memory is synchronous; service-owned storage is
3
+ * asynchronous and explicitly selects volatile or persistent space. Neither is workflow state.
4
+ */
5
+ export {};
@@ -8,6 +8,7 @@
8
8
  * Exports: `LoopEvent`, `StepOutcome`, `CoreStep`, `AgentCoreEngineOptions`.
9
9
  */
10
10
  import type { ExecutionContext, ExecutionOptions, MaybePromise } from '../common.js';
11
+ import type { CacheContext, EngineCacheOptions, AgentCachePort } from './cache.js';
11
12
  /** Progress fact for one loop round, correlated with the enclosing diagnostics span. */
12
13
  export interface LoopEvent {
13
14
  /** Unique id of this fact. */
@@ -31,9 +32,13 @@ export type StepOutcome<Value> =
31
32
  readonly value: Value;
32
33
  };
33
34
  /** One loop iteration. Must be re-entrant: the same step object runs every round. */
34
- export type CoreStep<Result> = (roundIndex: number, context: ExecutionContext & import('../debug.js').NodeReportingContext) => MaybePromise<StepOutcome<Result>>;
35
+ export type CoreStep<Result> = (roundIndex: number, context: ExecutionContext & import('../debug.js').NodeReportingContext & CacheContext) => MaybePromise<StepOutcome<Result>>;
35
36
  /** Engine construction options. */
36
37
  export interface AgentCoreEngineOptions {
38
+ /** Engine-local LRU bounds; not copied as cache data when forking. */
39
+ cache?: EngineCacheOptions;
40
+ /** Borrowed App service cache; forks must explicitly provide their own port. */
41
+ agentCache?: AgentCachePort;
37
42
  /** Round budget; must be a positive safe integer. Defaults to 8 rounds. */
38
43
  maxRounds?: number;
39
44
  /** Cancellation and diagnostics shared by every round. */
@@ -10,6 +10,7 @@
10
10
  * `AgentWorkflowDefinition`, `WorkflowEvent`, `WorkflowFailure`, `WorkflowExtension`,
11
11
  * `WorkflowModel`.
12
12
  */
13
+ import type { CacheContext } from './cache.js';
13
14
  import type { OperationContext } from '../common.js';
14
15
  import type { DiagnosticContext } from '../diagnostics.js';
15
16
  import type { ModelTransportDefinition } from '../model/runtime.js';
@@ -20,7 +21,7 @@ type WorkflowModelRequest<Transport> = import('../model/runtime.js').ModelCallRe
20
21
  readonly modelId?: string;
21
22
  };
22
23
  /** Execution facts available to every node of a running workflow. */
23
- export interface WorkflowContext<Runtime = undefined> extends OperationContext<Runtime>, NodeReportingContext {
24
+ export interface WorkflowContext<Runtime = undefined> extends OperationContext<Runtime>, NodeReportingContext, CacheContext {
24
25
  /** Zero-based bounded round; phases within a round do not consume another round. */
25
26
  readonly roundIndex: number;
26
27
  /** Report progress while this node is active; Core supplies its execution identity. */
@@ -54,3 +54,4 @@ export * from './common.js';
54
54
  export type * from './invocation.js';
55
55
  export type * from './debug.js';
56
56
  export type * from './execution/events.js';
57
+ export type * from './execution/cache.js';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Internal decoration: construct definition classes or bind methods to their real host,
3
+ * compile through existing entry points and automatically register in the kind's root.
4
+ */
5
+ import { type DefinitionKind, type IdentifiedDefinition } from './pool.js';
6
+ type Handler = (this: any, ...args: any[]) => any;
7
+ /** Preserve union branches when removing the decorator-owned id. */
8
+ export type DefinitionBody<Definition> = Definition extends unknown ? Omit<Definition, 'id'> : never;
9
+ /** Namespaces group external registration without changing Core options. */
10
+ export interface DecorationOptions {
11
+ readonly ns?: string | readonly string[];
12
+ }
13
+ export type ClassDecoration<Definition extends IdentifiedDefinition> = <Constructor extends new () => DefinitionBody<Definition>>(target: Constructor, context: ClassDecoratorContext<Constructor>) => void;
14
+ export type MethodDecoration<Method extends Handler> = <This>(method: (this: This, ...args: Parameters<Method>) => ReturnType<Method>, context: ClassMethodDecoratorContext<This, (this: This, ...args: Parameters<Method>) => ReturnType<Method>>) => void;
15
+ /** Class definitions register once after static initialization and successful construction. */
16
+ export declare function classDecoration<Definition extends IdentifiedDefinition>(kind: DefinitionKind, id: string, options: DecorationOptions, compile: (value: Definition) => Definition): ClassDecoration<Definition>;
17
+ /** Method initializers bind and register automatically; no scanning or caller-side bind is needed. */
18
+ export declare function methodDecoration<Definition extends IdentifiedDefinition, Method extends Handler>(kind: DefinitionKind, id: string, metadata: DefinitionBody<Definition>, handlerKey: keyof Definition, options: DecorationOptions, compile: (value: Definition) => Definition): MethodDecoration<Method>;
19
+ export {};
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Internal decoration: construct definition classes or bind methods to their real host,
3
+ * compile through existing entry points and automatically register in the kind's root.
4
+ */
5
+ import { registerDefinition } from './pool.js';
6
+ /** Restore only the decorator-owned id, preserving instance identity, getters and private state. */
7
+ function withId(id, instance) {
8
+ if ('id' in instance)
9
+ throw new TypeError('Decoration supplies id; do not also define it on the class');
10
+ Object.defineProperty(instance, 'id', { value: id, enumerable: true });
11
+ return instance;
12
+ }
13
+ /** Class definitions register once after static initialization and successful construction. */
14
+ export function classDecoration(kind, id, options, compile) {
15
+ return (_target, context) => {
16
+ if (context?.kind !== 'class')
17
+ throw new TypeError('Use standard class decorator semantics');
18
+ context.addInitializer(function () {
19
+ registerDefinition(kind, compile(withId(id, new this())), options.ns);
20
+ });
21
+ };
22
+ }
23
+ /** Method initializers bind and register automatically; no scanning or caller-side bind is needed. */
24
+ export function methodDecoration(kind, id, metadata, handlerKey, options, compile) {
25
+ return (method, context) => {
26
+ if (context?.kind !== 'method' || context.static || context.private)
27
+ throw new TypeError('Use a standard public instance method decorator');
28
+ context.addInitializer(function () {
29
+ const value = { ...metadata, [handlerKey]: method.bind(this) };
30
+ for (const key of Reflect.ownKeys(value)) {
31
+ const member = value[key];
32
+ if (typeof member === 'function')
33
+ value[key] = member.bind(this);
34
+ }
35
+ registerDefinition(kind, compile(withId(id, value)), options.ns);
36
+ });
37
+ };
38
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Optional standard decorators. Classes are definition implementations; public instance methods
3
+ * keep their original host. Only definitions are created here, never Engines or ModelRunners.
4
+ */
5
+ import type { WorkflowNode, AgentWorkflowDefinition } from '../../structs/execution/workflow.js';
6
+ import type { AgentCoreEngineSetup } from '../../execution/engine.js';
7
+ import type { ToolDefinition } from '../../structs/capabilities/tools.js';
8
+ import type { ContextFragmentConfig } from '../../structs/support/context.js';
9
+ import type { ModelRunnerOptions } from '../../structs/model/runtime.js';
10
+ import { type ClassDecoration, type MethodDecoration, type DecorationOptions } from './definition.js';
11
+ import type { IdentifiedDefinition } from './pool.js';
12
+ export { nodePool, toolPool, fragmentPool, workflowPool, agentPool, modelPool } from './pool.js';
13
+ export type { IdentifiedDefinition, DefinitionReader } from './pool.js';
14
+ export type { ClassDecoration, MethodDecoration, DecorationOptions } from './definition.js';
15
+ export type { Node, Tool, Fragment, Workflow, Agent, Model } from './types.js';
16
+ /** Method metadata describes the adapted handler, with optional external namespaces. */
17
+ type MethodOptions<Definition extends IdentifiedDefinition, Key extends keyof Definition> = Omit<Definition, 'id' | Key> & DecorationOptions;
18
+ /** Instantiate a node definition class once, injecting its id; graph validation stays in Workflow. */
19
+ export declare function node<State = any, Result = unknown, App = any>(id: string, options?: DecorationOptions): ClassDecoration<WorkflowNode<State, Result, App>>;
20
+ /** Bind a named host method as run without renaming or manually binding the original object. */
21
+ export declare function node<State = any, Result = unknown, App = any>(id: string, options: MethodOptions<WorkflowNode<State, Result, App>, 'run'>): MethodDecoration<NonNullable<WorkflowNode<State, Result, App>['run']>>;
22
+ /** Instantiate a tool definition class; schema and authorization stay with existing defineTool. */
23
+ export declare function tool<Input = unknown, Output = unknown, App = any>(id: string, options?: DecorationOptions): ClassDecoration<ToolDefinition<Input, Output, App>>;
24
+ /** Bind an existing host method as the tool's execute callback. */
25
+ export declare function tool<Input = unknown, Output = unknown, App = any>(id: string, options: MethodOptions<ToolDefinition<Input, Output, App>, 'execute'>): MethodDecoration<ToolDefinition<Input, Output, App>['execute']>;
26
+ /** Instantiate a self-describing fragment class; the original builder owns formatting checks. */
27
+ export declare function fragment<Input = unknown>(id: string, options?: DecorationOptions): ClassDecoration<ContextFragmentConfig<Input>>;
28
+ /** Bind a host's formatter method into an original fragment configuration. */
29
+ export declare function fragment<Input = unknown>(id: string, options: MethodOptions<ContextFragmentConfig<Input>, 'format'>): MethodDecoration<ContextFragmentConfig<Input>['format']>;
30
+ /** Instantiate and validate workflow options, leaving actual execution with the original Engine. */
31
+ export declare function workflow<State = any, Result = unknown, App = any>(id: string, options?: DecorationOptions): ClassDecoration<AgentWorkflowDefinition<State, Result, App>>;
32
+ /** Agent id is an external pool identity, not an override of Core branch or request identity. */
33
+ export type AgentOptions<State = unknown, Result = unknown, App = undefined> = AgentCoreEngineSetup<State, Result, App> & IdentifiedDefinition;
34
+ /** Instantiate an Engine configuration class without creating an Engine, budget or request. */
35
+ export declare function agent<State = unknown, Result = unknown, App = undefined>(id: string, options?: DecorationOptions): ClassDecoration<AgentOptions<State, Result, App>>;
36
+ /** Model id is only a pool key; Core still owns the configured model/transport identities. */
37
+ export type ModelOptions = ModelRunnerOptions & IdentifiedDefinition;
38
+ /** Instantiate and validate model options without creating a Runner or consuming calls. */
39
+ export declare function model(id: string, options?: DecorationOptions): ClassDecoration<ModelOptions>;
@@ -0,0 +1,48 @@
1
+ import { defineTool } from '../tools.js';
2
+ import { defineWorkflow } from '../../execution/workflow.js';
3
+ import { defineContextFragments } from '../context/assembly.js';
4
+ import { snapshotModelBinding } from '../../model/binding.js';
5
+ import { classDecoration, methodDecoration, } from './definition.js';
6
+ export { nodePool, toolPool, fragmentPool, workflowPool, agentPool, modelPool } from './pool.js';
7
+ /** Internal conversion shared by node/tool/fragment; never a public free-function adapter. */
8
+ function decorate(kind, id, options, key, compile) {
9
+ return (value, context) => {
10
+ if (context?.kind === 'class') {
11
+ classDecoration(kind, id, options, compile)(value, context);
12
+ }
13
+ else {
14
+ const { ns, ...metadata } = options;
15
+ methodDecoration(kind, id, metadata, key, { ns }, compile)(value, context);
16
+ }
17
+ };
18
+ }
19
+ export function node(id, options = {}) {
20
+ return decorate('node', id, options, 'run', (value) => value);
21
+ }
22
+ export function tool(id, options = {}) {
23
+ return decorate('tool', id, options, 'execute', (value) => defineTool(value));
24
+ }
25
+ export function fragment(id, options = {}) {
26
+ return decorate('fragment', id, options, 'format', (value) => {
27
+ defineContextFragments([value]);
28
+ return value;
29
+ });
30
+ }
31
+ /** Instantiate and validate workflow options, leaving actual execution with the original Engine. */
32
+ export function workflow(id, options = {}) {
33
+ return classDecoration('workflow', id, options, (value) => {
34
+ defineWorkflow(value);
35
+ return value;
36
+ });
37
+ }
38
+ /** Instantiate an Engine configuration class without creating an Engine, budget or request. */
39
+ export function agent(id, options = {}) {
40
+ return classDecoration('agent', id, options, (value) => value);
41
+ }
42
+ /** Instantiate and validate model options without creating a Runner or consuming calls. */
43
+ export function model(id, options = {}) {
44
+ return classDecoration('model', id, options, (value) => {
45
+ snapshotModelBinding(value);
46
+ return value;
47
+ });
48
+ }
@@ -0,0 +1,97 @@
1
+ import type { WorkflowNode, AgentWorkflowDefinition } from '../../structs/execution/workflow.js';
2
+ import type { ToolDefinition } from '../../structs/capabilities/tools.js';
3
+ import type { ContextFragmentConfig } from '../../structs/support/context.js';
4
+ /** Registration identity; ids are unique within one kind and namespace. */
5
+ export interface IdentifiedDefinition {
6
+ readonly id: string;
7
+ }
8
+ /** Read-only roots. A typed read describes the expected contract, not a static id/type lookup. */
9
+ export interface DefinitionReader<Definition extends IdentifiedDefinition> {
10
+ get<Value extends Definition = Definition>(id: string, ns?: string): Value | undefined;
11
+ getAll<Value extends Definition = Definition>(ns?: string): readonly Value[];
12
+ pick<Value extends Definition = Definition>(ids: readonly string[], ns?: string): readonly Value[];
13
+ }
14
+ /** Enumerable categories drive allocation and lookup; no additional runtime dispatch layer. */
15
+ export declare const definitionKinds: readonly ["node", "tool", "fragment", "workflow", "agent", "model"];
16
+ export type DefinitionKind = (typeof definitionKinds)[number];
17
+ /** Preflight every destination so a conflict never leaves partial multi-namespace registration. */
18
+ export declare function registerDefinition(kind: DefinitionKind, definition: IdentifiedDefinition, ns?: string | readonly string[]): void;
19
+ /** Decorated nodes; ids are unique within each namespace. */
20
+ export declare const nodePool: DefinitionReader<WorkflowNode<any, any, any>>;
21
+ /** Decorated tools; namespaces isolate otherwise identical ids. */
22
+ export declare const toolPool: DefinitionReader<ToolDefinition<any, any, any, string>>;
23
+ /** Decorated fragment configurations; use the original builder to assemble them. */
24
+ export declare const fragmentPool: DefinitionReader<ContextFragmentConfig<any>>;
25
+ /** Decorated workflow definitions; execution still belongs to the original Engine. */
26
+ export declare const workflowPool: DefinitionReader<AgentWorkflowDefinition<any, any, any>>;
27
+ /** Decorated Engine options; request-specific App contracts are supplied on typed reads. */
28
+ export declare const agentPool: DefinitionReader<(Omit<import("../../index.js").AgentCoreEngineOptions & {
29
+ readonly branch?: import("../../index.js").AgentBranch;
30
+ readonly tools?: {
31
+ invoke: (request: import("../../index.js").ToolInvocationRequest) => Promise<import("../../index.js").ToolResult<unknown>>;
32
+ } | Pick<import("../../index.js").ToolRegistry<import("../../index.js").ExecutableTool>, "invokeAsync">;
33
+ } & {
34
+ model?: import("../../index.js").AgentModelBinding;
35
+ models?: never;
36
+ } & {
37
+ workflow?: undefined;
38
+ app?: never;
39
+ }, "app"> & {
40
+ readonly app?: unknown;
41
+ } & IdentifiedDefinition) | (Omit<import("../../index.js").AgentCoreEngineOptions & {
42
+ readonly branch?: import("../../index.js").AgentBranch;
43
+ readonly tools?: {
44
+ invoke: (request: import("../../index.js").ToolInvocationRequest) => Promise<import("../../index.js").ToolResult<unknown>>;
45
+ } | Pick<import("../../index.js").ToolRegistry<import("../../index.js").ExecutableTool>, "invokeAsync">;
46
+ } & {
47
+ model?: string;
48
+ models: Readonly<Record<string, import("../../index.js").AgentModelBinding>>;
49
+ maxModelCalls?: number;
50
+ } & {
51
+ workflow?: undefined;
52
+ app?: never;
53
+ }, "app"> & {
54
+ readonly app?: unknown;
55
+ } & IdentifiedDefinition) | (Omit<import("../../index.js").AgentCoreEngineOptions & {
56
+ readonly branch?: import("../../index.js").AgentBranch;
57
+ readonly tools?: {
58
+ invoke: (request: import("../../index.js").ToolInvocationRequest) => Promise<import("../../index.js").ToolResult<unknown>>;
59
+ } | Pick<import("../../index.js").ToolRegistry<import("../../index.js").ExecutableTool>, "invokeAsync">;
60
+ } & {
61
+ model?: import("../../index.js").AgentModelBinding;
62
+ models?: never;
63
+ } & {
64
+ readonly workflow: import("../../index.js").AgentWorkflow<any, any, any>;
65
+ } & {
66
+ readonly app?: undefined;
67
+ }, "app"> & {
68
+ readonly app?: unknown;
69
+ } & IdentifiedDefinition) | (Omit<import("../../index.js").AgentCoreEngineOptions & {
70
+ readonly branch?: import("../../index.js").AgentBranch;
71
+ readonly tools?: {
72
+ invoke: (request: import("../../index.js").ToolInvocationRequest) => Promise<import("../../index.js").ToolResult<unknown>>;
73
+ } | Pick<import("../../index.js").ToolRegistry<import("../../index.js").ExecutableTool>, "invokeAsync">;
74
+ } & {
75
+ model?: string;
76
+ models: Readonly<Record<string, import("../../index.js").AgentModelBinding>>;
77
+ maxModelCalls?: number;
78
+ } & {
79
+ readonly workflow: import("../../index.js").AgentWorkflow<any, any, any>;
80
+ } & {
81
+ readonly app?: undefined;
82
+ }, "app"> & {
83
+ readonly app?: unknown;
84
+ } & IdentifiedDefinition)>;
85
+ /** Decorated Runner options; reading does not create a Runner or spend model calls. */
86
+ export declare const modelPool: DefinitionReader<{
87
+ provider: import("../../index.js").ChatProvider;
88
+ execution?: import("../../index.js").ExecutionOptions;
89
+ transports?: readonly import("../../index.js").ModelTransportDefinition<unknown>[] | undefined;
90
+ sink?: {
91
+ output?: import("../../index.js").ModelRunnerPorts["onOutput"];
92
+ };
93
+ errors?: {
94
+ inputBudget?: import("../../index.js").ModelRunnerPorts["inputBudgetError"];
95
+ outputBudget?: import("../../index.js").ModelRunnerPorts["outputBudgetError"];
96
+ };
97
+ } & import("../../index.js").ModelRunnerSettings & IdentifiedDefinition>;