@amodalai/runtime 0.3.68 → 0.3.70
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/routes/session-resolver.d.ts +10 -10
- package/dist/src/routes/session-resolver.js +33 -17
- package/dist/src/routes/session-resolver.js.map +1 -1
- package/dist/src/routes/session-resolver.test.js +121 -80
- package/dist/src/routes/session-resolver.test.js.map +1 -1
- package/dist/src/session/drizzle-session-store.d.ts +8 -12
- package/dist/src/session/drizzle-session-store.js +38 -37
- package/dist/src/session/drizzle-session-store.js.map +1 -1
- package/dist/src/session/store.d.ts +5 -5
- package/dist/src/session/store.js +54 -38
- package/dist/src/session/store.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -10,16 +10,16 @@
|
|
|
10
10
|
* resolution, session lookup/resume/create into a single function so
|
|
11
11
|
* chat-stream, ai-stream, and chat routes share the same flow.
|
|
12
12
|
*/
|
|
13
|
-
import type { AgentBundle, CustomToolExecutor, StoreBackend } from
|
|
14
|
-
import type { McpManager, FieldScrubber } from
|
|
15
|
-
import type { NodePgDatabase } from
|
|
16
|
-
import type { StandaloneSessionManager } from
|
|
17
|
-
import type { Session } from
|
|
18
|
-
import type { ToolContext } from
|
|
19
|
-
import type { SessionComponents, SessionType } from
|
|
20
|
-
import type { AuthContext } from
|
|
21
|
-
import type { Logger } from
|
|
22
|
-
import type { CredentialResolver } from
|
|
13
|
+
import type { AgentBundle, CustomToolExecutor, StoreBackend } from "@amodalai/types";
|
|
14
|
+
import type { McpManager, FieldScrubber } from "@amodalai/core";
|
|
15
|
+
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
16
|
+
import type { StandaloneSessionManager } from "../session/manager.js";
|
|
17
|
+
import type { Session } from "../session/types.js";
|
|
18
|
+
import type { ToolContext } from "../tools/types.js";
|
|
19
|
+
import type { SessionComponents, SessionType } from "../session/session-builder.js";
|
|
20
|
+
import type { AuthContext } from "../middleware/auth.js";
|
|
21
|
+
import type { Logger } from "../logger.js";
|
|
22
|
+
import type { CredentialResolver } from "../credentials.js";
|
|
23
23
|
/** How the route resolves an AgentBundle for a given request. */
|
|
24
24
|
export interface BundleResolver {
|
|
25
25
|
/** Static bundle for local dev (used when no deploy_id is provided). */
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Copyright 2026 Amodal Labs, Inc.
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
|
-
import { buildSessionComponents } from
|
|
7
|
-
import { loadIntents } from
|
|
8
|
-
import { loadMemoryContent } from
|
|
9
|
-
import { SessionError } from
|
|
6
|
+
import { buildSessionComponents } from "../session/session-builder.js";
|
|
7
|
+
import { loadIntents } from "../intent/index.js";
|
|
8
|
+
import { loadMemoryContent } from "../tools/memory-tool.js";
|
|
9
|
+
import { SessionError } from "../errors.js";
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
11
|
// Bundle resolution
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
@@ -30,17 +30,20 @@ async function buildComponents(bundle, shared, opts) {
|
|
|
30
30
|
let memoryContent;
|
|
31
31
|
const memoryDb = shared.memoryDb;
|
|
32
32
|
if (bundle.config.memory?.enabled && memoryDb) {
|
|
33
|
-
memoryContent = await loadMemoryContent(memoryDb, shared.appId ??
|
|
34
|
-
shared.logger.info(
|
|
33
|
+
memoryContent = await loadMemoryContent(memoryDb, shared.appId ?? "local", opts.scopeId ?? "");
|
|
34
|
+
shared.logger.info("memory_loaded", {
|
|
35
|
+
contentLength: memoryContent.length,
|
|
36
|
+
scopeId: opts.scopeId ?? "",
|
|
37
|
+
});
|
|
35
38
|
}
|
|
36
39
|
const credentialResolver = shared.buildCredentialResolver?.(opts.scopeId);
|
|
37
40
|
// Load deterministic intents from <repoPath>/intents/. Empty array
|
|
38
41
|
// when the repo doesn't have one (most agents). Loaded async here
|
|
39
42
|
// because esbuild compile + dynamic import are async; the rest of
|
|
40
43
|
// session building stays sync.
|
|
41
|
-
const intents = bundle.source ===
|
|
44
|
+
const intents = bundle.source === "local" && bundle.origin
|
|
42
45
|
? await loadIntents(bundle.origin).catch((err) => {
|
|
43
|
-
shared.logger.warn(
|
|
46
|
+
shared.logger.warn("intent_load_failed", {
|
|
44
47
|
repoPath: bundle.origin,
|
|
45
48
|
error: err instanceof Error ? err.message : String(err),
|
|
46
49
|
});
|
|
@@ -85,12 +88,15 @@ async function buildComponents(bundle, shared, opts) {
|
|
|
85
88
|
*/
|
|
86
89
|
export async function resolveSession(sessionId, opts) {
|
|
87
90
|
const { sessionManager, bundleResolver, shared, auth } = opts;
|
|
88
|
-
const scopeId = opts.scopeId ??
|
|
91
|
+
const scopeId = opts.scopeId ?? "";
|
|
89
92
|
// 1. In-memory lookup (existing live session)
|
|
90
93
|
if (sessionId) {
|
|
91
94
|
const existing = sessionManager.get(sessionId);
|
|
92
95
|
if (existing && existing.toolContextFactory) {
|
|
93
|
-
return {
|
|
96
|
+
return {
|
|
97
|
+
session: existing,
|
|
98
|
+
toolContextFactory: existing.toolContextFactory,
|
|
99
|
+
};
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
// Resolve bundle once — shared between resume and create paths
|
|
@@ -106,7 +112,10 @@ export async function resolveSession(sessionId, opts) {
|
|
|
106
112
|
// regardless of sessionId. Cache the first result and apply it to
|
|
107
113
|
// subsequent components by copying the enhanced systemPrompt.
|
|
108
114
|
if (!hookResult) {
|
|
109
|
-
hookResult = await opts.onSessionBuild(components, {
|
|
115
|
+
hookResult = await opts.onSessionBuild(components, {
|
|
116
|
+
auth,
|
|
117
|
+
bundle: bundle,
|
|
118
|
+
});
|
|
110
119
|
return hookResult;
|
|
111
120
|
}
|
|
112
121
|
// Reuse the enhanced prompt from the first call
|
|
@@ -118,10 +127,13 @@ export async function resolveSession(sessionId, opts) {
|
|
|
118
127
|
if (!resolvedSessionId && scopeId && bundle) {
|
|
119
128
|
const sessionStore = sessionManager.getStore();
|
|
120
129
|
if (sessionStore?.findByScopeId) {
|
|
121
|
-
const found = await sessionStore.findByScopeId(scopeId);
|
|
130
|
+
const found = await sessionStore.findByScopeId(scopeId, shared.appId);
|
|
122
131
|
if (found) {
|
|
123
132
|
resolvedSessionId = found;
|
|
124
|
-
shared.logger.info(
|
|
133
|
+
shared.logger.info("session_resolved_by_scope", {
|
|
134
|
+
scopeId,
|
|
135
|
+
sessionId: found,
|
|
136
|
+
});
|
|
125
137
|
}
|
|
126
138
|
}
|
|
127
139
|
}
|
|
@@ -142,18 +154,22 @@ export async function resolveSession(sessionId, opts) {
|
|
|
142
154
|
permissionChecker: components.permissionChecker,
|
|
143
155
|
systemPrompt: components.systemPrompt,
|
|
144
156
|
toolContextFactory: components.toolContextFactory,
|
|
157
|
+
appId: shared.appId,
|
|
145
158
|
scopeId,
|
|
146
159
|
...(metadata ? { metadata } : {}),
|
|
147
160
|
});
|
|
148
161
|
if (resumed) {
|
|
149
|
-
return {
|
|
162
|
+
return {
|
|
163
|
+
session: resumed,
|
|
164
|
+
toolContextFactory: components.toolContextFactory,
|
|
165
|
+
};
|
|
150
166
|
}
|
|
151
167
|
}
|
|
152
168
|
// 3. Create new session
|
|
153
169
|
if (!bundle) {
|
|
154
|
-
throw new SessionError(
|
|
155
|
-
sessionId: resolvedSessionId ??
|
|
156
|
-
context: { operation:
|
|
170
|
+
throw new SessionError("No bundle available — provide a deploy_id or configure a static bundle", {
|
|
171
|
+
sessionId: resolvedSessionId ?? "new",
|
|
172
|
+
context: { operation: "resolveSession", deployId: opts.deployId },
|
|
157
173
|
});
|
|
158
174
|
}
|
|
159
175
|
const rawComponents = await buildComponents(bundle, shared, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-resolver.js","sourceRoot":"","sources":["../../../src/routes/session-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"session-resolver.js","sourceRoot":"","sources":["../../../src/routes/session-resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,22 +3,25 @@
|
|
|
3
3
|
* Copyright 2026 Amodal Labs, Inc.
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
|
-
import { describe, it, expect, vi, beforeEach } from
|
|
7
|
-
import { resolveBundle, resolveSession } from
|
|
8
|
-
import { SessionError } from
|
|
6
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
7
|
+
import { resolveBundle, resolveSession } from "./session-resolver.js";
|
|
8
|
+
import { SessionError } from "../errors.js";
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// Mock buildSessionComponents — we test the resolver, not the builder
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
12
|
const mockBuildSessionComponents = vi.fn();
|
|
13
|
-
vi.mock(
|
|
13
|
+
vi.mock("../session/session-builder.js", () => ({
|
|
14
14
|
buildSessionComponents: (...args) => mockBuildSessionComponents(...args),
|
|
15
15
|
}));
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
// Fixtures
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
19
|
-
function stubBundle(name =
|
|
19
|
+
function stubBundle(name = "test-agent") {
|
|
20
20
|
return {
|
|
21
|
-
config: {
|
|
21
|
+
config: {
|
|
22
|
+
name,
|
|
23
|
+
models: { main: { provider: "test", model: "test-model" } },
|
|
24
|
+
},
|
|
22
25
|
connections: new Map(),
|
|
23
26
|
resolvedCredentials: {},
|
|
24
27
|
stores: [],
|
|
@@ -31,18 +34,18 @@ function stubBundle(name = 'test-agent') {
|
|
|
31
34
|
function stubComponents() {
|
|
32
35
|
const factory = vi.fn();
|
|
33
36
|
return {
|
|
34
|
-
provider: { model:
|
|
37
|
+
provider: { model: "test-model", provider: "test" },
|
|
35
38
|
toolRegistry: { size: 0 },
|
|
36
39
|
permissionChecker: { check: () => ({ allowed: true }) },
|
|
37
|
-
systemPrompt:
|
|
40
|
+
systemPrompt: "test prompt",
|
|
38
41
|
toolContextFactory: factory,
|
|
39
42
|
};
|
|
40
43
|
}
|
|
41
44
|
function stubSession(id, hasFactory = true) {
|
|
42
45
|
return {
|
|
43
46
|
id,
|
|
44
|
-
model:
|
|
45
|
-
providerName:
|
|
47
|
+
model: "test-model",
|
|
48
|
+
providerName: "test",
|
|
46
49
|
toolContextFactory: hasFactory ? vi.fn() : undefined,
|
|
47
50
|
};
|
|
48
51
|
}
|
|
@@ -51,6 +54,7 @@ function stubSessionManager(overrides = {}) {
|
|
|
51
54
|
get: vi.fn(),
|
|
52
55
|
resume: vi.fn(),
|
|
53
56
|
create: vi.fn(),
|
|
57
|
+
getStore: vi.fn(),
|
|
54
58
|
...overrides,
|
|
55
59
|
};
|
|
56
60
|
}
|
|
@@ -58,33 +62,44 @@ function stubShared() {
|
|
|
58
62
|
return {
|
|
59
63
|
storeBackend: null,
|
|
60
64
|
mcpManager: null,
|
|
61
|
-
logger: {
|
|
65
|
+
logger: {
|
|
66
|
+
info: vi.fn(),
|
|
67
|
+
warn: vi.fn(),
|
|
68
|
+
debug: vi.fn(),
|
|
69
|
+
error: vi.fn(),
|
|
70
|
+
fatal: vi.fn(),
|
|
71
|
+
child: vi.fn(),
|
|
72
|
+
},
|
|
73
|
+
appId: "test-agent",
|
|
62
74
|
};
|
|
63
75
|
}
|
|
64
76
|
// ---------------------------------------------------------------------------
|
|
65
77
|
// resolveBundle
|
|
66
78
|
// ---------------------------------------------------------------------------
|
|
67
|
-
describe(
|
|
68
|
-
it(
|
|
79
|
+
describe("resolveBundle", () => {
|
|
80
|
+
it("returns static bundle when no deployId", async () => {
|
|
69
81
|
const bundle = stubBundle();
|
|
70
82
|
const result = await resolveBundle({ staticBundle: bundle });
|
|
71
83
|
expect(result).toBe(bundle);
|
|
72
84
|
});
|
|
73
|
-
it(
|
|
85
|
+
it("returns null when no bundle available", async () => {
|
|
74
86
|
const result = await resolveBundle({});
|
|
75
87
|
expect(result).toBeNull();
|
|
76
88
|
});
|
|
77
|
-
it(
|
|
89
|
+
it("calls bundleProvider when deployId is provided", async () => {
|
|
78
90
|
const bundle = stubBundle();
|
|
79
91
|
const provider = vi.fn().mockResolvedValue(bundle);
|
|
80
|
-
const result = await resolveBundle({ bundleProvider: provider },
|
|
81
|
-
expect(provider).toHaveBeenCalledWith(
|
|
92
|
+
const result = await resolveBundle({ bundleProvider: provider }, "deploy-1", "token-abc");
|
|
93
|
+
expect(provider).toHaveBeenCalledWith("deploy-1", "token-abc");
|
|
82
94
|
expect(result).toBe(bundle);
|
|
83
95
|
});
|
|
84
|
-
it(
|
|
85
|
-
const staticBundle = stubBundle(
|
|
96
|
+
it("falls back to static bundle when no deployId even if bundleProvider exists", async () => {
|
|
97
|
+
const staticBundle = stubBundle("static");
|
|
86
98
|
const provider = vi.fn();
|
|
87
|
-
const result = await resolveBundle({
|
|
99
|
+
const result = await resolveBundle({
|
|
100
|
+
staticBundle,
|
|
101
|
+
bundleProvider: provider,
|
|
102
|
+
});
|
|
88
103
|
expect(provider).not.toHaveBeenCalled();
|
|
89
104
|
expect(result).toBe(staticBundle);
|
|
90
105
|
});
|
|
@@ -92,15 +107,15 @@ describe('resolveBundle', () => {
|
|
|
92
107
|
// ---------------------------------------------------------------------------
|
|
93
108
|
// resolveSession
|
|
94
109
|
// ---------------------------------------------------------------------------
|
|
95
|
-
describe(
|
|
110
|
+
describe("resolveSession", () => {
|
|
96
111
|
beforeEach(() => {
|
|
97
112
|
vi.clearAllMocks();
|
|
98
113
|
mockBuildSessionComponents.mockReturnValue(stubComponents());
|
|
99
114
|
});
|
|
100
|
-
it(
|
|
101
|
-
const session = stubSession(
|
|
115
|
+
it("returns in-memory session with cached factory (no rebuild)", async () => {
|
|
116
|
+
const session = stubSession("sess-1");
|
|
102
117
|
const mgr = stubSessionManager({ get: vi.fn().mockReturnValue(session) });
|
|
103
|
-
const result = await resolveSession(
|
|
118
|
+
const result = await resolveSession("sess-1", {
|
|
104
119
|
sessionManager: mgr,
|
|
105
120
|
bundleResolver: { staticBundle: stubBundle() },
|
|
106
121
|
shared: stubShared(),
|
|
@@ -109,29 +124,29 @@ describe('resolveSession', () => {
|
|
|
109
124
|
expect(result.toolContextFactory).toBe(session.toolContextFactory);
|
|
110
125
|
expect(mockBuildSessionComponents).not.toHaveBeenCalled();
|
|
111
126
|
});
|
|
112
|
-
it(
|
|
113
|
-
const resumed = stubSession(
|
|
127
|
+
it("resumes from store when not in memory", async () => {
|
|
128
|
+
const resumed = stubSession("sess-2");
|
|
114
129
|
const mgr = stubSessionManager({
|
|
115
130
|
get: vi.fn().mockReturnValue(undefined),
|
|
116
131
|
resume: vi.fn().mockResolvedValue(resumed),
|
|
117
132
|
});
|
|
118
|
-
const result = await resolveSession(
|
|
133
|
+
const result = await resolveSession("sess-2", {
|
|
119
134
|
sessionManager: mgr,
|
|
120
135
|
bundleResolver: { staticBundle: stubBundle() },
|
|
121
136
|
shared: stubShared(),
|
|
122
137
|
});
|
|
123
138
|
expect(result.session).toBe(resumed);
|
|
124
139
|
expect(mockBuildSessionComponents).toHaveBeenCalledOnce();
|
|
125
|
-
expect(mgr.resume).toHaveBeenCalledWith(
|
|
140
|
+
expect(mgr.resume).toHaveBeenCalledWith("sess-2", expect.any(Object));
|
|
126
141
|
});
|
|
127
|
-
it(
|
|
128
|
-
const created = stubSession(
|
|
142
|
+
it("creates new session when session_id not found", async () => {
|
|
143
|
+
const created = stubSession("sess-3");
|
|
129
144
|
const mgr = stubSessionManager({
|
|
130
145
|
get: vi.fn().mockReturnValue(undefined),
|
|
131
146
|
resume: vi.fn().mockResolvedValue(null),
|
|
132
147
|
create: vi.fn().mockReturnValue(created),
|
|
133
148
|
});
|
|
134
|
-
const result = await resolveSession(
|
|
149
|
+
const result = await resolveSession("sess-not-found", {
|
|
135
150
|
sessionManager: mgr,
|
|
136
151
|
bundleResolver: { staticBundle: stubBundle() },
|
|
137
152
|
shared: stubShared(),
|
|
@@ -139,8 +154,8 @@ describe('resolveSession', () => {
|
|
|
139
154
|
expect(result.session).toBe(created);
|
|
140
155
|
expect(mgr.create).toHaveBeenCalledOnce();
|
|
141
156
|
});
|
|
142
|
-
it(
|
|
143
|
-
const created = stubSession(
|
|
157
|
+
it("creates new session when no session_id provided", async () => {
|
|
158
|
+
const created = stubSession("sess-new");
|
|
144
159
|
const mgr = stubSessionManager({
|
|
145
160
|
create: vi.fn().mockReturnValue(created),
|
|
146
161
|
});
|
|
@@ -152,38 +167,54 @@ describe('resolveSession', () => {
|
|
|
152
167
|
expect(result.session).toBe(created);
|
|
153
168
|
expect(mgr.get).not.toHaveBeenCalled();
|
|
154
169
|
});
|
|
155
|
-
it(
|
|
156
|
-
const created = stubSession(
|
|
170
|
+
it("passes deploy metadata when creating a hosted session", async () => {
|
|
171
|
+
const created = stubSession("sess-deploy");
|
|
157
172
|
const mockCreate = vi.fn().mockReturnValue(created);
|
|
158
173
|
const mgr = stubSessionManager({ create: mockCreate });
|
|
159
174
|
await resolveSession(undefined, {
|
|
160
175
|
sessionManager: mgr,
|
|
161
176
|
bundleResolver: { staticBundle: stubBundle() },
|
|
162
177
|
shared: stubShared(),
|
|
163
|
-
deployId:
|
|
178
|
+
deployId: "deploy-123",
|
|
164
179
|
});
|
|
165
180
|
expect(mockCreate.mock.calls[0][0]).toEqual(expect.objectContaining({
|
|
166
|
-
metadata: { deployId:
|
|
181
|
+
metadata: { deployId: "deploy-123" },
|
|
167
182
|
}));
|
|
168
183
|
});
|
|
169
|
-
it(
|
|
170
|
-
const resumed = stubSession(
|
|
184
|
+
it("passes deploy metadata when resuming a hosted session", async () => {
|
|
185
|
+
const resumed = stubSession("sess-resume-deploy");
|
|
171
186
|
const mockResume = vi.fn().mockResolvedValue(resumed);
|
|
172
187
|
const mgr = stubSessionManager({
|
|
173
188
|
get: vi.fn().mockReturnValue(undefined),
|
|
174
189
|
resume: mockResume,
|
|
175
190
|
});
|
|
176
|
-
await resolveSession(
|
|
191
|
+
await resolveSession("sess-resume-deploy", {
|
|
177
192
|
sessionManager: mgr,
|
|
178
193
|
bundleResolver: { staticBundle: stubBundle() },
|
|
179
194
|
shared: stubShared(),
|
|
180
|
-
deployId:
|
|
195
|
+
deployId: "deploy-456",
|
|
181
196
|
});
|
|
182
197
|
expect(mockResume.mock.calls[0][1]).toEqual(expect.objectContaining({
|
|
183
|
-
|
|
198
|
+
appId: "test-agent",
|
|
199
|
+
metadata: { deployId: "deploy-456" },
|
|
184
200
|
}));
|
|
185
201
|
});
|
|
186
|
-
it(
|
|
202
|
+
it("scopes latest-session lookup by agent id", async () => {
|
|
203
|
+
const found = stubSession("scope-session");
|
|
204
|
+
const findByScopeId = vi.fn().mockResolvedValue("scope-session");
|
|
205
|
+
const mgr = stubSessionManager({
|
|
206
|
+
getStore: vi.fn().mockReturnValue({ findByScopeId }),
|
|
207
|
+
resume: vi.fn().mockResolvedValue(found),
|
|
208
|
+
});
|
|
209
|
+
await resolveSession(undefined, {
|
|
210
|
+
sessionManager: mgr,
|
|
211
|
+
bundleResolver: { staticBundle: stubBundle() },
|
|
212
|
+
shared: stubShared(),
|
|
213
|
+
scopeId: "scope-a",
|
|
214
|
+
});
|
|
215
|
+
expect(findByScopeId).toHaveBeenCalledWith("scope-a", "test-agent");
|
|
216
|
+
});
|
|
217
|
+
it("throws SessionError when no bundle available", async () => {
|
|
187
218
|
const mgr = stubSessionManager();
|
|
188
219
|
await expect(resolveSession(undefined, {
|
|
189
220
|
sessionManager: mgr,
|
|
@@ -191,11 +222,11 @@ describe('resolveSession', () => {
|
|
|
191
222
|
shared: stubShared(),
|
|
192
223
|
})).rejects.toThrow(SessionError);
|
|
193
224
|
});
|
|
194
|
-
it(
|
|
225
|
+
it("passes auth context through to the session manager", async () => {
|
|
195
226
|
// Session identity (tenant / user mapping) is no longer threaded
|
|
196
227
|
// through the session manager — callers that need it live at the
|
|
197
228
|
// API boundary. This test is reduced to verify create() is called.
|
|
198
|
-
const created = stubSession(
|
|
229
|
+
const created = stubSession("sess-auth");
|
|
199
230
|
const mgr = stubSessionManager({
|
|
200
231
|
create: vi.fn().mockReturnValue(created),
|
|
201
232
|
});
|
|
@@ -203,12 +234,12 @@ describe('resolveSession', () => {
|
|
|
203
234
|
sessionManager: mgr,
|
|
204
235
|
bundleResolver: { staticBundle: stubBundle() },
|
|
205
236
|
shared: stubShared(),
|
|
206
|
-
auth: { applicationId:
|
|
237
|
+
auth: { applicationId: "app-1", authMethod: "api_key" },
|
|
207
238
|
});
|
|
208
239
|
expect(mgr.create).toHaveBeenCalled();
|
|
209
240
|
});
|
|
210
|
-
it(
|
|
211
|
-
const created = stubSession(
|
|
241
|
+
it("stores toolContextFactory on created session via CreateSessionOptions", async () => {
|
|
242
|
+
const created = stubSession("sess-factory");
|
|
212
243
|
const mockCreate = vi.fn().mockReturnValue(created);
|
|
213
244
|
const mgr = stubSessionManager({ create: mockCreate });
|
|
214
245
|
await resolveSession(undefined, {
|
|
@@ -217,21 +248,21 @@ describe('resolveSession', () => {
|
|
|
217
248
|
shared: stubShared(),
|
|
218
249
|
});
|
|
219
250
|
const createArg = mockCreate.mock.calls[0][0];
|
|
220
|
-
expect(createArg[
|
|
221
|
-
expect(typeof createArg[
|
|
251
|
+
expect(createArg["toolContextFactory"]).toBeDefined();
|
|
252
|
+
expect(typeof createArg["toolContextFactory"]).toBe("function");
|
|
222
253
|
});
|
|
223
254
|
// -------------------------------------------------------------------------
|
|
224
255
|
// onSessionBuild hook
|
|
225
256
|
// -------------------------------------------------------------------------
|
|
226
|
-
describe(
|
|
227
|
-
it(
|
|
228
|
-
const created = stubSession(
|
|
257
|
+
describe("onSessionBuild hook", () => {
|
|
258
|
+
it("enhances components on new session creation", async () => {
|
|
259
|
+
const created = stubSession("sess-hook");
|
|
229
260
|
const mockCreate = vi.fn().mockReturnValue(created);
|
|
230
261
|
const mgr = stubSessionManager({ create: mockCreate });
|
|
231
262
|
const bundle = stubBundle();
|
|
232
263
|
const hook = vi.fn().mockImplementation((components) => ({
|
|
233
264
|
...components,
|
|
234
|
-
systemPrompt: components.systemPrompt +
|
|
265
|
+
systemPrompt: components.systemPrompt + "\n## Extra guidance",
|
|
235
266
|
}));
|
|
236
267
|
await resolveSession(undefined, {
|
|
237
268
|
sessionManager: mgr,
|
|
@@ -239,12 +270,12 @@ describe('resolveSession', () => {
|
|
|
239
270
|
shared: stubShared(),
|
|
240
271
|
onSessionBuild: hook,
|
|
241
272
|
});
|
|
242
|
-
expect(hook).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ systemPrompt:
|
|
273
|
+
expect(hook).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ systemPrompt: "test prompt" }), expect.objectContaining({ bundle }));
|
|
243
274
|
const createArg = mockCreate.mock.calls[0][0];
|
|
244
|
-
expect(createArg[
|
|
275
|
+
expect(createArg["systemPrompt"]).toBe("test prompt\n## Extra guidance");
|
|
245
276
|
});
|
|
246
|
-
it(
|
|
247
|
-
const resumed = stubSession(
|
|
277
|
+
it("enhances components on session resume", async () => {
|
|
278
|
+
const resumed = stubSession("sess-resume-hook");
|
|
248
279
|
const mockResume = vi.fn().mockResolvedValue(resumed);
|
|
249
280
|
const mgr = stubSessionManager({
|
|
250
281
|
get: vi.fn().mockReturnValue(undefined),
|
|
@@ -253,9 +284,9 @@ describe('resolveSession', () => {
|
|
|
253
284
|
const bundle = stubBundle();
|
|
254
285
|
const hook = vi.fn().mockImplementation((components) => ({
|
|
255
286
|
...components,
|
|
256
|
-
systemPrompt: components.systemPrompt +
|
|
287
|
+
systemPrompt: components.systemPrompt + "\n## Roles injected",
|
|
257
288
|
}));
|
|
258
|
-
await resolveSession(
|
|
289
|
+
await resolveSession("sess-resume-hook", {
|
|
259
290
|
sessionManager: mgr,
|
|
260
291
|
bundleResolver: { staticBundle: bundle },
|
|
261
292
|
shared: stubShared(),
|
|
@@ -263,13 +294,13 @@ describe('resolveSession', () => {
|
|
|
263
294
|
});
|
|
264
295
|
expect(hook).toHaveBeenCalledOnce();
|
|
265
296
|
const resumeArg = mockResume.mock.calls[0][1];
|
|
266
|
-
expect(resumeArg[
|
|
297
|
+
expect(resumeArg["systemPrompt"]).toBe("test prompt\n## Roles injected");
|
|
267
298
|
});
|
|
268
|
-
it(
|
|
269
|
-
const session = stubSession(
|
|
299
|
+
it("is not called for in-memory session hits", async () => {
|
|
300
|
+
const session = stubSession("sess-mem");
|
|
270
301
|
const mgr = stubSessionManager({ get: vi.fn().mockReturnValue(session) });
|
|
271
302
|
const hook = vi.fn();
|
|
272
|
-
await resolveSession(
|
|
303
|
+
await resolveSession("sess-mem", {
|
|
273
304
|
sessionManager: mgr,
|
|
274
305
|
bundleResolver: { staticBundle: stubBundle() },
|
|
275
306
|
shared: stubShared(),
|
|
@@ -277,8 +308,8 @@ describe('resolveSession', () => {
|
|
|
277
308
|
});
|
|
278
309
|
expect(hook).not.toHaveBeenCalled();
|
|
279
310
|
});
|
|
280
|
-
it(
|
|
281
|
-
const created = stubSession(
|
|
311
|
+
it("calls hook only once when resume misses and falls through to create", async () => {
|
|
312
|
+
const created = stubSession("sess-fallthrough");
|
|
282
313
|
const mgr = stubSessionManager({
|
|
283
314
|
get: vi.fn().mockReturnValue(undefined),
|
|
284
315
|
resume: vi.fn().mockResolvedValue(null),
|
|
@@ -286,9 +317,9 @@ describe('resolveSession', () => {
|
|
|
286
317
|
});
|
|
287
318
|
const hook = vi.fn().mockImplementation((components) => ({
|
|
288
319
|
...components,
|
|
289
|
-
systemPrompt:
|
|
320
|
+
systemPrompt: "enhanced prompt",
|
|
290
321
|
}));
|
|
291
|
-
await resolveSession(
|
|
322
|
+
await resolveSession("sess-not-in-store", {
|
|
292
323
|
sessionManager: mgr,
|
|
293
324
|
bundleResolver: { staticBundle: stubBundle() },
|
|
294
325
|
shared: stubShared(),
|
|
@@ -296,15 +327,18 @@ describe('resolveSession', () => {
|
|
|
296
327
|
});
|
|
297
328
|
// Hook called once (resume path), cached result reused (create path)
|
|
298
329
|
expect(hook).toHaveBeenCalledOnce();
|
|
299
|
-
const createArg = mgr.create.mock
|
|
300
|
-
|
|
330
|
+
const createArg = mgr.create.mock
|
|
331
|
+
.calls[0][0];
|
|
332
|
+
expect(createArg["systemPrompt"]).toBe("enhanced prompt");
|
|
301
333
|
});
|
|
302
|
-
it(
|
|
303
|
-
const created = stubSession(
|
|
304
|
-
const mgr = stubSessionManager({
|
|
334
|
+
it("works with async hooks", async () => {
|
|
335
|
+
const created = stubSession("sess-async");
|
|
336
|
+
const mgr = stubSessionManager({
|
|
337
|
+
create: vi.fn().mockReturnValue(created),
|
|
338
|
+
});
|
|
305
339
|
const hook = vi.fn().mockImplementation(async (components) => ({
|
|
306
340
|
...components,
|
|
307
|
-
systemPrompt:
|
|
341
|
+
systemPrompt: "async enhanced",
|
|
308
342
|
}));
|
|
309
343
|
await resolveSession(undefined, {
|
|
310
344
|
sessionManager: mgr,
|
|
@@ -312,14 +346,21 @@ describe('resolveSession', () => {
|
|
|
312
346
|
shared: stubShared(),
|
|
313
347
|
onSessionBuild: hook,
|
|
314
348
|
});
|
|
315
|
-
const createArg = mgr.create.mock
|
|
316
|
-
|
|
349
|
+
const createArg = mgr.create.mock
|
|
350
|
+
.calls[0][0];
|
|
351
|
+
expect(createArg["systemPrompt"]).toBe("async enhanced");
|
|
317
352
|
});
|
|
318
|
-
it(
|
|
319
|
-
const created = stubSession(
|
|
320
|
-
const mgr = stubSessionManager({
|
|
353
|
+
it("passes auth context to hook", async () => {
|
|
354
|
+
const created = stubSession("sess-auth-hook");
|
|
355
|
+
const mgr = stubSessionManager({
|
|
356
|
+
create: vi.fn().mockReturnValue(created),
|
|
357
|
+
});
|
|
321
358
|
const hook = vi.fn().mockImplementation((c) => c);
|
|
322
|
-
const auth = {
|
|
359
|
+
const auth = {
|
|
360
|
+
applicationId: "app-1",
|
|
361
|
+
authMethod: "api_key",
|
|
362
|
+
token: "tok-123",
|
|
363
|
+
};
|
|
323
364
|
await resolveSession(undefined, {
|
|
324
365
|
sessionManager: mgr,
|
|
325
366
|
bundleResolver: { staticBundle: stubBundle() },
|