@beignet/core 0.0.34 → 0.0.36

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 (120) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +145 -15
  3. package/dist/agent-capabilities/index.d.ts +197 -0
  4. package/dist/agent-capabilities/index.d.ts.map +1 -0
  5. package/dist/agent-capabilities/index.js +282 -0
  6. package/dist/agent-capabilities/index.js.map +1 -0
  7. package/dist/application/index.d.ts +2 -2
  8. package/dist/application/index.d.ts.map +1 -1
  9. package/dist/application/index.js +13 -3
  10. package/dist/application/index.js.map +1 -1
  11. package/dist/client/client.d.ts +2 -0
  12. package/dist/client/client.d.ts.map +1 -1
  13. package/dist/client/client.js +33 -16
  14. package/dist/client/client.js.map +1 -1
  15. package/dist/contracts/contract-builder.d.ts +5 -3
  16. package/dist/contracts/contract-builder.d.ts.map +1 -1
  17. package/dist/contracts/contract-builder.js +3 -12
  18. package/dist/contracts/contract-builder.js.map +1 -1
  19. package/dist/contracts/contract-group.d.ts +2 -2
  20. package/dist/contracts/contract-group.d.ts.map +1 -1
  21. package/dist/contracts/contract-group.js +2 -1
  22. package/dist/contracts/contract-group.js.map +1 -1
  23. package/dist/contracts/metadata.d.ts +3 -0
  24. package/dist/contracts/metadata.d.ts.map +1 -0
  25. package/dist/contracts/metadata.js +14 -0
  26. package/dist/contracts/metadata.js.map +1 -0
  27. package/dist/contracts/types.d.ts +20 -0
  28. package/dist/contracts/types.d.ts.map +1 -1
  29. package/dist/contracts/types.js.map +1 -1
  30. package/dist/events/index.d.ts +4 -4
  31. package/dist/events/index.d.ts.map +1 -1
  32. package/dist/events/index.js +5 -5
  33. package/dist/events/index.js.map +1 -1
  34. package/dist/idempotency/index.d.ts.map +1 -1
  35. package/dist/idempotency/index.js +14 -9
  36. package/dist/idempotency/index.js.map +1 -1
  37. package/dist/jobs/index.d.ts.map +1 -1
  38. package/dist/jobs/index.js +10 -4
  39. package/dist/jobs/index.js.map +1 -1
  40. package/dist/openapi/index.js +15 -8
  41. package/dist/openapi/index.js.map +1 -1
  42. package/dist/ports/auth.d.ts +2 -0
  43. package/dist/ports/auth.d.ts.map +1 -1
  44. package/dist/ports/auth.js +2 -0
  45. package/dist/ports/auth.js.map +1 -1
  46. package/dist/providers/index.d.ts +1 -1
  47. package/dist/providers/index.d.ts.map +1 -1
  48. package/dist/providers/index.js.map +1 -1
  49. package/dist/providers/provider.d.ts +9 -1
  50. package/dist/providers/provider.d.ts.map +1 -1
  51. package/dist/providers/provider.js.map +1 -1
  52. package/dist/schedules/index.d.ts +6 -13
  53. package/dist/schedules/index.d.ts.map +1 -1
  54. package/dist/schedules/index.js +24 -44
  55. package/dist/schedules/index.js.map +1 -1
  56. package/dist/server/index.d.ts +2 -2
  57. package/dist/server/index.d.ts.map +1 -1
  58. package/dist/server/index.js +1 -1
  59. package/dist/server/index.js.map +1 -1
  60. package/dist/server/request-executor.d.ts +72 -0
  61. package/dist/server/request-executor.d.ts.map +1 -0
  62. package/dist/server/request-executor.js +781 -0
  63. package/dist/server/request-executor.js.map +1 -0
  64. package/dist/server/request-preparation.d.ts +39 -0
  65. package/dist/server/request-preparation.d.ts.map +1 -0
  66. package/dist/server/request-preparation.js +248 -0
  67. package/dist/server/request-preparation.js.map +1 -0
  68. package/dist/server/response-finalization.d.ts +34 -0
  69. package/dist/server/response-finalization.d.ts.map +1 -0
  70. package/dist/server/response-finalization.js +279 -0
  71. package/dist/server/response-finalization.js.map +1 -0
  72. package/dist/server/route-definitions.d.ts +139 -0
  73. package/dist/server/route-definitions.d.ts.map +1 -0
  74. package/dist/server/route-definitions.js +94 -0
  75. package/dist/server/route-definitions.js.map +1 -0
  76. package/dist/server/route-matching.d.ts +15 -0
  77. package/dist/server/route-matching.d.ts.map +1 -0
  78. package/dist/server/route-matching.js +46 -0
  79. package/dist/server/route-matching.js.map +1 -0
  80. package/dist/server/server.d.ts +6 -147
  81. package/dist/server/server.d.ts.map +1 -1
  82. package/dist/server/server.js +29 -1350
  83. package/dist/server/server.js.map +1 -1
  84. package/dist/server/use-case-route.d.ts +1 -1
  85. package/dist/tracing/index.d.ts +1 -1
  86. package/dist/tracing/index.d.ts.map +1 -1
  87. package/dist/tracing/index.js.map +1 -1
  88. package/dist/uploads/client.js +4 -7
  89. package/dist/uploads/client.js.map +1 -1
  90. package/dist/uploads/index.d.ts.map +1 -1
  91. package/dist/uploads/index.js +59 -12
  92. package/dist/uploads/index.js.map +1 -1
  93. package/package.json +5 -1
  94. package/skills/app-architecture/SKILL.md +27 -1
  95. package/src/agent-capabilities/index.ts +643 -0
  96. package/src/application/index.ts +19 -5
  97. package/src/client/client.ts +41 -17
  98. package/src/contracts/contract-builder.ts +8 -16
  99. package/src/contracts/contract-group.ts +4 -6
  100. package/src/contracts/metadata.ts +20 -0
  101. package/src/contracts/types.ts +39 -0
  102. package/src/events/index.ts +16 -20
  103. package/src/idempotency/index.ts +17 -9
  104. package/src/jobs/index.ts +8 -3
  105. package/src/openapi/index.ts +24 -12
  106. package/src/ports/auth.ts +2 -0
  107. package/src/providers/index.ts +1 -0
  108. package/src/providers/provider.ts +17 -2
  109. package/src/schedules/index.ts +57 -63
  110. package/src/server/index.ts +4 -2
  111. package/src/server/request-executor.ts +1170 -0
  112. package/src/server/request-preparation.ts +409 -0
  113. package/src/server/response-finalization.ts +385 -0
  114. package/src/server/route-definitions.ts +338 -0
  115. package/src/server/route-matching.ts +64 -0
  116. package/src/server/server.ts +63 -2139
  117. package/src/server/use-case-route.ts +1 -1
  118. package/src/tracing/index.ts +1 -0
  119. package/src/uploads/client.ts +4 -7
  120. package/src/uploads/index.ts +70 -12
@@ -0,0 +1,643 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+ import {
3
+ createProviderInstrumentation,
4
+ type ProviderInstrumentationTarget,
5
+ } from "../providers/index.js";
6
+ import { runWithResolvedTracingContext } from "../tracing/execution.js";
7
+ import type { TracingPort } from "../tracing/index.js";
8
+
9
+ /** Any Standard Schema compatible validator. */
10
+ export type AgentCapabilitySchema = StandardSchemaV1<unknown, unknown>;
11
+
12
+ /** Value or promise of that value. */
13
+ export type MaybePromise<T> = T | Promise<T>;
14
+
15
+ /** Infer the input accepted by a capability schema. */
16
+ export type InferAgentCapabilitySchemaInput<Schema extends StandardSchemaV1> =
17
+ StandardSchemaV1.InferInput<Schema>;
18
+
19
+ /** Infer the parsed output produced by a capability schema. */
20
+ export type InferAgentCapabilitySchemaOutput<Schema extends StandardSchemaV1> =
21
+ StandardSchemaV1.InferOutput<Schema>;
22
+
23
+ /** A typed action that an authenticated agent transport may expose. */
24
+ export interface AgentCapabilityDef<
25
+ Name extends string = string,
26
+ Input extends AgentCapabilitySchema = AgentCapabilitySchema,
27
+ Output extends AgentCapabilitySchema = AgentCapabilitySchema,
28
+ Ctx = unknown,
29
+ Principal = unknown,
30
+ > {
31
+ readonly kind: "agentCapability";
32
+ readonly name: Name;
33
+ readonly description: string;
34
+ readonly input: Input;
35
+ readonly output: Output;
36
+ readonly handle: (args: {
37
+ capability: AgentCapabilityDef<Name, Input, Output, Ctx, Principal>;
38
+ ctx: Ctx;
39
+ principal: Principal;
40
+ input: InferAgentCapabilitySchemaOutput<Input>;
41
+ }) => MaybePromise<InferAgentCapabilitySchemaInput<Output>>;
42
+ }
43
+
44
+ /** Broad capability definition shape tied to one context and principal. */
45
+ export interface AgentCapabilityFor<Ctx, Principal> {
46
+ readonly kind: "agentCapability";
47
+ readonly name: string;
48
+ readonly description: string;
49
+ readonly input: AgentCapabilitySchema;
50
+ readonly output: AgentCapabilitySchema;
51
+ readonly handle: (args: {
52
+ capability: never;
53
+ ctx: Ctx;
54
+ principal: Principal;
55
+ input: never;
56
+ }) => MaybePromise<unknown>;
57
+ }
58
+
59
+ /** Broad capability definition shape used by registries and adapters. */
60
+ export type AnyAgentCapabilityDef = AgentCapabilityFor<never, never>;
61
+
62
+ /** Infer a capability's application context. */
63
+ export type AgentCapabilityContext<Capability extends AnyAgentCapabilityDef> =
64
+ Capability extends { handle(args: { ctx: infer Ctx }): unknown }
65
+ ? Ctx
66
+ : never;
67
+
68
+ /** Infer a capability's verified principal. */
69
+ export type AgentCapabilityPrincipal<Capability extends AnyAgentCapabilityDef> =
70
+ Capability extends { handle(args: { principal: infer Principal }): unknown }
71
+ ? Principal
72
+ : never;
73
+
74
+ /** Infer a capability's raw input. */
75
+ export type AgentCapabilityInput<Capability extends AnyAgentCapabilityDef> =
76
+ InferAgentCapabilitySchemaInput<Capability["input"]>;
77
+
78
+ /** Infer the parsed input passed to capability context and execution. */
79
+ export type AgentCapabilityParsedInput<
80
+ Capability extends AnyAgentCapabilityDef,
81
+ > = InferAgentCapabilitySchemaOutput<Capability["input"]>;
82
+
83
+ /** Infer a capability's validated output. */
84
+ export type AgentCapabilityOutput<Capability extends AnyAgentCapabilityDef> =
85
+ InferAgentCapabilitySchemaOutput<Capability["output"]>;
86
+
87
+ /** Options accepted by `defineAgentCapability(...)`. */
88
+ export interface DefineAgentCapabilityOptions<
89
+ Name extends string,
90
+ Input extends AgentCapabilitySchema,
91
+ Output extends AgentCapabilitySchema,
92
+ Ctx,
93
+ Principal,
94
+ > {
95
+ description: string;
96
+ input: Input;
97
+ output: Output;
98
+ handle(args: {
99
+ capability: AgentCapabilityDef<Name, Input, Output, Ctx, Principal>;
100
+ ctx: Ctx;
101
+ principal: Principal;
102
+ input: InferAgentCapabilitySchemaOutput<Input>;
103
+ }): MaybePromise<InferAgentCapabilitySchemaInput<Output>>;
104
+ }
105
+
106
+ /** Context-bound capability definition helpers. */
107
+ export interface AgentCapabilities<Ctx, Principal> {
108
+ defineAgentCapability<
109
+ const Name extends string,
110
+ Input extends AgentCapabilitySchema,
111
+ Output extends AgentCapabilitySchema,
112
+ >(
113
+ name: Name,
114
+ options: DefineAgentCapabilityOptions<Name, Input, Output, Ctx, Principal>,
115
+ ): AgentCapabilityDef<Name, Input, Output, Ctx, Principal>;
116
+ defineAgentCapabilityRegistry<
117
+ const Definitions extends readonly AgentCapabilityFor<Ctx, Principal>[],
118
+ >(definitions: Definitions): AgentCapabilityRegistry<Definitions>;
119
+ }
120
+
121
+ /** Bind application context and principal types once for capability definitions. */
122
+ export function createAgentCapabilities<Ctx, Principal>(): AgentCapabilities<
123
+ Ctx,
124
+ Principal
125
+ > {
126
+ return {
127
+ defineAgentCapability(name, options) {
128
+ return {
129
+ kind: "agentCapability",
130
+ name,
131
+ description: options.description,
132
+ input: options.input,
133
+ output: options.output,
134
+ handle: options.handle,
135
+ };
136
+ },
137
+ defineAgentCapabilityRegistry,
138
+ };
139
+ }
140
+
141
+ /** Error thrown when a capability registry is ambiguous. */
142
+ export class AgentCapabilityRegistryError extends Error {
143
+ constructor(message: string) {
144
+ super(message);
145
+ this.name = "AgentCapabilityRegistryError";
146
+ }
147
+ }
148
+
149
+ /** Registered capability definitions available to execution adapters. */
150
+ export interface AgentCapabilityRegistry<
151
+ Definitions extends
152
+ readonly AnyAgentCapabilityDef[] = readonly AnyAgentCapabilityDef[],
153
+ > {
154
+ readonly definitions: Definitions;
155
+ get(name: string): Definitions[number] | undefined;
156
+ }
157
+
158
+ /** Build a capability registry and reject duplicate external names. */
159
+ function defineAgentCapabilityRegistry<
160
+ const Definitions extends readonly AnyAgentCapabilityDef[],
161
+ >(definitions: Definitions): AgentCapabilityRegistry<Definitions> {
162
+ const byName = new Map<string, Definitions[number]>();
163
+
164
+ for (const capability of definitions) {
165
+ if (byName.has(capability.name)) {
166
+ throw new AgentCapabilityRegistryError(
167
+ `Duplicate agent capability definition "${capability.name}" in registry.`,
168
+ );
169
+ }
170
+ byName.set(capability.name, capability);
171
+ }
172
+
173
+ return {
174
+ definitions: [...definitions] as unknown as Definitions,
175
+ get(name) {
176
+ return byName.get(name);
177
+ },
178
+ };
179
+ }
180
+
181
+ /** Stable capability execution failure categories. */
182
+ export type AgentCapabilityErrorCode =
183
+ | "unknown_capability"
184
+ | "invalid_input"
185
+ | "invalid_output"
186
+ | "execution_failed";
187
+
188
+ /** Error raised by capability lookup, validation, or execution. */
189
+ export class AgentCapabilityError extends Error {
190
+ readonly code: AgentCapabilityErrorCode;
191
+ readonly capabilityName: string;
192
+ readonly issues?: readonly StandardSchemaV1.Issue[];
193
+ override readonly cause?: unknown;
194
+
195
+ constructor(args: {
196
+ code: AgentCapabilityErrorCode;
197
+ capabilityName: string;
198
+ message: string;
199
+ issues?: readonly StandardSchemaV1.Issue[];
200
+ cause?: unknown;
201
+ }) {
202
+ super(args.message);
203
+ this.name = "AgentCapabilityError";
204
+ this.code = args.code;
205
+ this.capabilityName = args.capabilityName;
206
+ this.issues = args.issues;
207
+ this.cause = args.cause;
208
+ }
209
+ }
210
+
211
+ /** A parsed invocation passed to the app-owned context resolver. */
212
+ export type AgentCapabilityContextRequest<
213
+ Definitions extends readonly AnyAgentCapabilityDef[],
214
+ Principal,
215
+ > = {
216
+ [Capability in Definitions[number] as Capability["name"]]: {
217
+ capability: Capability;
218
+ name: Capability["name"];
219
+ principal: Principal;
220
+ input: InferAgentCapabilitySchemaOutput<Capability["input"]>;
221
+ };
222
+ }[Definitions[number]["name"]];
223
+
224
+ /** Stage reached by an agent capability attempt. */
225
+ export type AgentCapabilityRunStage =
226
+ | "lookup"
227
+ | "input"
228
+ | "authorization"
229
+ | "context"
230
+ | "handler"
231
+ | "output";
232
+
233
+ /** Successful capability lifecycle event with validated application data. */
234
+ export type AgentCapabilityCompletedRunEvent<
235
+ Ctx,
236
+ Principal,
237
+ Definitions extends
238
+ readonly AnyAgentCapabilityDef[] = readonly AnyAgentCapabilityDef[],
239
+ Capability extends Definitions[number] = Definitions[number],
240
+ > = Capability extends Definitions[number]
241
+ ? {
242
+ name: Capability["name"];
243
+ phase: "end";
244
+ stage: "output";
245
+ capability: Capability;
246
+ ctx: Ctx;
247
+ principal: Principal;
248
+ input: AgentCapabilityParsedInput<Capability>;
249
+ output: AgentCapabilityOutput<Capability>;
250
+ durationMs: number;
251
+ error?: undefined;
252
+ }
253
+ : never;
254
+
255
+ /** Lifecycle event observed around a capability attempt. */
256
+ export type AgentCapabilityRunEvent<
257
+ Ctx,
258
+ Principal,
259
+ Definitions extends
260
+ readonly AnyAgentCapabilityDef[] = readonly AnyAgentCapabilityDef[],
261
+ > =
262
+ | {
263
+ name: string;
264
+ phase: "start";
265
+ stage: "lookup";
266
+ capability?: Definitions[number];
267
+ ctx?: undefined;
268
+ principal: Principal;
269
+ input?: undefined;
270
+ output?: undefined;
271
+ durationMs?: undefined;
272
+ error?: undefined;
273
+ }
274
+ | AgentCapabilityCompletedRunEvent<Ctx, Principal, Definitions>
275
+ | {
276
+ name: string;
277
+ phase: "error";
278
+ stage: AgentCapabilityRunStage;
279
+ capability?: Definitions[number];
280
+ ctx?: Ctx;
281
+ principal: Principal;
282
+ /** Present only when input validation completed successfully. */
283
+ input?: unknown;
284
+ output?: undefined;
285
+ durationMs: number;
286
+ error: unknown;
287
+ };
288
+
289
+ /** Best-effort lifecycle observer for capability execution. */
290
+ export type AgentCapabilityHook<
291
+ Ctx,
292
+ Principal,
293
+ Definitions extends
294
+ readonly AnyAgentCapabilityDef[] = readonly AnyAgentCapabilityDef[],
295
+ > = (
296
+ event: AgentCapabilityRunEvent<Ctx, Principal, Definitions>,
297
+ ) => MaybePromise<void>;
298
+
299
+ /** Options for a capability executor. */
300
+ export interface CreateAgentCapabilityExecutorOptions<
301
+ Ctx,
302
+ Principal,
303
+ Definitions extends readonly AnyAgentCapabilityDef[],
304
+ > {
305
+ registry: AgentCapabilityRegistry<Definitions>;
306
+ createContext(
307
+ request: AgentCapabilityContextRequest<Definitions, Principal>,
308
+ ): MaybePromise<Ctx>;
309
+ hooks?: readonly AgentCapabilityHook<Ctx, Principal, Definitions>[];
310
+ /** Independent target used to observe failures before context exists. */
311
+ instrumentation?: ProviderInstrumentationTarget;
312
+ tracing?: TracingPort;
313
+ }
314
+
315
+ /** Raw dynamic invocation accepted by protocol adapters. */
316
+ export interface DynamicAgentCapabilityInvocation<Principal> {
317
+ name: string;
318
+ principal: Principal;
319
+ input: unknown;
320
+ /**
321
+ * Transport-owned authorization against the exact parsed input that will be
322
+ * passed to context construction and the capability handler.
323
+ */
324
+ authorize?(
325
+ request: DynamicAgentCapabilityAuthorization<Principal>,
326
+ ): MaybePromise<void>;
327
+ }
328
+
329
+ /** Parsed invocation exposed to a dynamic transport authorization callback. */
330
+ export interface DynamicAgentCapabilityAuthorization<Principal> {
331
+ capability: AnyAgentCapabilityDef;
332
+ name: string;
333
+ principal: Principal;
334
+ input: unknown;
335
+ }
336
+
337
+ /** Typed and dynamic execution surface for one capability registry. */
338
+ export interface AgentCapabilityExecutor<
339
+ Principal,
340
+ Definitions extends readonly AnyAgentCapabilityDef[],
341
+ > {
342
+ execute<Name extends Definitions[number]["name"]>(args: {
343
+ name: Name;
344
+ principal: Principal;
345
+ input: AgentCapabilityInput<Extract<Definitions[number], { name: Name }>>;
346
+ }): Promise<
347
+ AgentCapabilityOutput<Extract<Definitions[number], { name: Name }>>
348
+ >;
349
+ executeDynamic(
350
+ args: DynamicAgentCapabilityInvocation<Principal>,
351
+ ): Promise<unknown>;
352
+ }
353
+
354
+ function formatPath(path: StandardSchemaV1.Issue["path"]): string {
355
+ if (!path?.length) return "";
356
+ return path
357
+ .map((segment) =>
358
+ typeof segment === "object" && segment !== null && "key" in segment
359
+ ? String(segment.key)
360
+ : String(segment),
361
+ )
362
+ .join(".");
363
+ }
364
+
365
+ function formatIssues(issues: readonly StandardSchemaV1.Issue[]): string {
366
+ return issues
367
+ .map((issue) => {
368
+ const path = formatPath(issue.path);
369
+ return path ? `${path}: ${issue.message}` : issue.message;
370
+ })
371
+ .join("; ");
372
+ }
373
+
374
+ async function parseCapabilitySchema<Schema extends AgentCapabilitySchema>(
375
+ schema: Schema,
376
+ input: unknown,
377
+ args: {
378
+ capabilityName: string;
379
+ code: "invalid_input" | "invalid_output";
380
+ },
381
+ ): Promise<InferAgentCapabilitySchemaOutput<Schema>> {
382
+ const result = await schema["~standard"].validate(input);
383
+ if (result.issues?.length) {
384
+ throw new AgentCapabilityError({
385
+ code: args.code,
386
+ capabilityName: args.capabilityName,
387
+ message: `Agent capability "${args.capabilityName}" ${args.code === "invalid_input" ? "input" : "output"} validation failed: ${formatIssues(result.issues)}`,
388
+ issues: result.issues,
389
+ });
390
+ }
391
+ if ("value" in result) {
392
+ return result.value as InferAgentCapabilitySchemaOutput<Schema>;
393
+ }
394
+ throw new AgentCapabilityError({
395
+ code: args.code,
396
+ capabilityName: args.capabilityName,
397
+ message: `Agent capability "${args.capabilityName}" schema returned no value.`,
398
+ });
399
+ }
400
+
401
+ function instrumentationTarget(ctx: unknown): ProviderInstrumentationTarget {
402
+ if (!ctx || typeof ctx !== "object") return undefined;
403
+ if ("ports" in ctx) {
404
+ return (ctx as { ports?: ProviderInstrumentationTarget }).ports;
405
+ }
406
+ return ctx as ProviderInstrumentationTarget;
407
+ }
408
+
409
+ async function notifyHooks<
410
+ Ctx,
411
+ Principal,
412
+ Definitions extends readonly AnyAgentCapabilityDef[],
413
+ >(
414
+ hooks: readonly AgentCapabilityHook<Ctx, Principal, Definitions>[],
415
+ event: AgentCapabilityRunEvent<Ctx, Principal, Definitions>,
416
+ ): Promise<void> {
417
+ await Promise.all(
418
+ hooks.map(async (hook) => {
419
+ try {
420
+ await hook(event);
421
+ } catch {
422
+ // Observers must not alter application behavior.
423
+ }
424
+ }),
425
+ );
426
+ }
427
+
428
+ /** Create a validated, traced executor for a capability registry. */
429
+ export function createAgentCapabilityExecutor<
430
+ const Definitions extends readonly AnyAgentCapabilityDef[],
431
+ >(
432
+ options: CreateAgentCapabilityExecutorOptions<
433
+ AgentCapabilityContext<Definitions[number]>,
434
+ AgentCapabilityPrincipal<Definitions[number]>,
435
+ Definitions
436
+ >,
437
+ ): AgentCapabilityExecutor<
438
+ AgentCapabilityPrincipal<Definitions[number]>,
439
+ Definitions
440
+ > {
441
+ type Ctx = AgentCapabilityContext<Definitions[number]>;
442
+ type Principal = AgentCapabilityPrincipal<Definitions[number]>;
443
+ const hooks = options.hooks ?? [];
444
+ const instrumentationConfig = {
445
+ providerName: "agent-capabilities",
446
+ watcher: "agentCapabilities",
447
+ } as const;
448
+
449
+ async function executeDynamic(
450
+ invocation: DynamicAgentCapabilityInvocation<Principal>,
451
+ ): Promise<unknown> {
452
+ const startedAt = Date.now();
453
+ let stage: AgentCapabilityRunStage = "lookup";
454
+ let ctx: Ctx | undefined;
455
+ let capability: Definitions[number] | undefined;
456
+ let input: unknown;
457
+ let hasValidatedInput = false;
458
+ let instrumentation = createProviderInstrumentation(
459
+ options.instrumentation,
460
+ instrumentationConfig,
461
+ );
462
+
463
+ try {
464
+ capability = options.registry.get(invocation.name);
465
+ const observedName = capability?.name ?? "unknown";
466
+ instrumentation.custom({
467
+ name: "agentCapability.started",
468
+ label: `Agent capability ${observedName}`,
469
+ summary: "Capability execution started",
470
+ details: { capabilityName: observedName, phase: "start" },
471
+ });
472
+ await notifyHooks(hooks, {
473
+ name: invocation.name,
474
+ phase: "start",
475
+ stage,
476
+ principal: invocation.principal,
477
+ });
478
+
479
+ if (!capability) {
480
+ throw new AgentCapabilityError({
481
+ code: "unknown_capability",
482
+ capabilityName: invocation.name,
483
+ message: `Unknown agent capability "${invocation.name}".`,
484
+ });
485
+ }
486
+
487
+ const registeredCapability = capability;
488
+ const runtimeCapability =
489
+ registeredCapability as unknown as AgentCapabilityDef<
490
+ string,
491
+ AgentCapabilitySchema,
492
+ AgentCapabilitySchema,
493
+ Ctx,
494
+ Principal
495
+ >;
496
+ return await runWithResolvedTracingContext({
497
+ tracing: options.tracing,
498
+ ctx: async () => {
499
+ stage = "input";
500
+ input = await parseCapabilitySchema(
501
+ runtimeCapability.input,
502
+ invocation.input,
503
+ {
504
+ capabilityName: runtimeCapability.name,
505
+ code: "invalid_input",
506
+ },
507
+ );
508
+ hasValidatedInput = true;
509
+ if (invocation.authorize) {
510
+ stage = "authorization";
511
+ await invocation.authorize({
512
+ capability: registeredCapability,
513
+ name: registeredCapability.name,
514
+ principal: invocation.principal,
515
+ input,
516
+ });
517
+ }
518
+ stage = "context";
519
+ ctx = await options.createContext({
520
+ capability: registeredCapability,
521
+ name: registeredCapability.name,
522
+ principal: invocation.principal,
523
+ input,
524
+ } as AgentCapabilityContextRequest<Definitions, Principal>);
525
+ return ctx;
526
+ },
527
+ operation: {
528
+ name: `beignet.agent_capability ${registeredCapability.name}`,
529
+ type: "agentCapability",
530
+ kind: "internal",
531
+ attributes: {
532
+ "beignet.agent_capability.name": registeredCapability.name,
533
+ },
534
+ metricAttributes: {
535
+ "beignet.agent_capability.name": registeredCapability.name,
536
+ },
537
+ },
538
+ async run(resolvedCtx) {
539
+ ctx = resolvedCtx;
540
+ if (options.instrumentation === undefined) {
541
+ instrumentation = createProviderInstrumentation(
542
+ instrumentationTarget(ctx),
543
+ instrumentationConfig,
544
+ );
545
+ instrumentation.custom({
546
+ name: "agentCapability.started",
547
+ label: `Agent capability ${registeredCapability.name}`,
548
+ summary: "Capability execution started",
549
+ details: {
550
+ capabilityName: registeredCapability.name,
551
+ phase: "start",
552
+ },
553
+ });
554
+ }
555
+
556
+ stage = "handler";
557
+ const rawOutput = await runtimeCapability.handle({
558
+ capability: runtimeCapability,
559
+ ctx,
560
+ principal: invocation.principal,
561
+ input,
562
+ });
563
+ stage = "output";
564
+ const output = await parseCapabilitySchema(
565
+ runtimeCapability.output,
566
+ rawOutput,
567
+ {
568
+ capabilityName: registeredCapability.name,
569
+ code: "invalid_output",
570
+ },
571
+ );
572
+ const durationMs = Date.now() - startedAt;
573
+ instrumentation.custom({
574
+ name: "agentCapability.completed",
575
+ label: `Agent capability ${registeredCapability.name}`,
576
+ summary: "Capability execution completed",
577
+ details: {
578
+ capabilityName: registeredCapability.name,
579
+ phase: "end",
580
+ durationMs,
581
+ },
582
+ });
583
+ await notifyHooks(hooks, {
584
+ name: registeredCapability.name,
585
+ phase: "end",
586
+ stage,
587
+ capability: registeredCapability,
588
+ ctx,
589
+ principal: invocation.principal,
590
+ input,
591
+ output,
592
+ durationMs,
593
+ } as AgentCapabilityRunEvent<Ctx, Principal, Definitions>);
594
+ return output;
595
+ },
596
+ });
597
+ } catch (error) {
598
+ const wrapped =
599
+ error instanceof AgentCapabilityError
600
+ ? error
601
+ : new AgentCapabilityError({
602
+ code: "execution_failed",
603
+ capabilityName: capability?.name ?? invocation.name,
604
+ message: `Agent capability "${capability?.name ?? invocation.name}" execution failed.`,
605
+ cause: error,
606
+ });
607
+ const durationMs = Date.now() - startedAt;
608
+ const observedName = capability?.name ?? "unknown";
609
+ instrumentation.custom({
610
+ name: "agentCapability.failed",
611
+ label: `Agent capability ${observedName}`,
612
+ summary: "Capability execution failed",
613
+ details: {
614
+ capabilityName: observedName,
615
+ phase: "error",
616
+ stage,
617
+ durationMs,
618
+ code: wrapped.code,
619
+ },
620
+ });
621
+ await notifyHooks(hooks, {
622
+ name: invocation.name,
623
+ phase: "error",
624
+ stage,
625
+ ...(capability ? { capability } : {}),
626
+ ctx,
627
+ principal: invocation.principal,
628
+ durationMs,
629
+ error: wrapped,
630
+ ...(hasValidatedInput ? { input } : {}),
631
+ } as AgentCapabilityRunEvent<Ctx, Principal, Definitions>);
632
+ throw wrapped;
633
+ }
634
+ }
635
+
636
+ return {
637
+ execute: executeDynamic as AgentCapabilityExecutor<
638
+ Principal,
639
+ Definitions
640
+ >["execute"],
641
+ executeDynamic,
642
+ };
643
+ }
@@ -419,7 +419,7 @@ export interface CreateUseCaseOptions<Ctx> {
419
419
  *
420
420
  * Observers run in addition to the built-in instrumentation.
421
421
  */
422
- onRun?: (event: UseCaseRunEvent<Ctx>) => void;
422
+ onRun?: (event: UseCaseRunEvent<Ctx>) => void | Promise<void>;
423
423
 
424
424
  /**
425
425
  * Built-in use-case instrumentation.
@@ -442,6 +442,18 @@ export interface CreateUseCaseOptions<Ctx> {
442
442
  validate?: boolean | { input?: boolean; output?: boolean };
443
443
  }
444
444
 
445
+ function notifyUseCaseRunObserver<Ctx>(
446
+ observer: CreateUseCaseOptions<Ctx>["onRun"],
447
+ event: UseCaseRunEvent<Ctx>,
448
+ ): void {
449
+ try {
450
+ const result = observer?.(event);
451
+ if (result) void result.catch(() => {});
452
+ } catch {
453
+ // Observers are best-effort instrumentation and cannot change execution.
454
+ }
455
+ }
456
+
445
457
  type ValidationOptions = {
446
458
  input: boolean;
447
459
  output: boolean;
@@ -615,7 +627,9 @@ class UseCaseBuilder<
615
627
  OutputSchema,
616
628
  Emits
617
629
  >,
618
- private readonly onRun?: (event: UseCaseRunEvent<Ctx>) => void,
630
+ private readonly onRun?: (
631
+ event: UseCaseRunEvent<Ctx>,
632
+ ) => void | Promise<void>,
619
633
  private readonly validation: ValidationOptions = {
620
634
  input: true,
621
635
  output: true,
@@ -753,7 +767,7 @@ class UseCaseBuilder<
753
767
  kind: useCaseKind,
754
768
  })
755
769
  : undefined;
756
- onRun?.({
770
+ notifyUseCaseRunObserver(onRun, {
757
771
  name: useCaseName,
758
772
  kind: useCaseKind,
759
773
  phase: "start",
@@ -788,7 +802,7 @@ class UseCaseBuilder<
788
802
 
789
803
  const durationMs = Date.now() - startedAt;
790
804
  instrumentation?.end(durationMs);
791
- onRun?.({
805
+ notifyUseCaseRunObserver(onRun, {
792
806
  name: useCaseName,
793
807
  kind: useCaseKind,
794
808
  phase: "end",
@@ -800,7 +814,7 @@ class UseCaseBuilder<
800
814
  } catch (err) {
801
815
  const durationMs = Date.now() - startedAt;
802
816
  instrumentation?.error(durationMs, err);
803
- onRun?.({
817
+ notifyUseCaseRunObserver(onRun, {
804
818
  name: useCaseName,
805
819
  kind: useCaseKind,
806
820
  phase: "error",