@agentuity/runtime 0.0.109 → 0.0.111

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/src/workbench.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Context, Handler } from 'hono';
1
+ import type { Context, Handler, MiddlewareHandler } from 'hono';
2
2
  import { timingSafeEqual } from 'node:crypto';
3
3
  import { toJSONSchema } from '@agentuity/server';
4
4
  import { getAgents, createAgentMiddleware } from './agent';
@@ -6,7 +6,87 @@ import { createRouter } from './router';
6
6
  import { websocket, type WebSocketConnection } from './handlers/websocket';
7
7
  import { privateContext } from './_server';
8
8
  import { getThreadProvider } from './_services';
9
- import { loadBuildMetadata, getAgentMetadataByAgentId, hasMetadata } from './_metadata';
9
+ import {
10
+ loadBuildMetadata,
11
+ getAgentMetadataByAgentId,
12
+ hasMetadata,
13
+ ensureAgentsImported,
14
+ } from './_metadata';
15
+ import { TOKENS_HEADER, DURATION_HEADER } from './_tokens';
16
+
17
+ /**
18
+ * Middleware that captures execution metadata (tokens, duration, sessionId) after the handler completes
19
+ * and saves it to thread state. Applied only to the /execute route.
20
+ */
21
+ const createWorkbenchExecutionMetadataMiddleware = (): MiddlewareHandler => {
22
+ return async (ctx, next) => {
23
+ const started = performance.now();
24
+
25
+ await next();
26
+
27
+ // After handler completes, tokens and duration headers are available
28
+ const thread = ctx.var.thread;
29
+ if (!thread) {
30
+ return;
31
+ }
32
+
33
+ // Get execution context set by the handler
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ const executionCtx = (ctx as any).var.workbenchExecution as
36
+ | { agentId: string; input: unknown; result: unknown }
37
+ | undefined;
38
+ if (!executionCtx) {
39
+ return;
40
+ }
41
+
42
+ const { agentId, input, result } = executionCtx;
43
+ const agentMessagesKey = `messages_${agentId}`;
44
+ const maxMessages = 50;
45
+
46
+ // Read tokens and duration from response headers
47
+ const tokens = ctx.res.headers.get(TOKENS_HEADER) ?? undefined;
48
+ const duration =
49
+ ctx.res.headers.get(DURATION_HEADER) ??
50
+ `${((performance.now() - started) / 1000).toFixed(1)}s`;
51
+ const sessionId = ctx.var.sessionId;
52
+
53
+ // Store input with metadata
54
+ await thread.state.push(
55
+ agentMessagesKey,
56
+ {
57
+ type: 'input',
58
+ data: input,
59
+ sessionId,
60
+ timestamp: Date.now(),
61
+ },
62
+ maxMessages
63
+ );
64
+
65
+ // Store output with metadata (tokens, duration)
66
+ if (result !== undefined && result !== null) {
67
+ await thread.state.push(
68
+ agentMessagesKey,
69
+ {
70
+ type: 'output',
71
+ data: result,
72
+ sessionId,
73
+ tokens,
74
+ duration,
75
+ timestamp: Date.now(),
76
+ },
77
+ maxMessages
78
+ );
79
+ }
80
+
81
+ // Save thread state
82
+ try {
83
+ const threadProvider = getThreadProvider();
84
+ await threadProvider.save(thread);
85
+ } catch {
86
+ ctx.var.logger?.warn('Failed to save thread state');
87
+ }
88
+ };
89
+ };
10
90
 
11
91
  export const createWorkbenchExecutionRoute = (): Handler => {
12
92
  const authHeader = process.env.AGENTUITY_WORKBENCH_APIKEY
@@ -86,32 +166,9 @@ export const createWorkbenchExecutionRoute = (): Handler => {
86
166
  result = await (agentObj as any).handler();
87
167
  }
88
168
 
89
- // Store input and output in thread state, keyed by agentId
90
- // This allows multiple agents to have separate message histories in the same thread
91
- if (ctx.var.thread) {
92
- const agentMessagesKey = `messages_${agentId}`;
93
- const maxMessages = 50;
94
-
95
- await ctx.var.thread.state.push(agentMessagesKey, { type: 'input', data: input }, maxMessages);
96
-
97
- if (result !== undefined && result !== null) {
98
- await ctx.var.thread.state.push(
99
- agentMessagesKey,
100
- { type: 'output', data: result },
101
- maxMessages
102
- );
103
- }
104
-
105
- // Manually save the thread to ensure state persists
106
- try {
107
- const threadProvider = getThreadProvider();
108
- await threadProvider.save(ctx.var.thread);
109
- } catch {
110
- ctx.var.logger?.warn('Failed to save thread state');
111
- }
112
- } else {
113
- ctx.var.logger?.warn('Thread not available in workbench execution route');
114
- }
169
+ // Store execution context for the metadata middleware to save with tokens/duration
170
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
+ (ctx as any).set('workbenchExecution', { agentId, input, result });
115
172
 
116
173
  return ctx.json({ success: true, data: result ?? null });
117
174
  } catch (error) {
@@ -268,7 +325,11 @@ export const createWorkbenchRouter = () => {
268
325
  router.get('/_agentuity/workbench/sample', createWorkbenchSampleRoute());
269
326
  router.get('/_agentuity/workbench/state', createWorkbenchStateRoute());
270
327
  router.delete('/_agentuity/workbench/state', createWorkbenchClearStateRoute());
271
- router.post('/_agentuity/workbench/execute', createWorkbenchExecutionRoute());
328
+ router.post(
329
+ '/_agentuity/workbench/execute',
330
+ createWorkbenchExecutionMetadataMiddleware(),
331
+ createWorkbenchExecutionRoute()
332
+ );
272
333
  return router;
273
334
  };
274
335
 
@@ -454,6 +515,9 @@ export const createWorkbenchMetadataRoute = (): Handler => {
454
515
  }
455
516
 
456
517
  try {
518
+ // Ensure all agents are imported so their schemas are available
519
+ await ensureAgentsImported();
520
+
457
521
  // Get runtime agents for JSON schema generation
458
522
  const agents = getAgents();
459
523
  const agentsByName = new Map();