Skip to content

Commit 506b5f5

Browse files
kamalcameroncooke
authored andcommitted
deep merge env
1 parent 7356c4c commit 506b5f5

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

src/utils/__tests__/session-aware-tool-factory.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,35 @@ describe('createSessionAwareTool', () => {
258258
expect(parsed.simulatorId).toBe('SIM-123');
259259
expect(parsed.simulatorName).toBeUndefined();
260260
});
261+
262+
it('deep-merges env so user-provided env vars are additive with session defaults', async () => {
263+
const envSchema = z.object({
264+
scheme: z.string(),
265+
projectPath: z.string().optional(),
266+
env: z.record(z.string(), z.string()).optional(),
267+
});
268+
269+
const envHandler = createSessionAwareTool<z.infer<typeof envSchema>>({
270+
internalSchema: envSchema,
271+
logicFunction: async (params) => ({
272+
content: [{ type: 'text', text: JSON.stringify(params.env) }],
273+
isError: false,
274+
}),
275+
getExecutor: () => createMockExecutor({ success: true }),
276+
requirements: [{ allOf: ['scheme'] }],
277+
});
278+
279+
sessionStore.setDefaults({
280+
scheme: 'App',
281+
projectPath: '/a.xcodeproj',
282+
env: { API_KEY: 'abc123', VERBOSE: '1' },
283+
});
284+
285+
// User provides additional env var; session default env vars should be preserved
286+
const result = await envHandler({ env: { DEBUG: 'true', VERBOSE: '0' } });
287+
expect(result.isError).toBe(false);
288+
289+
const parsed = JSON.parse(result.content[0].text);
290+
expect(parsed).toEqual({ API_KEY: 'abc123', DEBUG: 'true', VERBOSE: '0' });
291+
});
261292
});

src/utils/typed-tool-factory.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,14 @@ function createSessionAwareHandler<TParams, TContext>(opts: {
163163
}
164164

165165
// Start with session defaults merged with explicit args (args override session)
166-
const merged: Record<string, unknown> = { ...sessionStore.getAll(), ...sanitizedArgs };
166+
const sessionDefaults = sessionStore.getAll();
167+
const merged: Record<string, unknown> = { ...sessionDefaults, ...sanitizedArgs };
168+
169+
// Deep-merge env: combine session-default env vars with user-provided ones
170+
// (user-provided keys take precedence on conflict)
171+
if (sessionDefaults.env && typeof sanitizedArgs.env === 'object' && sanitizedArgs.env) {
172+
merged.env = { ...sessionDefaults.env, ...(sanitizedArgs.env as Record<string, string>) };
173+
}
167174

168175
// Apply exclusive pair pruning: only when caller provided a concrete (non-null/undefined) value
169176
// for any key in the pair. When activated, drop other keys in the pair coming from session defaults.

0 commit comments

Comments
 (0)