@amodalai/runtime 0.2.0 → 0.2.2
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/dist/src/__fixtures__/README.md +4 -0
- package/dist/src/{agent/user-context-fetcher.test.d.ts → __fixtures__/e2e.test.d.ts} +1 -1
- package/dist/src/__fixtures__/e2e.test.js +211 -0
- package/dist/src/__fixtures__/e2e.test.js.map +1 -0
- package/dist/src/__fixtures__/smoke-agent/automations/delivery-callback-test.json +9 -0
- package/dist/src/__fixtures__/smoke-agent/connections/mock-mcp/spec.json +1 -1
- package/dist/src/__fixtures__/smoke.test.js +715 -29
- package/dist/src/__fixtures__/smoke.test.js.map +1 -1
- package/dist/src/__fixtures__/test-env.d.ts +27 -0
- package/dist/src/__fixtures__/test-env.js +64 -0
- package/dist/src/__fixtures__/test-env.js.map +1 -0
- package/dist/src/__fixtures__/test-helpers.d.ts +30 -0
- package/dist/src/__fixtures__/test-helpers.js +120 -0
- package/dist/src/__fixtures__/test-helpers.js.map +1 -0
- package/dist/src/agent/agent-types.d.ts +22 -0
- package/dist/src/agent/agent-types.js.map +1 -1
- package/dist/src/agent/automation-bridge.d.ts +9 -0
- package/dist/src/agent/automation-bridge.js +26 -0
- package/dist/src/agent/automation-bridge.js.map +1 -1
- package/dist/src/agent/automation-bridge.test.js +63 -0
- package/dist/src/agent/automation-bridge.test.js.map +1 -1
- package/dist/src/agent/local-server.d.ts +0 -7
- package/dist/src/agent/local-server.js +274 -87
- package/dist/src/agent/local-server.js.map +1 -1
- package/dist/src/agent/local-server.test.js +14 -11
- package/dist/src/agent/local-server.test.js.map +1 -1
- package/dist/src/agent/loop-types.d.ts +81 -7
- package/dist/src/agent/loop-types.js +4 -0
- package/dist/src/agent/loop-types.js.map +1 -1
- package/dist/src/agent/loop.js +16 -3
- package/dist/src/agent/loop.js.map +1 -1
- package/dist/src/agent/loop.test.js +572 -10
- package/dist/src/agent/loop.test.js.map +1 -1
- package/dist/src/agent/page-builder.js +20 -17
- package/dist/src/agent/page-builder.js.map +1 -1
- package/dist/src/agent/proactive/delivery-router.d.ts +68 -0
- package/dist/src/agent/proactive/delivery-router.js +337 -0
- package/dist/src/agent/proactive/delivery-router.js.map +1 -0
- package/dist/src/agent/proactive/delivery-router.test.d.ts +6 -0
- package/dist/src/agent/proactive/delivery-router.test.js +455 -0
- package/dist/src/agent/proactive/delivery-router.test.js.map +1 -0
- package/dist/src/agent/proactive/proactive-runner.d.ts +23 -1
- package/dist/src/agent/proactive/proactive-runner.js +42 -10
- package/dist/src/agent/proactive/proactive-runner.js.map +1 -1
- package/dist/src/agent/proactive/proactive-runner.test.js +0 -3
- package/dist/src/agent/proactive/proactive-runner.test.js.map +1 -1
- package/dist/src/agent/routes/admin-chat-abort.test.d.ts +6 -0
- package/dist/src/agent/routes/admin-chat-abort.test.js +206 -0
- package/dist/src/agent/routes/admin-chat-abort.test.js.map +1 -0
- package/dist/src/agent/routes/admin-chat.js +0 -3
- package/dist/src/agent/routes/admin-chat.js.map +1 -1
- package/dist/src/agent/routes/files.js +46 -52
- package/dist/src/agent/routes/files.js.map +1 -1
- package/dist/src/agent/routes/inspect.js +4 -6
- package/dist/src/agent/routes/inspect.js.map +1 -1
- package/dist/src/agent/routes/task.test.js +0 -3
- package/dist/src/agent/routes/task.test.js.map +1 -1
- package/dist/src/agent/snapshot-server.js +37 -3
- package/dist/src/agent/snapshot-server.js.map +1 -1
- package/dist/src/agent/states/compacting.js +5 -3
- package/dist/src/agent/states/compacting.js.map +1 -1
- package/dist/src/agent/states/confirming.js +3 -0
- package/dist/src/agent/states/confirming.js.map +1 -1
- package/dist/src/agent/states/dispatching.js +45 -2
- package/dist/src/agent/states/dispatching.js.map +1 -1
- package/dist/src/agent/states/executing.js +225 -81
- package/dist/src/agent/states/executing.js.map +1 -1
- package/dist/src/agent/states/streaming.js +14 -0
- package/dist/src/agent/states/streaming.js.map +1 -1
- package/dist/src/agent/states/thinking.d.ts +1 -1
- package/dist/src/agent/states/thinking.js +246 -29
- package/dist/src/agent/states/thinking.js.map +1 -1
- package/dist/src/agent/token-estimate.d.ts +20 -6
- package/dist/src/agent/token-estimate.js +24 -3
- package/dist/src/agent/token-estimate.js.map +1 -1
- package/dist/src/agent/token-estimate.test.d.ts +6 -0
- package/dist/src/agent/token-estimate.test.js +44 -0
- package/dist/src/agent/token-estimate.test.js.map +1 -0
- package/dist/src/agent/tool-executor-local.test.js +0 -1
- package/dist/src/agent/tool-executor-local.test.js.map +1 -1
- package/dist/src/agent/tool-harness-template.js +0 -1
- package/dist/src/agent/tool-harness-template.js.map +1 -1
- package/dist/src/api/create-agent.js +1 -5
- package/dist/src/api/create-agent.js.map +1 -1
- package/dist/src/api/types.d.ts +1 -5
- package/dist/src/channels/bootstrap.d.ts +59 -0
- package/dist/src/channels/bootstrap.js +84 -0
- package/dist/src/channels/bootstrap.js.map +1 -0
- package/dist/src/channels/channel-session-mapper.d.ts +42 -0
- package/dist/src/channels/channel-session-mapper.js +91 -0
- package/dist/src/channels/channel-session-mapper.js.map +1 -0
- package/dist/src/channels/dedup-cache.d.ts +17 -0
- package/dist/src/channels/dedup-cache.js +51 -0
- package/dist/src/channels/dedup-cache.js.map +1 -0
- package/dist/src/channels/dedup-cache.test.d.ts +6 -0
- package/dist/src/channels/dedup-cache.test.js +51 -0
- package/dist/src/channels/dedup-cache.test.js.map +1 -0
- package/dist/src/channels/errors.d.ts +28 -0
- package/dist/src/channels/errors.js +38 -0
- package/dist/src/channels/errors.js.map +1 -0
- package/dist/src/channels/in-memory-session-mapper.d.ts +34 -0
- package/dist/src/channels/in-memory-session-mapper.js +50 -0
- package/dist/src/channels/in-memory-session-mapper.js.map +1 -0
- package/dist/src/channels/plugin-loader.d.ts +20 -0
- package/dist/src/channels/plugin-loader.js +136 -0
- package/dist/src/channels/plugin-loader.js.map +1 -0
- package/dist/src/channels/plugin-loader.test.d.ts +6 -0
- package/dist/src/channels/plugin-loader.test.js +113 -0
- package/dist/src/channels/plugin-loader.test.js.map +1 -0
- package/dist/src/channels/routes.d.ts +29 -0
- package/dist/src/channels/routes.js +165 -0
- package/dist/src/channels/routes.js.map +1 -0
- package/dist/src/config.d.ts +0 -2
- package/dist/src/config.js +0 -1
- package/dist/src/config.js.map +1 -1
- package/dist/src/config.test.js +0 -2
- package/dist/src/config.test.js.map +1 -1
- package/dist/src/context/compiler.js +11 -34
- package/dist/src/context/compiler.js.map +1 -1
- package/dist/src/context/compiler.test.js +7 -60
- package/dist/src/context/compiler.test.js.map +1 -1
- package/dist/src/context/types.d.ts +0 -4
- package/dist/src/env-ref.d.ts +13 -0
- package/dist/src/env-ref.js +31 -0
- package/dist/src/env-ref.js.map +1 -0
- package/dist/src/env-ref.test.d.ts +6 -0
- package/dist/src/env-ref.test.js +34 -0
- package/dist/src/env-ref.test.js.map +1 -0
- package/dist/src/errors.d.ts +15 -0
- package/dist/src/errors.js +22 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/errors.test.js +2 -2
- package/dist/src/errors.test.js.map +1 -1
- package/dist/src/events/event-bus.d.ts +54 -0
- package/dist/src/events/event-bus.js +84 -0
- package/dist/src/events/event-bus.js.map +1 -0
- package/dist/src/events/event-bus.test.d.ts +6 -0
- package/dist/src/events/event-bus.test.js +112 -0
- package/dist/src/events/event-bus.test.js.map +1 -0
- package/dist/src/events/events-route.d.ts +36 -0
- package/dist/src/events/events-route.js +80 -0
- package/dist/src/events/events-route.js.map +1 -0
- package/dist/src/events/events-route.test.d.ts +6 -0
- package/dist/src/events/events-route.test.js +134 -0
- package/dist/src/events/events-route.test.js.map +1 -0
- package/dist/src/events/store-event-wrapper.d.ts +19 -0
- package/dist/src/events/store-event-wrapper.js +57 -0
- package/dist/src/events/store-event-wrapper.js.map +1 -0
- package/dist/src/events/store-event-wrapper.test.d.ts +6 -0
- package/dist/src/events/store-event-wrapper.test.js +91 -0
- package/dist/src/events/store-event-wrapper.test.js.map +1 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.js +10 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/middleware/auth.d.ts +0 -2
- package/dist/src/middleware/auth.js.map +1 -1
- package/dist/src/providers/search-provider.d.ts +64 -0
- package/dist/src/providers/search-provider.js +174 -0
- package/dist/src/providers/search-provider.js.map +1 -0
- package/dist/src/providers/types.d.ts +8 -0
- package/dist/src/routes/ai-stream.d.ts +18 -4
- package/dist/src/routes/ai-stream.js +10 -2
- package/dist/src/routes/ai-stream.js.map +1 -1
- package/dist/src/routes/chat-stream.d.ts +9 -1
- package/dist/src/routes/chat-stream.js +3 -1
- package/dist/src/routes/chat-stream.js.map +1 -1
- package/dist/src/routes/chat.d.ts +6 -0
- package/dist/src/routes/chat.js +2 -1
- package/dist/src/routes/chat.js.map +1 -1
- package/dist/src/routes/session-resolver.d.ts +15 -2
- package/dist/src/routes/session-resolver.js +22 -25
- package/dist/src/routes/session-resolver.js.map +1 -1
- package/dist/src/routes/session-resolver.test.js +117 -20
- package/dist/src/routes/session-resolver.test.js.map +1 -1
- package/dist/src/server.d.ts +35 -1
- package/dist/src/server.js +33 -0
- package/dist/src/server.js.map +1 -1
- package/dist/src/session/drizzle-session-store.d.ts +57 -0
- package/dist/src/session/drizzle-session-store.js +204 -0
- package/dist/src/session/drizzle-session-store.js.map +1 -0
- package/dist/src/session/manager.d.ts +6 -3
- package/dist/src/session/manager.js +46 -19
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/manager.test.js +12 -18
- package/dist/src/session/manager.test.js.map +1 -1
- package/dist/src/session/pglite-session-store.d.ts +23 -0
- package/dist/src/session/pglite-session-store.js +86 -0
- package/dist/src/session/pglite-session-store.js.map +1 -0
- package/dist/src/session/postgres-session-store.d.ts +44 -0
- package/dist/src/session/postgres-session-store.js +153 -0
- package/dist/src/session/postgres-session-store.js.map +1 -0
- package/dist/src/session/session-builder.d.ts +0 -5
- package/dist/src/session/session-builder.js +22 -6
- package/dist/src/session/session-builder.js.map +1 -1
- package/dist/src/session/session-builder.test.js +3 -8
- package/dist/src/session/session-builder.test.js.map +1 -1
- package/dist/src/session/session-store-selector.d.ts +49 -0
- package/dist/src/session/session-store-selector.js +60 -0
- package/dist/src/session/session-store-selector.js.map +1 -0
- package/dist/src/session/session-store-selector.test.d.ts +6 -0
- package/dist/src/session/session-store-selector.test.js +79 -0
- package/dist/src/session/session-store-selector.test.js.map +1 -0
- package/dist/src/session/store.d.ts +146 -32
- package/dist/src/session/store.js +126 -138
- package/dist/src/session/store.js.map +1 -1
- package/dist/src/session/store.test.js +385 -107
- package/dist/src/session/store.test.js.map +1 -1
- package/dist/src/session/tool-context-factory.d.ts +3 -7
- package/dist/src/session/tool-context-factory.js +1 -3
- package/dist/src/session/tool-context-factory.js.map +1 -1
- package/dist/src/session/tool-context-factory.test.js +1 -6
- package/dist/src/session/tool-context-factory.test.js.map +1 -1
- package/dist/src/session/types.d.ts +13 -10
- package/dist/src/stores/schema.d.ts +111 -34
- package/dist/src/stores/schema.js +21 -4
- package/dist/src/stores/schema.js.map +1 -1
- package/dist/src/tools/admin-file-tools.d.ts +29 -0
- package/dist/src/tools/admin-file-tools.js +527 -13
- package/dist/src/tools/admin-file-tools.js.map +1 -1
- package/dist/src/tools/admin-file-tools.test.js +380 -9
- package/dist/src/tools/admin-file-tools.test.js.map +1 -1
- package/dist/src/tools/custom-tool-adapter.js +0 -1
- package/dist/src/tools/custom-tool-adapter.js.map +1 -1
- package/dist/src/tools/custom-tool-adapter.test.js +0 -2
- package/dist/src/tools/custom-tool-adapter.test.js.map +1 -1
- package/dist/src/tools/dispatch-tool.d.ts +4 -4
- package/dist/src/tools/fetch-url-tool.d.ts +23 -0
- package/dist/src/tools/fetch-url-tool.js +333 -0
- package/dist/src/tools/fetch-url-tool.js.map +1 -0
- package/dist/src/tools/fetch-url-tool.test.d.ts +6 -0
- package/dist/src/tools/fetch-url-tool.test.js +227 -0
- package/dist/src/tools/fetch-url-tool.test.js.map +1 -0
- package/dist/src/tools/mcp-tool-adapter.test.js +0 -2
- package/dist/src/tools/mcp-tool-adapter.test.js.map +1 -1
- package/dist/src/tools/registry.test.js +0 -2
- package/dist/src/tools/registry.test.js.map +1 -1
- package/dist/src/tools/request-tool.test.js +0 -2
- package/dist/src/tools/request-tool.test.js.map +1 -1
- package/dist/src/tools/store-tools.test.js +0 -2
- package/dist/src/tools/store-tools.test.js.map +1 -1
- package/dist/src/tools/types.d.ts +20 -7
- package/dist/src/tools/web-search-tool.d.ts +31 -0
- package/dist/src/tools/web-search-tool.js +170 -0
- package/dist/src/tools/web-search-tool.js.map +1 -0
- package/dist/src/tools/web-search-tool.test.d.ts +6 -0
- package/dist/src/tools/web-search-tool.test.js +152 -0
- package/dist/src/tools/web-search-tool.test.js.map +1 -0
- package/dist/src/tools/web-tools-shared.d.ts +21 -0
- package/dist/src/tools/web-tools-shared.js +32 -0
- package/dist/src/tools/web-tools-shared.js.map +1 -0
- package/dist/src/types.d.ts +20 -4
- package/dist/src/types.js +13 -2
- package/dist/src/types.js.map +1 -1
- package/dist/src/types.test.js +0 -3
- package/dist/src/types.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -3
- package/dist/src/agent/session-store.d.ts +0 -71
- package/dist/src/agent/session-store.js +0 -151
- package/dist/src/agent/session-store.js.map +0 -1
- package/dist/src/agent/user-context-fetcher.d.ts +0 -25
- package/dist/src/agent/user-context-fetcher.js +0 -79
- package/dist/src/agent/user-context-fetcher.js.map +0 -1
- package/dist/src/agent/user-context-fetcher.test.js +0 -121
- package/dist/src/agent/user-context-fetcher.test.js.map +0 -1
- package/dist/src/session/admin-file-tools.d.ts +0 -136
- package/dist/src/session/admin-file-tools.js +0 -240
- package/dist/src/session/admin-file-tools.js.map +0 -1
|
@@ -47,6 +47,69 @@ describe('bridgeAutomation', () => {
|
|
|
47
47
|
expect(result.prompt).toBe(prompt);
|
|
48
48
|
});
|
|
49
49
|
});
|
|
50
|
+
describe('bridgeAutomation env:NAME resolution', () => {
|
|
51
|
+
it('resolves env:NAME refs in delivery webhook URLs', () => {
|
|
52
|
+
process.env['TEST_WEBHOOK_URL'] = 'https://hooks.example.com/abc123';
|
|
53
|
+
try {
|
|
54
|
+
const result = bridgeAutomation(makeAutomation({
|
|
55
|
+
delivery: {
|
|
56
|
+
targets: [{ type: 'webhook', url: 'env:TEST_WEBHOOK_URL' }],
|
|
57
|
+
},
|
|
58
|
+
}));
|
|
59
|
+
expect(result.delivery?.targets[0]).toEqual({
|
|
60
|
+
type: 'webhook',
|
|
61
|
+
url: 'https://hooks.example.com/abc123',
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
delete process.env['TEST_WEBHOOK_URL'];
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
it('resolves env:NAME refs in failureAlert webhook URLs', () => {
|
|
69
|
+
process.env['TEST_ALERT_URL'] = 'https://alerts.example.com/oncall';
|
|
70
|
+
try {
|
|
71
|
+
const result = bridgeAutomation(makeAutomation({
|
|
72
|
+
failureAlert: {
|
|
73
|
+
targets: [{ type: 'webhook', url: 'env:TEST_ALERT_URL' }],
|
|
74
|
+
},
|
|
75
|
+
}));
|
|
76
|
+
expect(result.failureAlert?.targets[0]).toEqual({
|
|
77
|
+
type: 'webhook',
|
|
78
|
+
url: 'https://alerts.example.com/oncall',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
delete process.env['TEST_ALERT_URL'];
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
it('leaves callback targets unchanged', () => {
|
|
86
|
+
const result = bridgeAutomation(makeAutomation({
|
|
87
|
+
delivery: {
|
|
88
|
+
targets: [{ type: 'callback', name: 'my-handler' }],
|
|
89
|
+
},
|
|
90
|
+
}));
|
|
91
|
+
expect(result.delivery?.targets[0]).toEqual({ type: 'callback', name: 'my-handler' });
|
|
92
|
+
});
|
|
93
|
+
it('leaves literal http URLs unchanged', () => {
|
|
94
|
+
const result = bridgeAutomation(makeAutomation({
|
|
95
|
+
delivery: {
|
|
96
|
+
targets: [{ type: 'webhook', url: 'https://hooks.example.com/fixed' }],
|
|
97
|
+
},
|
|
98
|
+
}));
|
|
99
|
+
expect(result.delivery?.targets[0]).toEqual({
|
|
100
|
+
type: 'webhook',
|
|
101
|
+
url: 'https://hooks.example.com/fixed',
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
it('throws at bridge time if env var is missing (fail fast at boot)', () => {
|
|
105
|
+
delete process.env['NONEXISTENT_WEBHOOK'];
|
|
106
|
+
expect(() => bridgeAutomation(makeAutomation({
|
|
107
|
+
delivery: {
|
|
108
|
+
targets: [{ type: 'webhook', url: 'env:NONEXISTENT_WEBHOOK' }],
|
|
109
|
+
},
|
|
110
|
+
}))).toThrow(/NONEXISTENT_WEBHOOK/);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
50
113
|
describe('bridgeAutomations', () => {
|
|
51
114
|
it('converts an array of automations', () => {
|
|
52
115
|
const automations = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"automation-bridge.test.js","sourceRoot":"","sources":["../../../src/agent/automation-bridge.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"automation-bridge.test.js","sourceRoot":"","sources":["../../../src/agent/automation-bridge.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,11 +5,4 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { LocalServerConfig } from './agent-types.js';
|
|
7
7
|
import type { ServerInstance } from '../server.js';
|
|
8
|
-
/**
|
|
9
|
-
* Creates an Express server for repo-based agent mode.
|
|
10
|
-
*
|
|
11
|
-
* Loads the `.amodal/` config from `config.repoPath`, creates a
|
|
12
|
-
* `StandaloneSessionManager`, mounts all routes, and optionally watches
|
|
13
|
-
* for config changes (hot reload).
|
|
14
|
-
*/
|
|
15
8
|
export declare function createLocalServer(config: LocalServerConfig): Promise<ServerInstance>;
|
|
@@ -18,11 +18,15 @@ import { existsSync } from 'node:fs';
|
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import { loadRepo } from '@amodalai/core';
|
|
20
20
|
import { StandaloneSessionManager } from '../session/manager.js';
|
|
21
|
-
import {
|
|
21
|
+
import { selectSessionStore } from '../session/session-store-selector.js';
|
|
22
|
+
import { resolveEnvRef } from '../env-ref.js';
|
|
22
23
|
import { buildSessionComponents } from '../session/session-builder.js';
|
|
23
24
|
import { LocalToolExecutor } from './tool-executor-local.js';
|
|
24
25
|
import { buildMcpConfigs } from './mcp-config.js';
|
|
25
26
|
import { ConfigWatcher } from './config-watcher.js';
|
|
27
|
+
import { RuntimeEventBus } from '../events/event-bus.js';
|
|
28
|
+
import { createEventsRouter } from '../events/events-route.js';
|
|
29
|
+
import { wrapStoreBackendWithEvents } from '../events/store-event-wrapper.js';
|
|
26
30
|
import { ProactiveRunner } from './proactive/proactive-runner.js';
|
|
27
31
|
import { createChatStreamRouter } from '../routes/chat-stream.js';
|
|
28
32
|
import { createChatRouter } from '../routes/chat.js';
|
|
@@ -37,14 +41,22 @@ import { createStoresRouter } from './routes/stores.js';
|
|
|
37
41
|
import { createFilesRouter } from './routes/files.js';
|
|
38
42
|
import { createEvalRouter } from './routes/evals.js';
|
|
39
43
|
import { errorHandler } from '../middleware/error-handler.js';
|
|
44
|
+
import { asyncHandler } from '../routes/route-helpers.js';
|
|
40
45
|
import { createPGLiteStoreBackend } from '../stores/pglite-store-backend.js';
|
|
41
|
-
import { SessionStore } from './session-store.js';
|
|
42
46
|
import { EvalStore } from './eval-store.js';
|
|
43
47
|
import { buildPages } from './page-builder.js';
|
|
44
48
|
import { LOCAL_APP_ID } from '../constants.js';
|
|
45
49
|
import { log, createLogger } from '../logger.js';
|
|
50
|
+
import { bootstrapChannels } from '../channels/bootstrap.js';
|
|
51
|
+
import { DrizzleChannelSessionMapper } from '../channels/channel-session-mapper.js';
|
|
52
|
+
// Each check must use an endpoint that returns 200 on a valid key and
|
|
53
|
+
// a distinct auth-failure status (typically 401) on a bad key. Do NOT
|
|
54
|
+
// use endpoints with method guards that might return 405 before the
|
|
55
|
+
// auth check — `GET /v1/messages` on Anthropic does exactly that, and
|
|
56
|
+
// makes every key (good or bad) look invalid because Anthropic returns
|
|
57
|
+
// 405 for wrong-method regardless of whether the x-api-key is real.
|
|
46
58
|
const PROVIDER_CHECKS = [
|
|
47
|
-
{ provider: 'anthropic', envVar: 'ANTHROPIC_API_KEY', url: 'https://api.anthropic.com/v1/
|
|
59
|
+
{ provider: 'anthropic', envVar: 'ANTHROPIC_API_KEY', url: 'https://api.anthropic.com/v1/models', authHeader: (key) => ({ 'x-api-key': key, 'anthropic-version': '2023-06-01' }) },
|
|
48
60
|
{ provider: 'openai', envVar: 'OPENAI_API_KEY', url: 'https://api.openai.com/v1/models', authHeader: (key) => ({ Authorization: `Bearer ${key}` }) },
|
|
49
61
|
];
|
|
50
62
|
async function checkProviders() {
|
|
@@ -80,7 +92,33 @@ async function checkProviders() {
|
|
|
80
92
|
* `StandaloneSessionManager`, mounts all routes, and optionally watches
|
|
81
93
|
* for config changes (hot reload).
|
|
82
94
|
*/
|
|
95
|
+
/**
|
|
96
|
+
* Install a process-level unhandledRejection listener that logs instead
|
|
97
|
+
* of crashing. An escaped rejection is always a bug — we want loud logs,
|
|
98
|
+
* not silent outages. The previous behavior (default Node: print + crash)
|
|
99
|
+
* turned small bugs (one leaked promise) into whole-server downtime for
|
|
100
|
+
* every active session. Logging + continuing preserves service for all
|
|
101
|
+
* other sessions while still surfacing the issue to operators.
|
|
102
|
+
*
|
|
103
|
+
* Idempotent: only installs once per process (the local-server can be
|
|
104
|
+
* created and torn down repeatedly during tests).
|
|
105
|
+
*/
|
|
106
|
+
let unhandledRejectionListenerInstalled = false;
|
|
107
|
+
function installUnhandledRejectionLogger() {
|
|
108
|
+
if (unhandledRejectionListenerInstalled)
|
|
109
|
+
return;
|
|
110
|
+
unhandledRejectionListenerInstalled = true;
|
|
111
|
+
process.on('unhandledRejection', (reason) => {
|
|
112
|
+
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
113
|
+
log.error('unhandled_rejection', {
|
|
114
|
+
name: err.name,
|
|
115
|
+
message: err.message,
|
|
116
|
+
stack: err.stack,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
83
120
|
export async function createLocalServer(config) {
|
|
121
|
+
installUnhandledRejectionLogger();
|
|
84
122
|
let bundle = await loadRepo({ localPath: config.repoPath });
|
|
85
123
|
// Check provider API keys in the background at startup
|
|
86
124
|
let providerStatuses = PROVIDER_CHECKS.map((c) => ({
|
|
@@ -110,9 +148,7 @@ export async function createLocalServer(config) {
|
|
|
110
148
|
const storeConfig = bundle.config.stores;
|
|
111
149
|
const backend = storeConfig?.backend ?? 'pglite';
|
|
112
150
|
if (backend === 'postgres' && storeConfig?.postgresUrl) {
|
|
113
|
-
const connUrl = storeConfig.postgresUrl
|
|
114
|
-
? process.env[storeConfig.postgresUrl.slice(4)] ?? ''
|
|
115
|
-
: storeConfig.postgresUrl;
|
|
151
|
+
const connUrl = resolveEnvRef(storeConfig.postgresUrl) ?? '';
|
|
116
152
|
if (!connUrl) {
|
|
117
153
|
log.error('store_postgres_url_missing', { configured: storeConfig.postgresUrl });
|
|
118
154
|
}
|
|
@@ -178,19 +214,41 @@ export async function createLocalServer(config) {
|
|
|
178
214
|
}
|
|
179
215
|
}
|
|
180
216
|
// -------------------------------------------------------------------------
|
|
217
|
+
// Runtime event bus (powers /api/events SSE for live UI updates)
|
|
218
|
+
// -------------------------------------------------------------------------
|
|
219
|
+
const eventBus = new RuntimeEventBus({
|
|
220
|
+
onListenerError: (err, event) => {
|
|
221
|
+
log.warn('event_bus_listener_error', {
|
|
222
|
+
seq: event.seq,
|
|
223
|
+
type: event.type,
|
|
224
|
+
error: err instanceof Error ? err.message : String(err),
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
// Wrap the store backend so every write emits store_updated events.
|
|
229
|
+
// Covers every write path through one seam: tools, REST routes, admin
|
|
230
|
+
// file tools, task execution — they all go through this backend.
|
|
231
|
+
if (storeBackend) {
|
|
232
|
+
storeBackend = wrapStoreBackendWithEvents(storeBackend, eventBus);
|
|
233
|
+
}
|
|
234
|
+
// -------------------------------------------------------------------------
|
|
181
235
|
// Session manager (new standalone stack)
|
|
182
236
|
// -------------------------------------------------------------------------
|
|
183
237
|
const sessionLogger = createLogger({ component: 'session-manager' });
|
|
184
|
-
const
|
|
185
|
-
await
|
|
238
|
+
const sessionDataDir = `${config.repoPath}/.amodal/session-data`;
|
|
239
|
+
const sessionStore = await selectSessionStore({
|
|
240
|
+
backend: bundle.config.stores?.backend,
|
|
241
|
+
postgresUrl: resolveEnvRef(bundle.config.stores?.postgresUrl),
|
|
242
|
+
logger: sessionLogger,
|
|
243
|
+
dataDir: sessionDataDir,
|
|
244
|
+
});
|
|
186
245
|
const sessionManager = new StandaloneSessionManager({
|
|
187
246
|
logger: sessionLogger,
|
|
188
247
|
store: sessionStore,
|
|
189
248
|
ttlMs: config.sessionTtlMs,
|
|
249
|
+
eventBus,
|
|
190
250
|
});
|
|
191
251
|
sessionManager.start();
|
|
192
|
-
// Legacy session store for UI history (file-based)
|
|
193
|
-
const legacySessionStore = new SessionStore(config.repoPath);
|
|
194
252
|
// -------------------------------------------------------------------------
|
|
195
253
|
// MCP connections (shared across sessions)
|
|
196
254
|
// -------------------------------------------------------------------------
|
|
@@ -235,13 +293,10 @@ export async function createLocalServer(config) {
|
|
|
235
293
|
sessionType: 'automation',
|
|
236
294
|
});
|
|
237
295
|
const session = sessionManager.create({
|
|
238
|
-
tenantId: 'local',
|
|
239
|
-
userId: 'automation',
|
|
240
296
|
provider: components.provider,
|
|
241
297
|
toolRegistry: components.toolRegistry,
|
|
242
298
|
permissionChecker: components.permissionChecker,
|
|
243
299
|
systemPrompt: components.systemPrompt,
|
|
244
|
-
userRoles: components.userRoles,
|
|
245
300
|
toolContextFactory: components.toolContextFactory,
|
|
246
301
|
appId: LOCAL_APP_ID,
|
|
247
302
|
});
|
|
@@ -255,11 +310,50 @@ export async function createLocalServer(config) {
|
|
|
255
310
|
createSessionComponents: createAutomationSessionComponents,
|
|
256
311
|
logger: log,
|
|
257
312
|
webhookSecret: config.webhookSecret,
|
|
258
|
-
|
|
313
|
+
summarizeToolResult: config.summarizeToolResult,
|
|
314
|
+
onSessionComplete: (session, automationName) => {
|
|
315
|
+
// Tag the automation name onto metadata so the UI can filter
|
|
316
|
+
// sessions by automation via /sessions?automation=<name>.
|
|
317
|
+
session.metadata.automationName = automationName;
|
|
259
318
|
void sessionManager.persist(session);
|
|
260
319
|
},
|
|
320
|
+
eventBus,
|
|
321
|
+
onAutomationResult: config.onAutomationResult,
|
|
261
322
|
});
|
|
262
323
|
// -------------------------------------------------------------------------
|
|
324
|
+
// Channel plugins (messaging integrations)
|
|
325
|
+
// -------------------------------------------------------------------------
|
|
326
|
+
let channelsResult = null;
|
|
327
|
+
if (bundle.channels && bundle.channels.length > 0) {
|
|
328
|
+
// Both PGLite and Postgres factories return DrizzleSessionStore which
|
|
329
|
+
// exposes `db`. Cast through the concrete type safely.
|
|
330
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowing to concrete DrizzleSessionStore
|
|
331
|
+
const storeDb = sessionStore.db;
|
|
332
|
+
const channelSessionMapper = new DrizzleChannelSessionMapper({
|
|
333
|
+
db: storeDb,
|
|
334
|
+
logger: log,
|
|
335
|
+
eventBus,
|
|
336
|
+
});
|
|
337
|
+
channelsResult = await bootstrapChannels({
|
|
338
|
+
channels: bundle.channels,
|
|
339
|
+
repoPath: config.repoPath,
|
|
340
|
+
packages: bundle.config.packages,
|
|
341
|
+
sessionMapper: channelSessionMapper,
|
|
342
|
+
sessionManager,
|
|
343
|
+
buildSessionComponents: () => buildSessionComponents({
|
|
344
|
+
bundle,
|
|
345
|
+
storeBackend: storeBackend ?? null,
|
|
346
|
+
mcpManager,
|
|
347
|
+
logger: log,
|
|
348
|
+
toolExecutor,
|
|
349
|
+
sessionType: 'chat',
|
|
350
|
+
}),
|
|
351
|
+
appId: LOCAL_APP_ID,
|
|
352
|
+
eventBus,
|
|
353
|
+
logger: log,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
// -------------------------------------------------------------------------
|
|
263
357
|
// Config watcher (hot reload)
|
|
264
358
|
// -------------------------------------------------------------------------
|
|
265
359
|
let watcher = null;
|
|
@@ -327,7 +421,11 @@ export async function createLocalServer(config) {
|
|
|
327
421
|
// Resolve resume session ID
|
|
328
422
|
let resumeSessionId = config.resumeSessionId;
|
|
329
423
|
if (resumeSessionId === 'latest') {
|
|
330
|
-
|
|
424
|
+
const { sessions: recent } = await sessionStore.list({
|
|
425
|
+
limit: 1,
|
|
426
|
+
filter: { appId: LOCAL_APP_ID },
|
|
427
|
+
});
|
|
428
|
+
resumeSessionId = recent[0]?.id;
|
|
331
429
|
}
|
|
332
430
|
if (resumeSessionId) {
|
|
333
431
|
log.debug('resume_session', { sessionId: resumeSessionId });
|
|
@@ -336,43 +434,48 @@ export async function createLocalServer(config) {
|
|
|
336
434
|
app.get('/config', (_req, res) => {
|
|
337
435
|
res.json({ resumeSessionId: resumeSessionId ?? null });
|
|
338
436
|
});
|
|
339
|
-
// Sessions endpoints
|
|
340
|
-
|
|
437
|
+
// Sessions endpoints — served directly from the DrizzleSessionStore.
|
|
438
|
+
//
|
|
439
|
+
// Dev-UI consumers (sidebar Recent list, Sessions page, Automation detail
|
|
440
|
+
// page) don't paginate — they render what they get and slice the top N.
|
|
441
|
+
// A 500-session ceiling keeps the response bounded without forcing a
|
|
442
|
+
// cursor API on the client today. If dev sessions regularly exceed this,
|
|
443
|
+
// the store already supports cursor pagination via SessionListOptions.
|
|
444
|
+
const SESSION_LIST_LIMIT = 500;
|
|
445
|
+
app.get('/sessions', asyncHandler(async (req, res) => {
|
|
341
446
|
const automationFilter = typeof req.query?.['automation'] === 'string' ? String(req.query['automation']) : undefined;
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const
|
|
447
|
+
// Automation filter uses metadata.automationName; otherwise restrict
|
|
448
|
+
// to chat sessions by metadata.appId (excludes eval-runner / admin).
|
|
449
|
+
const filter = automationFilter
|
|
450
|
+
? { automationName: automationFilter }
|
|
451
|
+
: { appId: LOCAL_APP_ID };
|
|
452
|
+
const { sessions: rows } = await sessionStore.list({ limit: SESSION_LIST_LIMIT, filter });
|
|
453
|
+
const sessions = rows.map((s) => {
|
|
454
|
+
const title = typeof s.metadata['title'] === 'string' ? s.metadata['title'] : undefined;
|
|
455
|
+
const appId = typeof s.metadata['appId'] === 'string' ? s.metadata['appId'] : LOCAL_APP_ID;
|
|
456
|
+
const automationName = typeof s.metadata['automationName'] === 'string' ? s.metadata['automationName'] : undefined;
|
|
457
|
+
return {
|
|
458
|
+
id: s.id,
|
|
459
|
+
appId,
|
|
460
|
+
title,
|
|
461
|
+
summary: title ?? extractFirstUserText(s.messages) ?? 'Untitled',
|
|
462
|
+
createdAt: s.createdAt.getTime(),
|
|
463
|
+
lastAccessedAt: s.updatedAt.getTime(),
|
|
464
|
+
automationName,
|
|
465
|
+
};
|
|
466
|
+
});
|
|
467
|
+
res.json({ sessions });
|
|
468
|
+
}));
|
|
469
|
+
app.get('/session/:id', asyncHandler(async (req, res) => {
|
|
470
|
+
const persisted = await sessionStore.load(req.params['id'] ?? '');
|
|
349
471
|
if (!persisted) {
|
|
350
472
|
res.status(404).json({ error: 'Session not found' });
|
|
351
473
|
return;
|
|
352
474
|
}
|
|
353
|
-
const messages = persisted.
|
|
354
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Persisted message
|
|
355
|
-
const m = msg;
|
|
356
|
-
if (m['type'] === 'user') {
|
|
357
|
-
return { role: 'user', text: String(m['text'] ?? '') };
|
|
358
|
-
}
|
|
359
|
-
if (m['type'] === 'assistant_text') {
|
|
360
|
-
return { role: 'assistant', text: String(m['text'] ?? ''), toolCalls: m['toolCalls'] };
|
|
361
|
-
}
|
|
362
|
-
if (m['role'] === 'user') {
|
|
363
|
-
return { role: 'user', text: typeof m['content'] === 'string' ? m['content'] : '' };
|
|
364
|
-
}
|
|
365
|
-
if (m['role'] === 'assistant') {
|
|
366
|
-
const blocks = Array.isArray(m['content']) ? m['content'] : [];
|
|
367
|
-
const isTextBlock = (b) => typeof b === 'object' && b !== null && 'type' in b && b['type'] === 'text' && 'text' in b;
|
|
368
|
-
const text = blocks.filter(isTextBlock).map((b) => b.text).join('');
|
|
369
|
-
return { role: 'assistant', text };
|
|
370
|
-
}
|
|
371
|
-
return { role: String(m['role'] ?? m['type'] ?? 'unknown'), text: String(m['text'] ?? '') };
|
|
372
|
-
});
|
|
475
|
+
const messages = persisted.messages.map(flattenModelMessage).filter((m) => m !== null);
|
|
373
476
|
res.json({ session_id: persisted.id, messages });
|
|
374
|
-
});
|
|
375
|
-
app.patch('/session/:id', express.json(), (req, res) => {
|
|
477
|
+
}));
|
|
478
|
+
app.patch('/session/:id', express.json(), asyncHandler(async (req, res) => {
|
|
376
479
|
const sessionId = req.params['id'] ?? '';
|
|
377
480
|
const body = req.body;
|
|
378
481
|
if (!body || typeof body !== 'object' || !('title' in body) || typeof body['title'] !== 'string') {
|
|
@@ -381,72 +484,67 @@ export async function createLocalServer(config) {
|
|
|
381
484
|
}
|
|
382
485
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- validated above
|
|
383
486
|
const title = body['title'];
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
487
|
+
// Live session: mutate metadata on the shared object and persist so
|
|
488
|
+
// the next /sessions read reflects the new title. A concurrent
|
|
489
|
+
// runMessage may be mid-turn, but JSON.stringify runs atomically in
|
|
490
|
+
// JS's single-threaded event loop — no torn writes. The next
|
|
491
|
+
// end-of-turn persist will overwrite with the completed messages
|
|
492
|
+
// array; metadata.title stays because it's on the live session.
|
|
493
|
+
//
|
|
494
|
+
// Not-live: load → mutate → save. No race possible.
|
|
495
|
+
const live = sessionManager.get(sessionId);
|
|
496
|
+
if (live) {
|
|
497
|
+
live.metadata.title = title;
|
|
498
|
+
await sessionManager.persist(live);
|
|
388
499
|
}
|
|
500
|
+
else {
|
|
501
|
+
const persisted = await sessionStore.load(sessionId);
|
|
502
|
+
if (!persisted) {
|
|
503
|
+
res.status(404).json({ error: 'Session not found' });
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
persisted.metadata.title = title;
|
|
507
|
+
persisted.updatedAt = new Date();
|
|
508
|
+
await sessionStore.save(persisted);
|
|
509
|
+
}
|
|
510
|
+
// Emit session_updated so the sidebar picks up the new title live.
|
|
511
|
+
eventBus.emit({ type: 'session_updated', sessionId, appId: LOCAL_APP_ID, title });
|
|
389
512
|
res.json({ ok: true });
|
|
390
|
-
});
|
|
391
|
-
app.delete('/session/:id', (req, res) => {
|
|
513
|
+
}));
|
|
514
|
+
app.delete('/session/:id', asyncHandler(async (req, res) => {
|
|
392
515
|
const sessionId = req.params['id'] ?? '';
|
|
393
|
-
|
|
394
|
-
const deleted =
|
|
516
|
+
await sessionManager.destroy(sessionId);
|
|
517
|
+
const deleted = await sessionStore.delete(sessionId);
|
|
395
518
|
if (!deleted) {
|
|
396
519
|
res.status(404).json({ error: 'Session not found' });
|
|
397
520
|
return;
|
|
398
521
|
}
|
|
522
|
+
eventBus.emit({ type: 'session_deleted', sessionId });
|
|
399
523
|
res.json({ ok: true });
|
|
400
|
-
});
|
|
524
|
+
}));
|
|
401
525
|
// File browser/editor
|
|
402
526
|
app.use(createFilesRouter({ repoPath: config.repoPath }));
|
|
527
|
+
// Event bus SSE stream (live UI updates)
|
|
528
|
+
app.use(createEventsRouter({ bus: eventBus, logger: log }));
|
|
403
529
|
// Evals
|
|
404
530
|
const evalStore = new EvalStore(config.repoPath);
|
|
405
531
|
app.use(createEvalRouter({ getBundle, evalStore, repoPath: config.repoPath, getPort: () => config.port }));
|
|
406
532
|
// Feedback
|
|
407
533
|
const feedbackStore = new FeedbackStore(config.repoPath);
|
|
408
534
|
app.use(createFeedbackRouter({ feedbackStore }));
|
|
409
|
-
// Chat routes (new stack)
|
|
535
|
+
// Chat routes (new stack) — persistence is handled inside runMessage /
|
|
536
|
+
// route-helpers, so no explicit hooks are needed here.
|
|
410
537
|
app.use(createChatStreamRouter({
|
|
411
538
|
sessionManager,
|
|
412
539
|
bundleResolver: { staticBundle: bundle },
|
|
413
540
|
shared,
|
|
414
|
-
|
|
415
|
-
onSessionPersist: (sessionId) => {
|
|
416
|
-
const session = sessionManager.get(sessionId);
|
|
417
|
-
if (session) {
|
|
418
|
-
void sessionManager.persist(session);
|
|
419
|
-
// Also mirror to the legacy file-based store so the /sessions
|
|
420
|
-
// endpoints (read by the UI history panel) see chat sessions.
|
|
421
|
-
// Without this, chat sessions only land in PGLite and the UI
|
|
422
|
-
// only sees automation/task sessions. PGLite remains the source
|
|
423
|
-
// of truth — a mirror failure is logged and swallowed (the hook
|
|
424
|
-
// is invoked from fireDrainHooks after the response has drained,
|
|
425
|
-
// so throwing here would break the route handler).
|
|
426
|
-
try {
|
|
427
|
-
legacySessionStore.save({
|
|
428
|
-
id: session.id,
|
|
429
|
-
appId: session.appId,
|
|
430
|
-
title: session.metadata.title,
|
|
431
|
-
messages: session.messages,
|
|
432
|
-
createdAt: session.createdAt,
|
|
433
|
-
lastAccessedAt: session.lastAccessedAt,
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
catch (err) {
|
|
437
|
-
log.warn('legacy_session_mirror_failed', {
|
|
438
|
-
sessionId: session.id,
|
|
439
|
-
error: err instanceof Error ? err.message : String(err),
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
},
|
|
444
|
-
}),
|
|
541
|
+
summarizeToolResult: config.summarizeToolResult,
|
|
445
542
|
}));
|
|
446
543
|
app.use(createChatRouter({
|
|
447
544
|
sessionManager,
|
|
448
545
|
bundleResolver: { staticBundle: bundle },
|
|
449
546
|
shared,
|
|
547
|
+
summarizeToolResult: config.summarizeToolResult,
|
|
450
548
|
}));
|
|
451
549
|
// Task runner
|
|
452
550
|
app.use(createTaskRouter({ sessionManager, createTaskSession: createAutomationSessionComponents }));
|
|
@@ -462,6 +560,11 @@ export async function createLocalServer(config) {
|
|
|
462
560
|
// Automations
|
|
463
561
|
app.use(createAutomationRouter({ runner }));
|
|
464
562
|
app.use(createWebhookRouter({ runner, webhookSecret: config.webhookSecret }));
|
|
563
|
+
// Messaging channels
|
|
564
|
+
if (channelsResult) {
|
|
565
|
+
app.use('/channels', channelsResult.router);
|
|
566
|
+
log.info('channels_router_mounted', { channels: [...channelsResult.adapters.keys()] });
|
|
567
|
+
}
|
|
465
568
|
// Store REST API (if stores are defined)
|
|
466
569
|
if (storeBackend) {
|
|
467
570
|
app.use(createStoresRouter({ repo: bundle, storeBackend, appId: LOCAL_APP_ID }));
|
|
@@ -520,6 +623,8 @@ export async function createLocalServer(config) {
|
|
|
520
623
|
// Shared resources and session components will pick up the new
|
|
521
624
|
// bundle on next session creation via getBundle().
|
|
522
625
|
log.info('config_reloaded', { name: newBundle.config.name });
|
|
626
|
+
eventBus.emit({ type: 'manifest_changed' });
|
|
627
|
+
eventBus.emit({ type: 'files_changed' });
|
|
523
628
|
});
|
|
524
629
|
watcher.start();
|
|
525
630
|
}
|
|
@@ -539,6 +644,7 @@ export async function createLocalServer(config) {
|
|
|
539
644
|
}
|
|
540
645
|
if (server) {
|
|
541
646
|
const s = server;
|
|
647
|
+
// Stop accepting new connections
|
|
542
648
|
await new Promise((resolve, reject) => {
|
|
543
649
|
s.close((err) => {
|
|
544
650
|
if (err)
|
|
@@ -546,6 +652,9 @@ export async function createLocalServer(config) {
|
|
|
546
652
|
else
|
|
547
653
|
resolve();
|
|
548
654
|
});
|
|
655
|
+
// Force-close existing connections (SSE streams, etc.) so
|
|
656
|
+
// close() doesn't hang waiting for them to drain.
|
|
657
|
+
s.closeAllConnections();
|
|
549
658
|
});
|
|
550
659
|
server = null;
|
|
551
660
|
}
|
|
@@ -560,4 +669,82 @@ export async function createLocalServer(config) {
|
|
|
560
669
|
},
|
|
561
670
|
};
|
|
562
671
|
}
|
|
672
|
+
// ---------------------------------------------------------------------------
|
|
673
|
+
// /sessions + /session/:id response helpers
|
|
674
|
+
// ---------------------------------------------------------------------------
|
|
675
|
+
/** Max length of the first-user-message excerpt shown in session lists. */
|
|
676
|
+
const SUMMARY_EXCERPT_MAX = 80;
|
|
677
|
+
function isRecord(x) {
|
|
678
|
+
return typeof x === 'object' && x !== null;
|
|
679
|
+
}
|
|
680
|
+
function isTextPart(part) {
|
|
681
|
+
return isRecord(part) && part['type'] === 'text' && typeof part['text'] === 'string';
|
|
682
|
+
}
|
|
683
|
+
function isToolCallPart(part) {
|
|
684
|
+
return (isRecord(part) &&
|
|
685
|
+
part['type'] === 'tool-call' &&
|
|
686
|
+
typeof part['toolCallId'] === 'string' &&
|
|
687
|
+
typeof part['toolName'] === 'string');
|
|
688
|
+
}
|
|
689
|
+
function getMessageRole(raw) {
|
|
690
|
+
if (!isRecord(raw))
|
|
691
|
+
return null;
|
|
692
|
+
const role = raw['role'];
|
|
693
|
+
return typeof role === 'string' ? role : null;
|
|
694
|
+
}
|
|
695
|
+
function getMessageContent(raw) {
|
|
696
|
+
if (!isRecord(raw))
|
|
697
|
+
return undefined;
|
|
698
|
+
return raw['content'];
|
|
699
|
+
}
|
|
700
|
+
/** Truncate with an ellipsis when the source exceeds the excerpt budget. */
|
|
701
|
+
function excerpt(s) {
|
|
702
|
+
return s.length > SUMMARY_EXCERPT_MAX ? `${s.slice(0, SUMMARY_EXCERPT_MAX)}…` : s;
|
|
703
|
+
}
|
|
704
|
+
/** Extract the first user-message text from a persisted message array for list summaries. */
|
|
705
|
+
function extractFirstUserText(messages) {
|
|
706
|
+
for (const raw of messages) {
|
|
707
|
+
if (getMessageRole(raw) !== 'user')
|
|
708
|
+
continue;
|
|
709
|
+
const content = getMessageContent(raw);
|
|
710
|
+
if (typeof content === 'string')
|
|
711
|
+
return excerpt(content);
|
|
712
|
+
if (Array.isArray(content)) {
|
|
713
|
+
const firstText = content.find(isTextPart);
|
|
714
|
+
if (firstText)
|
|
715
|
+
return excerpt(firstText.text);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return undefined;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Flatten a persisted `ModelMessage` (ai SDK v6) into the shape the web UI's
|
|
722
|
+
* /session/:id consumer expects: {role, text, toolCalls?}. Returns null for
|
|
723
|
+
* tool-result messages and for assistant turns with no renderable content
|
|
724
|
+
* (the history panel shows conversation + tool-call chips, not raw tool
|
|
725
|
+
* plumbing).
|
|
726
|
+
*/
|
|
727
|
+
function flattenModelMessage(raw) {
|
|
728
|
+
const role = getMessageRole(raw);
|
|
729
|
+
if (role !== 'user' && role !== 'assistant')
|
|
730
|
+
return null;
|
|
731
|
+
const content = getMessageContent(raw);
|
|
732
|
+
if (typeof content === 'string') {
|
|
733
|
+
return { role, text: content };
|
|
734
|
+
}
|
|
735
|
+
if (Array.isArray(content)) {
|
|
736
|
+
const text = content.filter(isTextPart).map((p) => p.text).join('');
|
|
737
|
+
const toolCalls = role === 'assistant'
|
|
738
|
+
? content.filter(isToolCallPart).map((p) => ({
|
|
739
|
+
toolId: p.toolCallId,
|
|
740
|
+
toolName: p.toolName,
|
|
741
|
+
parameters: isRecord(p.input) ? p.input : {},
|
|
742
|
+
}))
|
|
743
|
+
: [];
|
|
744
|
+
if (text.length === 0 && toolCalls.length === 0)
|
|
745
|
+
return null;
|
|
746
|
+
return toolCalls.length > 0 ? { role, text, toolCalls } : { role, text };
|
|
747
|
+
}
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
563
750
|
//# sourceMappingURL=local-server.js.map
|