@agentuity/runtime 0.0.94 → 0.0.96

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 (72) hide show
  1. package/AGENTS.md +3 -1
  2. package/dist/_events.d.ts +64 -0
  3. package/dist/_events.d.ts.map +1 -0
  4. package/dist/_events.js +92 -0
  5. package/dist/_events.js.map +1 -0
  6. package/dist/_idle.d.ts +1 -1
  7. package/dist/_idle.d.ts.map +1 -1
  8. package/dist/_idle.js +2 -16
  9. package/dist/_idle.js.map +1 -1
  10. package/dist/_server.d.ts +30 -13
  11. package/dist/_server.d.ts.map +1 -1
  12. package/dist/_server.js +39 -572
  13. package/dist/_server.js.map +1 -1
  14. package/dist/_services.d.ts.map +1 -1
  15. package/dist/_services.js +4 -2
  16. package/dist/_services.js.map +1 -1
  17. package/dist/_standalone.d.ts.map +1 -1
  18. package/dist/_standalone.js +2 -1
  19. package/dist/_standalone.js.map +1 -1
  20. package/dist/agent.d.ts.map +1 -1
  21. package/dist/agent.js +13 -17
  22. package/dist/agent.js.map +1 -1
  23. package/dist/app.d.ts +58 -171
  24. package/dist/app.d.ts.map +1 -1
  25. package/dist/app.js +119 -218
  26. package/dist/app.js.map +1 -1
  27. package/dist/index.d.ts +11 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +18 -3
  30. package/dist/index.js.map +1 -1
  31. package/dist/middleware.d.ts +29 -0
  32. package/dist/middleware.d.ts.map +1 -0
  33. package/dist/middleware.js +200 -0
  34. package/dist/middleware.js.map +1 -0
  35. package/dist/router.d.ts.map +1 -1
  36. package/dist/router.js +5 -2
  37. package/dist/router.js.map +1 -1
  38. package/dist/services/local/vector.d.ts.map +1 -1
  39. package/dist/services/local/vector.js +3 -2
  40. package/dist/services/local/vector.js.map +1 -1
  41. package/dist/services/thread/local.d.ts +20 -0
  42. package/dist/services/thread/local.d.ts.map +1 -0
  43. package/dist/services/thread/local.js +76 -0
  44. package/dist/services/thread/local.js.map +1 -0
  45. package/dist/session.d.ts +60 -8
  46. package/dist/session.d.ts.map +1 -1
  47. package/dist/session.js +186 -54
  48. package/dist/session.js.map +1 -1
  49. package/dist/web.d.ts +8 -0
  50. package/dist/web.d.ts.map +1 -0
  51. package/dist/web.js +66 -0
  52. package/dist/web.js.map +1 -0
  53. package/dist/workbench.d.ts +3 -0
  54. package/dist/workbench.d.ts.map +1 -1
  55. package/dist/workbench.js +300 -31
  56. package/dist/workbench.js.map +1 -1
  57. package/package.json +10 -10
  58. package/src/_events.ts +142 -0
  59. package/src/_idle.ts +2 -18
  60. package/src/_server.ts +48 -681
  61. package/src/_services.ts +4 -2
  62. package/src/_standalone.ts +2 -1
  63. package/src/agent.ts +11 -14
  64. package/src/app.ts +164 -246
  65. package/src/index.ts +42 -4
  66. package/src/middleware.ts +252 -0
  67. package/src/router.ts +6 -2
  68. package/src/services/local/vector.ts +3 -2
  69. package/src/services/thread/local.ts +106 -0
  70. package/src/session.ts +238 -59
  71. package/src/web.ts +75 -0
  72. package/src/workbench.ts +367 -30
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Middleware factories for Vite-native architecture
3
+ * Extracted from _server.ts to be used by generated entry files
4
+ */
5
+
6
+ import { createMiddleware } from 'hono/factory';
7
+ import { cors } from 'hono/cors';
8
+ import type { Env } from './app';
9
+ import type { Logger } from './logger';
10
+ import { generateId } from './session';
11
+ import { runInHTTPContext } from './_context';
12
+ import { DURATION_HEADER, TOKENS_HEADER } from './_tokens';
13
+ import { extractTraceContextFromRequest } from './otel/http';
14
+ import {
15
+ context,
16
+ SpanKind,
17
+ SpanStatusCode,
18
+ trace,
19
+ propagation,
20
+ Meter,
21
+ Tracer,
22
+ } from '@opentelemetry/api';
23
+ import { TraceState } from '@opentelemetry/core';
24
+ import * as runtimeConfig from './_config';
25
+
26
+ const SESSION_HEADER = 'x-session-id';
27
+ const THREAD_HEADER = 'x-thread-id';
28
+ const DEPLOYMENT_HEADER = 'x-deployment';
29
+
30
+ export const AGENT_CONTEXT_PROPERTIES = [
31
+ 'logger',
32
+ 'tracer',
33
+ 'sessionId',
34
+ 'kv',
35
+ 'stream',
36
+ 'vector',
37
+ 'state',
38
+ 'thread',
39
+ 'session',
40
+ 'config',
41
+ 'app',
42
+ 'waitUntil',
43
+ ] as const;
44
+
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ function installContextPropertyHelpers(c: any): void {
47
+ for (const property of AGENT_CONTEXT_PROPERTIES) {
48
+ if (Object.prototype.hasOwnProperty.call(c, property)) {
49
+ continue;
50
+ }
51
+
52
+ Object.defineProperty(c, property, {
53
+ get() {
54
+ throw new Error(
55
+ `In route handlers, use c.var.${property} instead of c.${property}. ` +
56
+ `The property '${property}' is available on AgentContext (for agent handlers) ` +
57
+ `but must be accessed via c.var in HonoContext (route handlers).`
58
+ );
59
+ },
60
+ configurable: true,
61
+ enumerable: false,
62
+ });
63
+ }
64
+ }
65
+
66
+ export interface MiddlewareConfig {
67
+ logger: Logger;
68
+ tracer: Tracer;
69
+ meter: Meter;
70
+ corsOptions?: Parameters<typeof cors>[0];
71
+ }
72
+
73
+ /**
74
+ * Create base middleware that sets up context variables
75
+ */
76
+ export function createBaseMiddleware(config: MiddlewareConfig) {
77
+ return createMiddleware<Env>(async (c, next) => {
78
+ c.set('logger', config.logger);
79
+ c.set('tracer', config.tracer);
80
+ c.set('meter', config.meter);
81
+
82
+ // Import services dynamically to avoid circular deps
83
+ const { getServices } = await import('./_services');
84
+ const { getAppState } = await import('./app');
85
+
86
+ c.set('app', getAppState());
87
+
88
+ const services = getServices();
89
+ c.set('kv', services.kv);
90
+ c.set('stream', services.stream);
91
+ c.set('vector', services.vector);
92
+
93
+ installContextPropertyHelpers(c);
94
+
95
+ const isWebSocket = c.req.header('upgrade')?.toLowerCase() === 'websocket';
96
+ const skipLogging = c.req.path.startsWith('/_agentuity/');
97
+ const started = performance.now();
98
+
99
+ if (!skipLogging) {
100
+ config.logger.debug('%s %s started', c.req.method, c.req.path);
101
+ }
102
+
103
+ await runInHTTPContext(c, next);
104
+
105
+ if (!isWebSocket) {
106
+ const endTime = performance.now();
107
+ const duration = ((endTime - started) / 1000).toFixed(1);
108
+ c.header(DURATION_HEADER, `${duration}s`);
109
+ }
110
+
111
+ if (!skipLogging && !isWebSocket) {
112
+ config.logger.debug(
113
+ '%s %s completed (%d) in %sms',
114
+ c.req.method,
115
+ c.req.path,
116
+ c.res.status,
117
+ Number(performance.now() - started).toFixed(2)
118
+ );
119
+ }
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Create CORS middleware
125
+ */
126
+ export function createCorsMiddleware(corsOptions?: Parameters<typeof cors>[0]) {
127
+ return cors({
128
+ origin: corsOptions?.origin ?? ((origin) => origin),
129
+ allowHeaders: corsOptions?.allowHeaders ?? [
130
+ 'Content-Type',
131
+ 'Authorization',
132
+ 'Accept',
133
+ 'Origin',
134
+ 'X-Requested-With',
135
+ THREAD_HEADER,
136
+ ],
137
+ allowMethods: ['POST', 'GET', 'OPTIONS', 'HEAD', 'PUT', 'DELETE', 'PATCH'],
138
+ exposeHeaders: [
139
+ 'Content-Length',
140
+ TOKENS_HEADER,
141
+ DURATION_HEADER,
142
+ THREAD_HEADER,
143
+ SESSION_HEADER,
144
+ DEPLOYMENT_HEADER,
145
+ ],
146
+ maxAge: 600,
147
+ credentials: true,
148
+ ...(corsOptions ?? {}),
149
+ });
150
+ }
151
+
152
+ /**
153
+ * Create OpenTelemetry middleware for session/thread tracking
154
+ * This is the critical middleware that creates AgentContext
155
+ */
156
+ export function createOtelMiddleware() {
157
+ return createMiddleware<Env>(async (c, next) => {
158
+ // Import providers dynamically to avoid circular deps
159
+ const { getThreadProvider, getSessionProvider } = await import('./_services');
160
+ const WaitUntilHandler = (await import('./_waituntil')).default;
161
+
162
+ const extractedContext = extractTraceContextFromRequest(c.req.raw);
163
+ const method = c.req.method;
164
+ const url = new URL(c.req.url);
165
+ const threadProvider = getThreadProvider();
166
+ const sessionProvider = getSessionProvider();
167
+
168
+ await context.with(extractedContext, async (): Promise<void> => {
169
+ const tracer = trace.getTracer('http-server');
170
+ await tracer.startActiveSpan(
171
+ `HTTP ${method}`,
172
+ {
173
+ kind: SpanKind.SERVER,
174
+ attributes: {
175
+ 'http.method': method,
176
+ 'http.host': url.host,
177
+ 'http.user_agent': c.req.header('user-agent') || '',
178
+ 'http.path': url.pathname,
179
+ },
180
+ },
181
+ async (span): Promise<void> => {
182
+ const sctx = span.spanContext();
183
+ const sessionId = sctx?.traceId ? `sess_${sctx.traceId}` : generateId('sess');
184
+
185
+ let traceState = sctx.traceState ?? new TraceState();
186
+ const projectId = runtimeConfig.getProjectId();
187
+ const orgId = runtimeConfig.getOrganizationId();
188
+ const deploymentId = runtimeConfig.getDeploymentId();
189
+ const isDevMode = runtimeConfig.isDevMode();
190
+
191
+ if (projectId) traceState = traceState.set('pid', projectId);
192
+ if (orgId) traceState = traceState.set('oid', orgId);
193
+ if (isDevMode) traceState = traceState.set('d', '1');
194
+
195
+ // Update the active context with the new trace state
196
+ // Note: SpanContext.traceState is readonly, so we update it by setting the span with a new context
197
+ trace.setSpan(
198
+ context.active(),
199
+ trace.wrapSpanContext({
200
+ ...sctx,
201
+ traceState,
202
+ })
203
+ );
204
+
205
+ const thread = await threadProvider.restore(c);
206
+ const session = await sessionProvider.restore(thread, sessionId);
207
+ const handler = new WaitUntilHandler(tracer);
208
+
209
+ c.set('sessionId', sessionId);
210
+ c.set('thread', thread);
211
+ c.set('session', session);
212
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
+ (c as any).set('waitUntilHandler', handler);
214
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
215
+ (c as any).set('agentIds', new Set<string>());
216
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
+ (c as any).set('trigger', 'api');
218
+
219
+ try {
220
+ await next();
221
+
222
+ // Save session/thread and send events
223
+ await sessionProvider.save(session);
224
+ await threadProvider.save(thread);
225
+ span.setStatus({ code: SpanStatusCode.OK });
226
+ } catch (ex) {
227
+ if (ex instanceof Error) {
228
+ span.recordException(ex);
229
+ }
230
+ span.setStatus({
231
+ code: SpanStatusCode.ERROR,
232
+ message: (ex as Error).message ?? String(ex),
233
+ });
234
+ throw ex;
235
+ } finally {
236
+ const headers: Record<string, string> = {};
237
+ propagation.inject(context.active(), headers);
238
+ for (const key of Object.keys(headers)) {
239
+ c.header(key, headers[key]);
240
+ }
241
+ const traceId = sctx?.traceId || sessionId.replace(/^sess_/, '');
242
+ c.header(SESSION_HEADER, `sess_${traceId}`);
243
+ if (deploymentId) {
244
+ c.header(DEPLOYMENT_HEADER, deploymentId);
245
+ }
246
+ span.end();
247
+ }
248
+ }
249
+ );
250
+ });
251
+ });
252
+ }
package/src/router.ts CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  type Env as HonoEnv,
10
10
  } from 'hono';
11
11
  import { stream as honoStream, streamSSE as honoStreamSSE } from 'hono/streaming';
12
- import { upgradeWebSocket as honoUpgradeWebSocket } from 'hono/bun';
12
+ import { upgradeWebSocket } from 'hono/bun';
13
13
  import { hash, returnResponse } from './_util';
14
14
  import type { Env } from './app';
15
15
  import { getAgentAsyncLocalStorage } from './_context';
@@ -487,6 +487,9 @@ export const createRouter = <E extends Env = Env, S extends Schema = Schema>():
487
487
  const asyncLocalStorage = getAgentAsyncLocalStorage();
488
488
  const capturedContext = asyncLocalStorage.getStore();
489
489
 
490
+ // Set Content-Type header for streaming response detection by clients
491
+ c.header('Content-Type', 'application/octet-stream');
492
+
490
493
  return honoStream(c, async (s: any) => {
491
494
  const runInContext = async () => {
492
495
  try {
@@ -527,7 +530,8 @@ export const createRouter = <E extends Env = Env, S extends Schema = Schema>():
527
530
  handler = args[1];
528
531
  }
529
532
 
530
- const wrapper = honoUpgradeWebSocket((c: Context) => {
533
+ // Use upgradeWebSocket directly from hono/bun
534
+ const wrapper = upgradeWebSocket((c: Context) => {
531
535
  let openHandler: ((event: any) => void | Promise<void>) | undefined;
532
536
  let messageHandler: ((event: any) => void | Promise<void>) | undefined;
533
537
  let closeHandler: ((event: any) => void | Promise<void>) | undefined;
@@ -188,12 +188,13 @@ export class LocalVectorStorage implements VectorStorage {
188
188
  }>;
189
189
 
190
190
  // If no vectors exist, return empty results
191
- if (rows.length === 0) {
191
+ const row = rows[0];
192
+ if (!row) {
192
193
  return [];
193
194
  }
194
195
 
195
196
  // Detect dimensionality from first stored vector
196
- const firstEmbedding = JSON.parse(rows[0].embedding);
197
+ const firstEmbedding = JSON.parse(row.embedding);
197
198
  const dimensions = firstEmbedding.length;
198
199
 
199
200
  // Generate query embedding with matching dimensions
@@ -0,0 +1,106 @@
1
+ import type { Context } from 'hono';
2
+ import type { Database } from 'bun:sqlite';
3
+ import type { AppState } from '../../index';
4
+ import type { Env } from '../../app';
5
+ import {
6
+ DefaultThread,
7
+ DefaultThreadIDProvider,
8
+ validateThreadIdOrThrow,
9
+ type Thread,
10
+ type ThreadIDProvider,
11
+ type ThreadProvider,
12
+ } from '../../session';
13
+ import { getLocalDB } from '../local/_db';
14
+
15
+ /**
16
+ * Local thread provider with SQLite persistence.
17
+ * Stores thread state in local DB for development and testing.
18
+ * Suitable for local development and testing with persistence across requests.
19
+ */
20
+ export class LocalThreadProvider implements ThreadProvider {
21
+ private appState: AppState | null = null;
22
+ private threadIDProvider: ThreadIDProvider = new DefaultThreadIDProvider();
23
+ private db: Database | null = null;
24
+
25
+ async initialize(appState: AppState): Promise<void> {
26
+ this.appState = appState;
27
+ this.db = getLocalDB();
28
+
29
+ // Create threads table if it doesn't exist
30
+ this.db.run(`
31
+ CREATE TABLE IF NOT EXISTS threads (
32
+ id TEXT PRIMARY KEY,
33
+ state TEXT NOT NULL,
34
+ updated_at INTEGER NOT NULL
35
+ )
36
+ `);
37
+ }
38
+
39
+ setThreadIDProvider(provider: ThreadIDProvider): void {
40
+ this.threadIDProvider = provider;
41
+ }
42
+
43
+ async restore(ctx: Context<Env>): Promise<Thread> {
44
+ if (this.appState === null || this.db === null) {
45
+ throw new Error(
46
+ 'LocalThreadProvider.restore called before initialize(): appState/db not set; call initialize(appState) first'
47
+ );
48
+ }
49
+
50
+ const threadId = await this.threadIDProvider.getThreadId(this.appState, ctx);
51
+ validateThreadIdOrThrow(threadId);
52
+
53
+ // Try to restore state from DB
54
+ const row = this.db
55
+ .query<{ state: string }, [string]>('SELECT state FROM threads WHERE id = ?')
56
+ .get(threadId);
57
+ const initialStateJson = row?.state;
58
+
59
+ // Create thread with restored state
60
+ const thread = new DefaultThread(this, threadId, initialStateJson);
61
+
62
+ // Populate thread state from restored data
63
+ if (initialStateJson) {
64
+ try {
65
+ const data = JSON.parse(initialStateJson);
66
+ for (const [key, value] of Object.entries(data)) {
67
+ thread.state.set(key, value);
68
+ }
69
+ } catch {
70
+ // Continue with empty state if parsing fails
71
+ }
72
+ }
73
+
74
+ return thread;
75
+ }
76
+
77
+ async save(thread: Thread): Promise<void> {
78
+ if (!this.db || !(thread instanceof DefaultThread)) {
79
+ return;
80
+ }
81
+
82
+ // Only save if state was modified
83
+ if (!thread.isDirty()) {
84
+ return;
85
+ }
86
+
87
+ const stateJson = thread.getSerializedState();
88
+ const now = Date.now();
89
+
90
+ // Upsert thread state
91
+ this.db.run(
92
+ `INSERT INTO threads (id, state, updated_at) VALUES (?, ?, ?)
93
+ ON CONFLICT(id) DO UPDATE SET state = ?, updated_at = ?`,
94
+ [thread.id, stateJson, now, stateJson, now]
95
+ );
96
+ }
97
+
98
+ async destroy(thread: Thread): Promise<void> {
99
+ if (!this.db) {
100
+ return;
101
+ }
102
+
103
+ // Delete thread from DB
104
+ this.db.run('DELETE FROM threads WHERE id = ?', [thread.id]);
105
+ }
106
+ }