@amodalai/runtime 0.2.0 → 0.2.1

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 (209) hide show
  1. package/dist/src/__fixtures__/README.md +4 -0
  2. package/dist/src/__fixtures__/e2e.test.d.ts +6 -0
  3. package/dist/src/__fixtures__/e2e.test.js +211 -0
  4. package/dist/src/__fixtures__/e2e.test.js.map +1 -0
  5. package/dist/src/__fixtures__/smoke-agent/automations/delivery-callback-test.json +9 -0
  6. package/dist/src/__fixtures__/smoke-agent/connections/mock-mcp/spec.json +1 -1
  7. package/dist/src/__fixtures__/smoke.test.js +715 -29
  8. package/dist/src/__fixtures__/smoke.test.js.map +1 -1
  9. package/dist/src/__fixtures__/test-env.d.ts +27 -0
  10. package/dist/src/__fixtures__/test-env.js +64 -0
  11. package/dist/src/__fixtures__/test-env.js.map +1 -0
  12. package/dist/src/__fixtures__/test-helpers.d.ts +30 -0
  13. package/dist/src/__fixtures__/test-helpers.js +120 -0
  14. package/dist/src/__fixtures__/test-helpers.js.map +1 -0
  15. package/dist/src/agent/agent-types.d.ts +22 -0
  16. package/dist/src/agent/agent-types.js.map +1 -1
  17. package/dist/src/agent/automation-bridge.d.ts +9 -0
  18. package/dist/src/agent/automation-bridge.js +26 -0
  19. package/dist/src/agent/automation-bridge.js.map +1 -1
  20. package/dist/src/agent/automation-bridge.test.js +63 -0
  21. package/dist/src/agent/automation-bridge.test.js.map +1 -1
  22. package/dist/src/agent/local-server.d.ts +0 -7
  23. package/dist/src/agent/local-server.js +230 -86
  24. package/dist/src/agent/local-server.js.map +1 -1
  25. package/dist/src/agent/local-server.test.js +14 -8
  26. package/dist/src/agent/local-server.test.js.map +1 -1
  27. package/dist/src/agent/loop-types.d.ts +81 -2
  28. package/dist/src/agent/loop-types.js +4 -0
  29. package/dist/src/agent/loop-types.js.map +1 -1
  30. package/dist/src/agent/loop.js +16 -3
  31. package/dist/src/agent/loop.js.map +1 -1
  32. package/dist/src/agent/loop.test.js +572 -8
  33. package/dist/src/agent/loop.test.js.map +1 -1
  34. package/dist/src/agent/proactive/delivery-router.d.ts +68 -0
  35. package/dist/src/agent/proactive/delivery-router.js +337 -0
  36. package/dist/src/agent/proactive/delivery-router.js.map +1 -0
  37. package/dist/src/agent/proactive/delivery-router.test.d.ts +6 -0
  38. package/dist/src/agent/proactive/delivery-router.test.js +455 -0
  39. package/dist/src/agent/proactive/delivery-router.test.js.map +1 -0
  40. package/dist/src/agent/proactive/proactive-runner.d.ts +23 -1
  41. package/dist/src/agent/proactive/proactive-runner.js +42 -10
  42. package/dist/src/agent/proactive/proactive-runner.js.map +1 -1
  43. package/dist/src/agent/proactive/proactive-runner.test.js +0 -2
  44. package/dist/src/agent/proactive/proactive-runner.test.js.map +1 -1
  45. package/dist/src/agent/routes/admin-chat-abort.test.d.ts +6 -0
  46. package/dist/src/agent/routes/admin-chat-abort.test.js +206 -0
  47. package/dist/src/agent/routes/admin-chat-abort.test.js.map +1 -0
  48. package/dist/src/agent/routes/admin-chat.js +0 -2
  49. package/dist/src/agent/routes/admin-chat.js.map +1 -1
  50. package/dist/src/agent/routes/task.test.js +0 -2
  51. package/dist/src/agent/routes/task.test.js.map +1 -1
  52. package/dist/src/agent/snapshot-server.js +0 -2
  53. package/dist/src/agent/snapshot-server.js.map +1 -1
  54. package/dist/src/agent/states/compacting.js +5 -3
  55. package/dist/src/agent/states/compacting.js.map +1 -1
  56. package/dist/src/agent/states/confirming.js +3 -0
  57. package/dist/src/agent/states/confirming.js.map +1 -1
  58. package/dist/src/agent/states/dispatching.js +45 -1
  59. package/dist/src/agent/states/dispatching.js.map +1 -1
  60. package/dist/src/agent/states/executing.js +225 -81
  61. package/dist/src/agent/states/executing.js.map +1 -1
  62. package/dist/src/agent/states/streaming.js +14 -0
  63. package/dist/src/agent/states/streaming.js.map +1 -1
  64. package/dist/src/agent/states/thinking.d.ts +1 -1
  65. package/dist/src/agent/states/thinking.js +246 -29
  66. package/dist/src/agent/states/thinking.js.map +1 -1
  67. package/dist/src/agent/token-estimate.d.ts +20 -6
  68. package/dist/src/agent/token-estimate.js +24 -3
  69. package/dist/src/agent/token-estimate.js.map +1 -1
  70. package/dist/src/agent/token-estimate.test.d.ts +6 -0
  71. package/dist/src/agent/token-estimate.test.js +44 -0
  72. package/dist/src/agent/token-estimate.test.js.map +1 -0
  73. package/dist/src/api/create-agent.js +0 -3
  74. package/dist/src/api/create-agent.js.map +1 -1
  75. package/dist/src/api/types.d.ts +0 -2
  76. package/dist/src/env-ref.d.ts +13 -0
  77. package/dist/src/env-ref.js +31 -0
  78. package/dist/src/env-ref.js.map +1 -0
  79. package/dist/src/env-ref.test.d.ts +6 -0
  80. package/dist/src/env-ref.test.js +34 -0
  81. package/dist/src/env-ref.test.js.map +1 -0
  82. package/dist/src/errors.d.ts +15 -0
  83. package/dist/src/errors.js +22 -0
  84. package/dist/src/errors.js.map +1 -1
  85. package/dist/src/errors.test.js +2 -2
  86. package/dist/src/errors.test.js.map +1 -1
  87. package/dist/src/events/event-bus.d.ts +54 -0
  88. package/dist/src/events/event-bus.js +84 -0
  89. package/dist/src/events/event-bus.js.map +1 -0
  90. package/dist/src/events/event-bus.test.d.ts +6 -0
  91. package/dist/src/events/event-bus.test.js +112 -0
  92. package/dist/src/events/event-bus.test.js.map +1 -0
  93. package/dist/src/events/events-route.d.ts +36 -0
  94. package/dist/src/events/events-route.js +80 -0
  95. package/dist/src/events/events-route.js.map +1 -0
  96. package/dist/src/events/events-route.test.d.ts +6 -0
  97. package/dist/src/events/events-route.test.js +134 -0
  98. package/dist/src/events/events-route.test.js.map +1 -0
  99. package/dist/src/events/store-event-wrapper.d.ts +19 -0
  100. package/dist/src/events/store-event-wrapper.js +57 -0
  101. package/dist/src/events/store-event-wrapper.js.map +1 -0
  102. package/dist/src/events/store-event-wrapper.test.d.ts +6 -0
  103. package/dist/src/events/store-event-wrapper.test.js +91 -0
  104. package/dist/src/events/store-event-wrapper.test.js.map +1 -0
  105. package/dist/src/middleware/auth.d.ts +0 -2
  106. package/dist/src/middleware/auth.js.map +1 -1
  107. package/dist/src/providers/search-provider.d.ts +64 -0
  108. package/dist/src/providers/search-provider.js +174 -0
  109. package/dist/src/providers/search-provider.js.map +1 -0
  110. package/dist/src/providers/types.d.ts +8 -0
  111. package/dist/src/routes/ai-stream.d.ts +15 -0
  112. package/dist/src/routes/ai-stream.js +9 -0
  113. package/dist/src/routes/ai-stream.js.map +1 -1
  114. package/dist/src/routes/chat-stream.d.ts +6 -0
  115. package/dist/src/routes/chat-stream.js +2 -0
  116. package/dist/src/routes/chat-stream.js.map +1 -1
  117. package/dist/src/routes/chat.d.ts +6 -0
  118. package/dist/src/routes/chat.js +2 -0
  119. package/dist/src/routes/chat.js.map +1 -1
  120. package/dist/src/routes/session-resolver.d.ts +5 -0
  121. package/dist/src/routes/session-resolver.js +1 -15
  122. package/dist/src/routes/session-resolver.js.map +1 -1
  123. package/dist/src/routes/session-resolver.test.js +7 -6
  124. package/dist/src/routes/session-resolver.test.js.map +1 -1
  125. package/dist/src/server.d.ts +6 -0
  126. package/dist/src/server.js +2 -0
  127. package/dist/src/server.js.map +1 -1
  128. package/dist/src/session/drizzle-session-store.d.ts +56 -0
  129. package/dist/src/session/drizzle-session-store.js +203 -0
  130. package/dist/src/session/drizzle-session-store.js.map +1 -0
  131. package/dist/src/session/manager.d.ts +6 -3
  132. package/dist/src/session/manager.js +46 -16
  133. package/dist/src/session/manager.js.map +1 -1
  134. package/dist/src/session/manager.test.js +12 -18
  135. package/dist/src/session/manager.test.js.map +1 -1
  136. package/dist/src/session/pglite-session-store.d.ts +23 -0
  137. package/dist/src/session/pglite-session-store.js +70 -0
  138. package/dist/src/session/pglite-session-store.js.map +1 -0
  139. package/dist/src/session/postgres-session-store.d.ts +44 -0
  140. package/dist/src/session/postgres-session-store.js +138 -0
  141. package/dist/src/session/postgres-session-store.js.map +1 -0
  142. package/dist/src/session/session-builder.d.ts +0 -2
  143. package/dist/src/session/session-builder.js +22 -2
  144. package/dist/src/session/session-builder.js.map +1 -1
  145. package/dist/src/session/session-builder.test.js +0 -2
  146. package/dist/src/session/session-builder.test.js.map +1 -1
  147. package/dist/src/session/session-store-selector.d.ts +49 -0
  148. package/dist/src/session/session-store-selector.js +60 -0
  149. package/dist/src/session/session-store-selector.js.map +1 -0
  150. package/dist/src/session/session-store-selector.test.d.ts +6 -0
  151. package/dist/src/session/session-store-selector.test.js +79 -0
  152. package/dist/src/session/session-store-selector.test.js.map +1 -0
  153. package/dist/src/session/store.d.ts +146 -32
  154. package/dist/src/session/store.js +126 -138
  155. package/dist/src/session/store.js.map +1 -1
  156. package/dist/src/session/store.test.js +385 -107
  157. package/dist/src/session/store.test.js.map +1 -1
  158. package/dist/src/session/tool-context-factory.d.ts +3 -2
  159. package/dist/src/session/tool-context-factory.js +1 -2
  160. package/dist/src/session/tool-context-factory.js.map +1 -1
  161. package/dist/src/session/tool-context-factory.test.js +1 -4
  162. package/dist/src/session/tool-context-factory.test.js.map +1 -1
  163. package/dist/src/session/types.d.ts +13 -6
  164. package/dist/src/stores/schema.d.ts +0 -34
  165. package/dist/src/stores/schema.js +6 -4
  166. package/dist/src/stores/schema.js.map +1 -1
  167. package/dist/src/tools/admin-file-tools.d.ts +29 -0
  168. package/dist/src/tools/admin-file-tools.js +525 -11
  169. package/dist/src/tools/admin-file-tools.js.map +1 -1
  170. package/dist/src/tools/admin-file-tools.test.js +373 -4
  171. package/dist/src/tools/admin-file-tools.test.js.map +1 -1
  172. package/dist/src/tools/custom-tool-adapter.test.js +0 -1
  173. package/dist/src/tools/custom-tool-adapter.test.js.map +1 -1
  174. package/dist/src/tools/dispatch-tool.d.ts +4 -4
  175. package/dist/src/tools/fetch-url-tool.d.ts +23 -0
  176. package/dist/src/tools/fetch-url-tool.js +333 -0
  177. package/dist/src/tools/fetch-url-tool.js.map +1 -0
  178. package/dist/src/tools/fetch-url-tool.test.d.ts +6 -0
  179. package/dist/src/tools/fetch-url-tool.test.js +228 -0
  180. package/dist/src/tools/fetch-url-tool.test.js.map +1 -0
  181. package/dist/src/tools/mcp-tool-adapter.test.js +0 -1
  182. package/dist/src/tools/mcp-tool-adapter.test.js.map +1 -1
  183. package/dist/src/tools/registry.test.js +0 -1
  184. package/dist/src/tools/registry.test.js.map +1 -1
  185. package/dist/src/tools/request-tool.test.js +0 -1
  186. package/dist/src/tools/request-tool.test.js.map +1 -1
  187. package/dist/src/tools/store-tools.test.js +0 -1
  188. package/dist/src/tools/store-tools.test.js.map +1 -1
  189. package/dist/src/tools/types.d.ts +20 -2
  190. package/dist/src/tools/web-search-tool.d.ts +31 -0
  191. package/dist/src/tools/web-search-tool.js +170 -0
  192. package/dist/src/tools/web-search-tool.js.map +1 -0
  193. package/dist/src/tools/web-search-tool.test.d.ts +6 -0
  194. package/dist/src/tools/web-search-tool.test.js +153 -0
  195. package/dist/src/tools/web-search-tool.test.js.map +1 -0
  196. package/dist/src/tools/web-tools-shared.d.ts +21 -0
  197. package/dist/src/tools/web-tools-shared.js +32 -0
  198. package/dist/src/tools/web-tools-shared.js.map +1 -0
  199. package/dist/src/types.d.ts +20 -0
  200. package/dist/src/types.js +13 -0
  201. package/dist/src/types.js.map +1 -1
  202. package/dist/tsconfig.tsbuildinfo +1 -1
  203. package/package.json +17 -3
  204. package/dist/src/agent/session-store.d.ts +0 -71
  205. package/dist/src/agent/session-store.js +0 -151
  206. package/dist/src/agent/session-store.js.map +0 -1
  207. package/dist/src/session/admin-file-tools.d.ts +0 -136
  208. package/dist/src/session/admin-file-tools.js +0 -240
  209. package/dist/src/session/admin-file-tools.js.map +0 -1
@@ -0,0 +1,134 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { describe, it, expect, afterEach } from 'vitest';
7
+ import express from 'express';
8
+ import { RuntimeEventBus } from './event-bus.js';
9
+ import { createEventsRouter } from './events-route.js';
10
+ function startServer(bus) {
11
+ const app = express();
12
+ app.use(createEventsRouter({ bus, heartbeatMs: 60_000 }));
13
+ return new Promise((resolve) => {
14
+ const server = app.listen(0, () => {
15
+ const address = server.address();
16
+ const port = address && typeof address === 'object' ? address.port : 0;
17
+ resolve({ port, server, bus });
18
+ });
19
+ });
20
+ }
21
+ function closeServer(server) {
22
+ return new Promise((resolve) => {
23
+ server.close(() => resolve());
24
+ });
25
+ }
26
+ async function collectEvents(url, expected, opts = {}) {
27
+ const headers = { Accept: 'text/event-stream' };
28
+ if (opts.lastEventId)
29
+ headers['Last-Event-ID'] = opts.lastEventId;
30
+ const controller = new AbortController();
31
+ const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? 2000);
32
+ const res = await fetch(url, { headers, signal: controller.signal });
33
+ if (!res.body)
34
+ throw new Error('no response body');
35
+ const reader = res.body.getReader();
36
+ const decoder = new TextDecoder();
37
+ const collected = [];
38
+ let buffer = '';
39
+ try {
40
+ while (collected.length < expected) {
41
+ const { done, value } = await reader.read();
42
+ if (done)
43
+ break;
44
+ buffer += decoder.decode(value, { stream: true });
45
+ let idx;
46
+ while ((idx = buffer.indexOf('\n\n')) !== -1) {
47
+ const frame = buffer.slice(0, idx);
48
+ buffer = buffer.slice(idx + 2);
49
+ const dataLine = frame.split('\n').find((l) => l.startsWith('data:'));
50
+ if (dataLine) {
51
+ const json = dataLine.slice(5).trim();
52
+ collected.push(JSON.parse(json));
53
+ if (collected.length >= expected)
54
+ break;
55
+ }
56
+ }
57
+ }
58
+ }
59
+ finally {
60
+ clearTimeout(timeout);
61
+ controller.abort();
62
+ reader.cancel().catch(() => { });
63
+ }
64
+ return collected;
65
+ }
66
+ describe('events SSE route', () => {
67
+ let harness = null;
68
+ afterEach(async () => {
69
+ if (harness) {
70
+ await closeServer(harness.server);
71
+ harness = null;
72
+ }
73
+ });
74
+ it('streams emitted events to connected clients', async () => {
75
+ const bus = new RuntimeEventBus();
76
+ harness = await startServer(bus);
77
+ const url = `http://127.0.0.1:${String(harness.port)}/api/events`;
78
+ const eventsPromise = collectEvents(url, 2, { timeoutMs: 2000 });
79
+ setTimeout(() => {
80
+ bus.emit({ type: 'session_created', sessionId: 's1', appId: 'local' });
81
+ bus.emit({ type: 'manifest_changed' });
82
+ }, 50);
83
+ const events = await eventsPromise;
84
+ expect(events).toHaveLength(2);
85
+ expect(events[0]?.['type']).toBe('session_created');
86
+ expect(events[0]?.['seq']).toBe(1);
87
+ expect(events[1]?.['type']).toBe('manifest_changed');
88
+ expect(events[1]?.['seq']).toBe(2);
89
+ });
90
+ it('replays buffered events matching Last-Event-ID', async () => {
91
+ const bus = new RuntimeEventBus();
92
+ harness = await startServer(bus);
93
+ const url = `http://127.0.0.1:${String(harness.port)}/api/events`;
94
+ bus.emit({ type: 'session_created', sessionId: 's1', appId: 'local' });
95
+ bus.emit({ type: 'session_created', sessionId: 's2', appId: 'local' });
96
+ bus.emit({ type: 'session_created', sessionId: 's3', appId: 'local' });
97
+ const events = await collectEvents(url, 2, { lastEventId: '1', timeoutMs: 2000 });
98
+ expect(events).toHaveLength(2);
99
+ expect(events[0]?.['seq']).toBe(2);
100
+ expect(events[1]?.['seq']).toBe(3);
101
+ });
102
+ it('includes id and event headers in SSE frames', async () => {
103
+ const bus = new RuntimeEventBus();
104
+ harness = await startServer(bus);
105
+ const url = `http://127.0.0.1:${String(harness.port)}/api/events`;
106
+ const controller = new AbortController();
107
+ const timeout = setTimeout(() => controller.abort(), 2000);
108
+ const res = await fetch(url, {
109
+ headers: { Accept: 'text/event-stream' },
110
+ signal: controller.signal,
111
+ });
112
+ if (!res.body)
113
+ throw new Error('no response body');
114
+ const reader = res.body.getReader();
115
+ const decoder = new TextDecoder();
116
+ setTimeout(() => {
117
+ bus.emit({ type: 'manifest_changed' });
118
+ }, 50);
119
+ let raw = '';
120
+ while (!raw.includes('\n\n')) {
121
+ const { value, done } = await reader.read();
122
+ if (done)
123
+ break;
124
+ raw += decoder.decode(value, { stream: true });
125
+ }
126
+ clearTimeout(timeout);
127
+ controller.abort();
128
+ reader.cancel().catch(() => { });
129
+ expect(raw).toContain('id: 1');
130
+ expect(raw).toContain('event: manifest_changed');
131
+ expect(raw).toContain('data: {"type":"manifest_changed"');
132
+ });
133
+ });
134
+ //# sourceMappingURL=events-route.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events-route.test.js","sourceRoot":"","sources":["../../../src/events/events-route.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * Wraps a `StoreBackend` so every successful write emits a `store_updated`
8
+ * event to the runtime event bus. Read operations pass through unchanged.
9
+ *
10
+ * Instrumenting at the backend level (rather than each call site: tools,
11
+ * REST routes, admin file tools, task execution) means we cover every
12
+ * write path through one seam.
13
+ */
14
+ import type { StoreBackend, RuntimeEventPayload } from '@amodalai/types';
15
+ interface StoreEventSink {
16
+ emit: (payload: RuntimeEventPayload) => unknown;
17
+ }
18
+ export declare function wrapStoreBackendWithEvents(inner: StoreBackend, bus: StoreEventSink): StoreBackend;
19
+ export {};
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export function wrapStoreBackendWithEvents(inner, bus) {
7
+ return {
8
+ initialize(stores) {
9
+ return inner.initialize(stores);
10
+ },
11
+ get(appId, store, key) {
12
+ return inner.get(appId, store, key);
13
+ },
14
+ list(appId, store, opts) {
15
+ return inner.list(appId, store, opts);
16
+ },
17
+ history(appId, store, key) {
18
+ return inner.history(appId, store, key);
19
+ },
20
+ async put(appId, store, key, payload, meta) {
21
+ const result = await inner.put(appId, store, key, payload, meta);
22
+ bus.emit({
23
+ type: 'store_updated',
24
+ storeName: store,
25
+ operation: 'put',
26
+ });
27
+ return result;
28
+ },
29
+ async delete(appId, store, key) {
30
+ const result = await inner.delete(appId, store, key);
31
+ if (result) {
32
+ bus.emit({
33
+ type: 'store_updated',
34
+ storeName: store,
35
+ operation: 'delete',
36
+ });
37
+ }
38
+ return result;
39
+ },
40
+ async purgeExpired(appId, store) {
41
+ const count = await inner.purgeExpired(appId, store);
42
+ if (count > 0) {
43
+ bus.emit({
44
+ type: 'store_updated',
45
+ storeName: store ?? '*',
46
+ operation: 'delete',
47
+ count,
48
+ });
49
+ }
50
+ return count;
51
+ },
52
+ close() {
53
+ return inner.close();
54
+ },
55
+ };
56
+ }
57
+ //# sourceMappingURL=store-event-wrapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-event-wrapper.js","sourceRoot":"","sources":["../../../src/events/store-event-wrapper.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA0BH,MAAM,UAAU,0BAA0B,CACxC,KAAmB,EACnB,GAAmB;IAEnB,OAAO;QACL,UAAU,CAAC,MAAqB;YAC9B,OAAO,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAClC,CAAC;QAED,GAAG,CAAC,KAAa,EAAE,KAAa,EAAE,GAAW;YAC3C,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QACtC,CAAC;QAED,IAAI,CAAC,KAAa,EAAE,KAAa,EAAE,IAAuB;YACxD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QACxC,CAAC;QAED,OAAO,CAAC,KAAa,EAAE,KAAa,EAAE,GAAW;YAC/C,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QAC1C,CAAC;QAED,KAAK,CAAC,GAAG,CACP,KAAa,EACb,KAAa,EACb,GAAW,EACX,OAAgC,EAChC,IAAgC;YAEhC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;YACjE,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,eAAe;gBACrB,SAAS,EAAE,KAAK;gBAChB,SAAS,EAAE,KAAK;aACjB,CAAC,CAAC;YACH,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,KAAa,EAAE,GAAW;YACpD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;YACrD,IAAI,MAAM,EAAE,CAAC;gBACX,GAAG,CAAC,IAAI,CAAC;oBACP,IAAI,EAAE,eAAe;oBACrB,SAAS,EAAE,KAAK;oBAChB,SAAS,EAAE,QAAQ;iBACpB,CAAC,CAAC;YACL,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,KAAK,CAAC,YAAY,CAAC,KAAa,EAAE,KAAc;YAC9C,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YACrD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBACd,GAAG,CAAC,IAAI,CAAC;oBACP,IAAI,EAAE,eAAe;oBACrB,SAAS,EAAE,KAAK,IAAI,GAAG;oBACvB,SAAS,EAAE,QAAQ;oBACnB,KAAK;iBACN,CAAC,CAAC;YACL,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,KAAK;YACH,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export {};
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { describe, it, expect, vi } from 'vitest';
7
+ import { wrapStoreBackendWithEvents } from './store-event-wrapper.js';
8
+ import { RuntimeEventBus } from './event-bus.js';
9
+ function makeStubBackend(overrides = {}) {
10
+ return {
11
+ initialize: vi.fn().mockResolvedValue(undefined),
12
+ get: vi.fn().mockResolvedValue(null),
13
+ list: vi.fn().mockResolvedValue({ rows: [], total: 0 }),
14
+ history: vi.fn().mockResolvedValue([]),
15
+ put: vi.fn().mockResolvedValue({ version: 1, stale: false }),
16
+ delete: vi.fn().mockResolvedValue(true),
17
+ purgeExpired: vi.fn().mockResolvedValue(0),
18
+ close: vi.fn().mockResolvedValue(undefined),
19
+ ...overrides,
20
+ };
21
+ }
22
+ describe('wrapStoreBackendWithEvents', () => {
23
+ it('emits store_updated on successful put', async () => {
24
+ const bus = new RuntimeEventBus();
25
+ const emits = [];
26
+ bus.subscribe((e) => emits.push({ type: e.type, storeName: e.storeName, operation: e.operation }));
27
+ const wrapped = wrapStoreBackendWithEvents(makeStubBackend(), bus);
28
+ await wrapped.put('local', 'leads', 'k1', { x: 1 }, {});
29
+ expect(emits).toHaveLength(1);
30
+ expect(emits[0]).toEqual({ type: 'store_updated', storeName: 'leads', operation: 'put' });
31
+ });
32
+ it('emits store_updated on successful delete', async () => {
33
+ const bus = new RuntimeEventBus();
34
+ const emits = [];
35
+ bus.subscribe((e) => emits.push(e));
36
+ const wrapped = wrapStoreBackendWithEvents(makeStubBackend(), bus);
37
+ await wrapped.delete('local', 'leads', 'k1');
38
+ expect(emits).toHaveLength(1);
39
+ expect(emits[0]?.['type']).toBe('store_updated');
40
+ expect(emits[0]?.['operation']).toBe('delete');
41
+ });
42
+ it('does NOT emit on delete that returns false (not found)', async () => {
43
+ const bus = new RuntimeEventBus();
44
+ const emits = [];
45
+ bus.subscribe((e) => emits.push(e));
46
+ const wrapped = wrapStoreBackendWithEvents(makeStubBackend({ delete: vi.fn().mockResolvedValue(false) }), bus);
47
+ await wrapped.delete('local', 'leads', 'missing');
48
+ expect(emits).toHaveLength(0);
49
+ });
50
+ it('emits store_updated with count on purgeExpired', async () => {
51
+ const bus = new RuntimeEventBus();
52
+ const emits = [];
53
+ bus.subscribe((e) => emits.push(e));
54
+ const wrapped = wrapStoreBackendWithEvents(makeStubBackend({ purgeExpired: vi.fn().mockResolvedValue(3) }), bus);
55
+ await wrapped.purgeExpired('local', 'leads');
56
+ expect(emits).toHaveLength(1);
57
+ expect(emits[0]?.['count']).toBe(3);
58
+ expect(emits[0]?.['storeName']).toBe('leads');
59
+ });
60
+ it('does NOT emit on purgeExpired that deletes nothing', async () => {
61
+ const bus = new RuntimeEventBus();
62
+ const emits = [];
63
+ bus.subscribe((e) => emits.push(e));
64
+ const wrapped = wrapStoreBackendWithEvents(makeStubBackend(), bus);
65
+ await wrapped.purgeExpired('local');
66
+ expect(emits).toHaveLength(0);
67
+ });
68
+ it('passes read operations through unchanged without emitting', async () => {
69
+ const bus = new RuntimeEventBus();
70
+ const emits = [];
71
+ bus.subscribe((e) => emits.push(e));
72
+ const inner = makeStubBackend();
73
+ const wrapped = wrapStoreBackendWithEvents(inner, bus);
74
+ await wrapped.get('local', 'leads', 'k1');
75
+ await wrapped.list('local', 'leads');
76
+ await wrapped.history('local', 'leads', 'k1');
77
+ expect(emits).toHaveLength(0);
78
+ expect(inner.get).toHaveBeenCalled();
79
+ expect(inner.list).toHaveBeenCalled();
80
+ expect(inner.history).toHaveBeenCalled();
81
+ });
82
+ it('propagates put/delete errors without emitting', async () => {
83
+ const bus = new RuntimeEventBus();
84
+ const emits = [];
85
+ bus.subscribe((e) => emits.push(e));
86
+ const wrapped = wrapStoreBackendWithEvents(makeStubBackend({ put: vi.fn().mockRejectedValue(new Error('disk full')) }), bus);
87
+ await expect(wrapped.put('local', 'leads', 'k1', {}, {})).rejects.toThrow('disk full');
88
+ expect(emits).toHaveLength(0);
89
+ });
90
+ });
91
+ //# sourceMappingURL=store-event-wrapper.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-event-wrapper.test.js","sourceRoot":"","sources":["../../../src/events/store-event-wrapper.test.ts"],"names":[],"mappings":"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"}
@@ -12,10 +12,8 @@ export interface AuthContext {
12
12
  apiKey?: string;
13
13
  /** Raw Bearer token (JWT or ak_ key) for forwarding to platform API */
14
14
  token?: string;
15
- orgId: string;
16
15
  applicationId: string;
17
16
  authMethod: string;
18
- actor?: string;
19
17
  }
20
18
  /**
21
19
  * Get the auth context from response locals.
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkBH,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAEvC;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,GAAa;IAC1C,uEAAuE;IACvE,OAAO,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAA4B,CAAC;AACjE,CAAC"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgBH,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAEvC;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,GAAa;IAC1C,uEAAuE;IACvE,OAAO,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAA4B,CAAC;AACjE,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import type { WebToolsConfig } from '@amodalai/types';
7
+ /** Default Gemini model for search + urlContext when none is configured. */
8
+ export declare const DEFAULT_SEARCH_MODEL = "gemini-3-flash-preview";
9
+ /** A single grounded source citation (from googleSearch or urlContext). */
10
+ export interface SearchSource {
11
+ /** Fully-qualified source URL. */
12
+ uri: string;
13
+ /** Human-readable title if the provider supplied one. */
14
+ title?: string;
15
+ }
16
+ /** Result of a grounded search query. */
17
+ export interface SearchResult {
18
+ /** Synthesized answer text from the model. */
19
+ text: string;
20
+ /** Source URLs the answer was grounded in (may be empty). */
21
+ sources: SearchSource[];
22
+ }
23
+ /** Result of a grounded URL fetch. */
24
+ export interface FetchResult {
25
+ /** Extracted page content as markdown (or summary if `prompt` was provided). */
26
+ text: string;
27
+ /** URLs that were actually retrieved by urlContext (usually 1, may be 0 on failure). */
28
+ retrievedUrls: string[];
29
+ }
30
+ /** Options for a single search call. */
31
+ export interface SearchOptions {
32
+ /** Abort signal for cancellation/timeout. */
33
+ signal?: AbortSignal;
34
+ }
35
+ /** Options for a single fetch call. */
36
+ export interface FetchOptions {
37
+ /** Extraction prompt — what to pull from the page. Defaults to "render as markdown". */
38
+ prompt?: string;
39
+ /** Abort signal for cancellation/timeout. */
40
+ signal?: AbortSignal;
41
+ }
42
+ /**
43
+ * Narrow, purpose-specific interface for grounded search + fetch.
44
+ *
45
+ * Not an `LLMProvider` — it returns grounding metadata that `LLMProvider`
46
+ * doesn't surface. Tool executors (`web_search`, `fetch_url`) depend on
47
+ * this interface, not on the concrete AI SDK types.
48
+ */
49
+ export interface SearchProvider {
50
+ /** Run a grounded search query. */
51
+ search(query: string, opts?: SearchOptions): Promise<SearchResult>;
52
+ /** Fetch + extract content from a URL using urlContext grounding. */
53
+ fetchUrl(url: string, opts?: FetchOptions): Promise<FetchResult>;
54
+ /** Model identifier used for introspection/logging. */
55
+ readonly model: string;
56
+ }
57
+ /**
58
+ * Create a SearchProvider from webTools config.
59
+ *
60
+ * Throws `ConfigError` when the provider string is unsupported. All
61
+ * current config values go through Zod validation at load time, so this
62
+ * is defensive rather than user-facing.
63
+ */
64
+ export declare function createSearchProvider(config: WebToolsConfig): SearchProvider;
@@ -0,0 +1,174 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * Search provider: dedicated Gemini Flash instance for web_search + fetch_url.
8
+ *
9
+ * This is SEPARATE from the main LLM provider. Regardless of what model the
10
+ * agent is using (Anthropic/OpenAI/Google), web search and URL fetch always
11
+ * route through a Gemini Flash call with Google Search + urlContext grounding
12
+ * enabled. That gives us synthesized answers with cited URLs without paying
13
+ * the main-model rate and without coupling search to the main provider.
14
+ *
15
+ * Deviation from the implementation plan: the plan said to return an
16
+ * `LLMProvider` wrapper, but `LLMProvider.generateText` doesn't expose
17
+ * `providerMetadata`, which is where grounding sources live. Rather than
18
+ * pollute `LLMProvider` with Google-specific fields, we return a narrow
19
+ * purpose-specific interface with `search()` and `fetchUrl()` methods that
20
+ * yield `{ text, sources }` shaped results.
21
+ */
22
+ import { generateText } from 'ai';
23
+ import { createGoogleGenerativeAI } from '@ai-sdk/google';
24
+ import { ConfigError, ProviderError } from '../errors.js';
25
+ // ---------------------------------------------------------------------------
26
+ // Constants
27
+ // ---------------------------------------------------------------------------
28
+ /** Default Gemini model for search + urlContext when none is configured. */
29
+ export const DEFAULT_SEARCH_MODEL = 'gemini-3-flash-preview';
30
+ /**
31
+ * Preamble prepended to every grounded search query. Biases Gemini toward
32
+ * authoritative sources and precise citation. ~30 tokens per call.
33
+ */
34
+ const SEARCH_SYSTEM_PREAMBLE = 'Prefer authoritative sources: official documentation, GitHub, package registries, release notes. ' +
35
+ 'When asked for a version number, date, or other exact value, cite it directly from the source. ' +
36
+ 'If sources disagree, say so.';
37
+ /** Tool names required by the Google provider — must match SDK expectations. */
38
+ const GOOGLE_SEARCH_TOOL_NAME = 'google_search';
39
+ const URL_CONTEXT_TOOL_NAME = 'url_context';
40
+ function extractGoogleMetadata(providerMetadata) {
41
+ if (!providerMetadata)
42
+ return undefined;
43
+ const google = providerMetadata['google'];
44
+ if (!google || typeof google !== 'object')
45
+ return undefined;
46
+ return google;
47
+ }
48
+ function extractSources(metadata) {
49
+ const chunks = metadata?.groundingMetadata?.groundingChunks;
50
+ if (!Array.isArray(chunks))
51
+ return [];
52
+ const sources = [];
53
+ for (const chunk of chunks) {
54
+ const uri = chunk.web?.uri;
55
+ if (typeof uri === 'string' && uri.length > 0) {
56
+ sources.push({
57
+ uri,
58
+ ...(chunk.web?.title ? { title: chunk.web.title } : {}),
59
+ });
60
+ }
61
+ }
62
+ return sources;
63
+ }
64
+ /**
65
+ * Extract an HTTP status code from an AI SDK error if present. The SDK wraps
66
+ * provider failures in `AI_APICallError` which carries `statusCode`. We use
67
+ * this to classify failures into auth/quota/transient for the tool executors.
68
+ */
69
+ function extractStatusCode(err) {
70
+ if (err && typeof err === 'object' && 'statusCode' in err) {
71
+ const code = err.statusCode;
72
+ if (typeof code === 'number')
73
+ return code;
74
+ }
75
+ // Error wrapper — check cause
76
+ if (err instanceof Error && err.cause)
77
+ return extractStatusCode(err.cause);
78
+ return undefined;
79
+ }
80
+ function extractRetrievedUrls(metadata) {
81
+ const entries = metadata?.urlContextMetadata?.urlMetadata;
82
+ if (!Array.isArray(entries))
83
+ return [];
84
+ const urls = [];
85
+ for (const entry of entries) {
86
+ if (typeof entry.retrievedUrl === 'string' && entry.retrievedUrl.length > 0) {
87
+ urls.push(entry.retrievedUrl);
88
+ }
89
+ }
90
+ return urls;
91
+ }
92
+ // ---------------------------------------------------------------------------
93
+ // Factory
94
+ // ---------------------------------------------------------------------------
95
+ /**
96
+ * Create a SearchProvider from webTools config.
97
+ *
98
+ * Throws `ConfigError` when the provider string is unsupported. All
99
+ * current config values go through Zod validation at load time, so this
100
+ * is defensive rather than user-facing.
101
+ */
102
+ export function createSearchProvider(config) {
103
+ if (config.provider !== 'google') {
104
+ throw new ConfigError(`Unsupported webTools provider: ${config.provider}`, {
105
+ key: 'webTools.provider',
106
+ suggestion: 'Only "google" is supported today.',
107
+ });
108
+ }
109
+ const google = createGoogleGenerativeAI({ apiKey: config.apiKey });
110
+ const modelId = config.model ?? DEFAULT_SEARCH_MODEL;
111
+ const model = google(modelId);
112
+ return {
113
+ model: modelId,
114
+ async search(query, opts) {
115
+ try {
116
+ const result = await generateText({
117
+ model,
118
+ system: SEARCH_SYSTEM_PREAMBLE,
119
+ prompt: query,
120
+ tools: {
121
+ [GOOGLE_SEARCH_TOOL_NAME]: google.tools.googleSearch({}),
122
+ },
123
+ ...(opts?.signal ? { abortSignal: opts.signal } : {}),
124
+ });
125
+ const metadata = extractGoogleMetadata(result.providerMetadata);
126
+ return {
127
+ text: result.text,
128
+ sources: extractSources(metadata),
129
+ };
130
+ }
131
+ catch (err) {
132
+ const statusCode = extractStatusCode(err);
133
+ throw new ProviderError('Grounded search failed', {
134
+ provider: 'google',
135
+ ...(statusCode !== undefined ? { statusCode } : {}),
136
+ retryable: statusCode !== undefined && statusCode >= 500,
137
+ context: { model: modelId, operation: 'search' },
138
+ cause: err,
139
+ });
140
+ }
141
+ },
142
+ async fetchUrl(url, opts) {
143
+ const prompt = opts?.prompt
144
+ ? `Fetch ${url} and ${opts.prompt}. Respond with the result.`
145
+ : `Fetch ${url} and render the page content as markdown. Preserve headings, links, and lists.`;
146
+ try {
147
+ const result = await generateText({
148
+ model,
149
+ prompt,
150
+ tools: {
151
+ [URL_CONTEXT_TOOL_NAME]: google.tools.urlContext({}),
152
+ },
153
+ ...(opts?.signal ? { abortSignal: opts.signal } : {}),
154
+ });
155
+ const metadata = extractGoogleMetadata(result.providerMetadata);
156
+ return {
157
+ text: result.text,
158
+ retrievedUrls: extractRetrievedUrls(metadata),
159
+ };
160
+ }
161
+ catch (err) {
162
+ const statusCode = extractStatusCode(err);
163
+ throw new ProviderError('Grounded URL fetch failed', {
164
+ provider: 'google',
165
+ ...(statusCode !== undefined ? { statusCode } : {}),
166
+ retryable: statusCode !== undefined && statusCode >= 500,
167
+ context: { model: modelId, operation: 'fetchUrl', url },
168
+ cause: err,
169
+ });
170
+ }
171
+ },
172
+ };
173
+ }
174
+ //# sourceMappingURL=search-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-provider.js","sourceRoot":"","sources":["../../../src/providers/search-provider.ts"],"names":[],"mappings":"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"}
@@ -95,6 +95,14 @@ export interface LLMProvider {
95
95
  readonly provider: string;
96
96
  /** The underlying AI SDK LanguageModel instance (for advanced use) */
97
97
  readonly languageModel: LanguageModel;
98
+ /**
99
+ * Optional provider-native token counter. When implemented, the runtime
100
+ * uses this to decide when to compact context (more accurate than the
101
+ * default 4-chars-per-token heuristic). Implementations should be cheap
102
+ * and synchronous — return an estimate based on a local tokenizer, not
103
+ * an API call.
104
+ */
105
+ countTokens?(messages: ModelMessage[]): number;
98
106
  }
99
107
  export interface ProviderConfig {
100
108
  /** Provider name: 'anthropic', 'openai', 'google', 'deepseek', etc. */