Skip to content

Commit c39be78

Browse files
authored
Merge branch 'main' into feat/bwrap-sandbox-v2
2 parents 2293f4e + 64c50d3 commit c39be78

11 files changed

Lines changed: 152 additions & 129 deletions

File tree

packages/cli/src/config/config.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,7 +1773,7 @@ describe('loadCliConfig model selection', () => {
17731773
});
17741774

17751775
it('always prefers model from argv', async () => {
1776-
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash'];
1776+
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash-preview'];
17771777
const argv = await parseArguments(createTestMergedSettings());
17781778
const config = await loadCliConfig(
17791779
createTestMergedSettings({
@@ -1785,11 +1785,11 @@ describe('loadCliConfig model selection', () => {
17851785
argv,
17861786
);
17871787

1788-
expect(config.getModel()).toBe('gemini-2.5-flash');
1788+
expect(config.getModel()).toBe('gemini-2.5-flash-preview');
17891789
});
17901790

17911791
it('selects the model from argv if provided', async () => {
1792-
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash'];
1792+
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash-preview'];
17931793
const argv = await parseArguments(createTestMergedSettings());
17941794
const config = await loadCliConfig(
17951795
createTestMergedSettings({
@@ -1799,7 +1799,7 @@ describe('loadCliConfig model selection', () => {
17991799
argv,
18001800
);
18011801

1802-
expect(config.getModel()).toBe('gemini-2.5-flash');
1802+
expect(config.getModel()).toBe('gemini-2.5-flash-preview');
18031803
});
18041804

18051805
it('selects the default auto model if provided via auto alias', async () => {

packages/cli/src/config/config.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ import {
3131
type HierarchicalMemory,
3232
coreEvents,
3333
GEMINI_MODEL_ALIAS_AUTO,
34-
isValidModelOrAlias,
35-
getValidModelsAndAliases,
3634
getAdminErrorMessage,
3735
isHeadlessMode,
3836
Config,
@@ -673,18 +671,6 @@ export async function loadCliConfig(
673671
const specifiedModel =
674672
argv.model || process.env['GEMINI_MODEL'] || settings.model?.name;
675673

676-
// Validate the model if one was explicitly specified
677-
if (specifiedModel && specifiedModel !== GEMINI_MODEL_ALIAS_AUTO) {
678-
if (!isValidModelOrAlias(specifiedModel)) {
679-
const validModels = getValidModelsAndAliases();
680-
681-
throw new FatalConfigError(
682-
`Invalid model: "${specifiedModel}"\n\n` +
683-
`Valid models and aliases:\n${validModels.map((m) => ` - ${m}`).join('\n')}\n\n` +
684-
`Use /model to switch models interactively.`,
685-
);
686-
}
687-
}
688674
const resolvedModel =
689675
specifiedModel === GEMINI_MODEL_ALIAS_AUTO
690676
? defaultModel

packages/core/src/agents/agent-scheduler.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,25 @@ describe('agent-scheduler', () => {
120120
expect(schedulerConfig.toolRegistry).toBe(agentRegistry);
121121
expect(schedulerConfig.toolRegistry).not.toBe(mainRegistry);
122122
});
123+
124+
it('should create an AgentLoopContext that has a defined .config property', async () => {
125+
const mockConfig = {
126+
messageBus: mockMessageBus,
127+
toolRegistry: mockToolRegistry,
128+
promptId: 'test-prompt',
129+
} as unknown as Mocked<Config>;
130+
131+
const options = {
132+
schedulerId: 'subagent-1',
133+
toolRegistry: mockToolRegistry as unknown as ToolRegistry,
134+
signal: new AbortController().signal,
135+
};
136+
137+
await scheduleAgentTools(mockConfig as unknown as Config, [], options);
138+
139+
const schedulerContext = vi.mocked(Scheduler).mock.calls[0][0].context;
140+
expect(schedulerContext.config).toBeDefined();
141+
expect(schedulerContext.config.promptId).toBe('test-prompt');
142+
expect(schedulerContext.toolRegistry).toBe(mockToolRegistry);
143+
});
123144
});

packages/core/src/agents/agent-scheduler.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,17 @@ export async function scheduleAgentTools(
6767
configurable: true,
6868
});
6969

70+
const schedulerContext = {
71+
config: agentConfig,
72+
promptId: config.promptId,
73+
toolRegistry,
74+
messageBus: toolRegistry.messageBus,
75+
geminiClient: config.geminiClient,
76+
sandboxManager: config.sandboxManager,
77+
};
78+
7079
const scheduler = new Scheduler({
71-
context: agentConfig,
80+
context: schedulerContext,
7281
messageBus: toolRegistry.messageBus,
7382
getPreferredEditor: getPreferredEditor ?? (() => undefined),
7483
schedulerId,

packages/core/src/config/models.test.ts

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
GEMINI_MODEL_ALIAS_PRO,
2323
GEMINI_MODEL_ALIAS_FLASH,
2424
GEMINI_MODEL_ALIAS_AUTO,
25-
GEMINI_MODEL_ALIAS_FLASH_LITE,
2625
PREVIEW_GEMINI_FLASH_MODEL,
2726
PREVIEW_GEMINI_MODEL_AUTO,
2827
DEFAULT_GEMINI_MODEL_AUTO,
@@ -31,10 +30,6 @@ import {
3130
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
3231
isPreviewModel,
3332
isProModel,
34-
isValidModelOrAlias,
35-
getValidModelsAndAliases,
36-
VALID_GEMINI_MODELS,
37-
VALID_ALIASES,
3833
} from './models.js';
3934

4035
describe('isPreviewModel', () => {
@@ -394,62 +389,3 @@ describe('isActiveModel', () => {
394389
).toBe(false);
395390
});
396391
});
397-
398-
describe('isValidModelOrAlias', () => {
399-
it('should return true for valid model names', () => {
400-
expect(isValidModelOrAlias(DEFAULT_GEMINI_MODEL)).toBe(true);
401-
expect(isValidModelOrAlias(PREVIEW_GEMINI_MODEL)).toBe(true);
402-
expect(isValidModelOrAlias(DEFAULT_GEMINI_FLASH_MODEL)).toBe(true);
403-
expect(isValidModelOrAlias(DEFAULT_GEMINI_FLASH_LITE_MODEL)).toBe(true);
404-
expect(isValidModelOrAlias(PREVIEW_GEMINI_FLASH_MODEL)).toBe(true);
405-
expect(isValidModelOrAlias(PREVIEW_GEMINI_3_1_MODEL)).toBe(true);
406-
expect(isValidModelOrAlias(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL)).toBe(
407-
true,
408-
);
409-
});
410-
411-
it('should return true for valid aliases', () => {
412-
expect(isValidModelOrAlias(GEMINI_MODEL_ALIAS_AUTO)).toBe(true);
413-
expect(isValidModelOrAlias(GEMINI_MODEL_ALIAS_PRO)).toBe(true);
414-
expect(isValidModelOrAlias(GEMINI_MODEL_ALIAS_FLASH)).toBe(true);
415-
expect(isValidModelOrAlias(GEMINI_MODEL_ALIAS_FLASH_LITE)).toBe(true);
416-
expect(isValidModelOrAlias(PREVIEW_GEMINI_MODEL_AUTO)).toBe(true);
417-
expect(isValidModelOrAlias(DEFAULT_GEMINI_MODEL_AUTO)).toBe(true);
418-
});
419-
420-
it('should return true for custom (non-gemini) models', () => {
421-
expect(isValidModelOrAlias('gpt-4')).toBe(true);
422-
expect(isValidModelOrAlias('claude-3')).toBe(true);
423-
expect(isValidModelOrAlias('my-custom-model')).toBe(true);
424-
});
425-
426-
it('should return false for invalid gemini model names', () => {
427-
expect(isValidModelOrAlias('gemini-4-pro')).toBe(false);
428-
expect(isValidModelOrAlias('gemini-99-flash')).toBe(false);
429-
expect(isValidModelOrAlias('gemini-invalid')).toBe(false);
430-
});
431-
});
432-
433-
describe('getValidModelsAndAliases', () => {
434-
it('should return a sorted array', () => {
435-
const result = getValidModelsAndAliases();
436-
const sorted = [...result].sort();
437-
expect(result).toEqual(sorted);
438-
});
439-
440-
it('should include all valid models and aliases', () => {
441-
const result = getValidModelsAndAliases();
442-
for (const model of VALID_GEMINI_MODELS) {
443-
expect(result).toContain(model);
444-
}
445-
for (const alias of VALID_ALIASES) {
446-
expect(result).toContain(alias);
447-
}
448-
});
449-
450-
it('should not contain duplicates', () => {
451-
const result = getValidModelsAndAliases();
452-
const unique = [...new Set(result)];
453-
expect(result).toEqual(unique);
454-
});
455-
});

packages/core/src/config/models.ts

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,6 @@ export const GEMINI_MODEL_ALIAS_PRO = 'pro';
3232
export const GEMINI_MODEL_ALIAS_FLASH = 'flash';
3333
export const GEMINI_MODEL_ALIAS_FLASH_LITE = 'flash-lite';
3434

35-
export const VALID_ALIASES = new Set([
36-
GEMINI_MODEL_ALIAS_AUTO,
37-
GEMINI_MODEL_ALIAS_PRO,
38-
GEMINI_MODEL_ALIAS_FLASH,
39-
GEMINI_MODEL_ALIAS_FLASH_LITE,
40-
PREVIEW_GEMINI_MODEL_AUTO,
41-
DEFAULT_GEMINI_MODEL_AUTO,
42-
]);
43-
4435
export const DEFAULT_GEMINI_EMBEDDING_MODEL = 'gemini-embedding-001';
4536

4637
// Cap the thinking at 8192 to prevent run-away thinking loops.
@@ -292,37 +283,3 @@ export function isActiveModel(
292283
);
293284
}
294285
}
295-
296-
/**
297-
* Checks if the model name is valid (either a valid model or a valid alias).
298-
*
299-
* @param model The model name to check.
300-
* @returns True if the model is valid.
301-
*/
302-
export function isValidModelOrAlias(model: string): boolean {
303-
// Check if it's a valid alias
304-
if (VALID_ALIASES.has(model)) {
305-
return true;
306-
}
307-
308-
// Check if it's a valid model name
309-
if (VALID_GEMINI_MODELS.has(model)) {
310-
return true;
311-
}
312-
313-
// Allow custom models (non-gemini models)
314-
if (!model.startsWith('gemini-')) {
315-
return true;
316-
}
317-
318-
return false;
319-
}
320-
321-
/**
322-
* Gets a list of all valid model names and aliases for error messages.
323-
*
324-
* @returns Array of valid model names and aliases.
325-
*/
326-
export function getValidModelsAndAliases(): string[] {
327-
return [...new Set([...VALID_ALIASES, ...VALID_GEMINI_MODELS])].sort();
328-
}

packages/core/src/config/storage.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,25 @@ describe('Storage – additional helpers', () => {
180180
expect(storageWithSession.getProjectTempPlansDir()).toBe(expected);
181181
});
182182

183+
it('getProjectTempTrackerDir returns ~/.gemini/tmp/<identifier>/tracker when no sessionId is provided', async () => {
184+
await storage.initialize();
185+
const tempDir = storage.getProjectTempDir();
186+
const expected = path.join(tempDir, 'tracker');
187+
expect(storage.getProjectTempTrackerDir()).toBe(expected);
188+
});
189+
190+
it('getProjectTempTrackerDir returns ~/.gemini/tmp/<identifier>/<sessionId>/tracker when sessionId is provided', async () => {
191+
const sessionId = 'test-session-id';
192+
const storageWithSession = new Storage(projectRoot, sessionId);
193+
ProjectRegistry.prototype.getShortId = vi
194+
.fn()
195+
.mockReturnValue(PROJECT_SLUG);
196+
await storageWithSession.initialize();
197+
const tempDir = storageWithSession.getProjectTempDir();
198+
const expected = path.join(tempDir, sessionId, 'tracker');
199+
expect(storageWithSession.getProjectTempTrackerDir()).toBe(expected);
200+
});
201+
183202
describe('Session and JSON Loading', () => {
184203
beforeEach(async () => {
185204
await storage.initialize();

packages/core/src/config/storage.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ export class Storage {
302302
}
303303

304304
getProjectTempTrackerDir(): string {
305+
if (this.sessionId) {
306+
return path.join(this.getProjectTempDir(), this.sessionId, 'tracker');
307+
}
305308
return path.join(this.getProjectTempDir(), 'tracker');
306309
}
307310

packages/core/src/core/coreToolScheduler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export class CoreToolScheduler {
133133
this.onAllToolCallsComplete = options.onAllToolCallsComplete;
134134
this.onToolCallsUpdate = options.onToolCallsUpdate;
135135
this.getPreferredEditor = options.getPreferredEditor;
136-
this.toolExecutor = new ToolExecutor(this.context.config);
136+
this.toolExecutor = new ToolExecutor(this.context);
137137
this.toolModifier = new ToolModificationHandler();
138138

139139
// Subscribe to message bus for ASK_USER policy decisions

packages/core/src/mcp/oauth-utils.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,34 @@ describe('OAuthUtils', () => {
272272
OAuthUtils.discoverOAuthConfig('https://example.com/mcp'),
273273
).rejects.toThrow(/does not match expected/);
274274
});
275+
276+
it('should accept equivalent root resources with and without trailing slash', async () => {
277+
mockFetch
278+
// fetchProtectedResourceMetadata
279+
.mockResolvedValueOnce({
280+
ok: true,
281+
json: () =>
282+
Promise.resolve({
283+
resource: 'https://example.com',
284+
authorization_servers: ['https://auth.example.com'],
285+
bearer_methods_supported: ['header'],
286+
}),
287+
})
288+
// discoverAuthorizationServerMetadata
289+
.mockResolvedValueOnce({
290+
ok: true,
291+
json: () => Promise.resolve(mockAuthServerMetadata),
292+
});
293+
294+
await expect(
295+
OAuthUtils.discoverOAuthConfig('https://example.com'),
296+
).resolves.toEqual({
297+
authorizationUrl: 'https://auth.example.com/authorize',
298+
issuer: 'https://auth.example.com',
299+
tokenUrl: 'https://auth.example.com/token',
300+
scopes: ['read', 'write'],
301+
});
302+
});
275303
});
276304

277305
describe('metadataToOAuthConfig', () => {
@@ -336,6 +364,45 @@ describe('OAuthUtils', () => {
336364
});
337365
});
338366

367+
describe('discoverOAuthFromWWWAuthenticate', () => {
368+
const mockAuthServerMetadata: OAuthAuthorizationServerMetadata = {
369+
issuer: 'https://auth.example.com',
370+
authorization_endpoint: 'https://auth.example.com/authorize',
371+
token_endpoint: 'https://auth.example.com/token',
372+
scopes_supported: ['read', 'write'],
373+
};
374+
375+
it('should accept equivalent root resources with and without trailing slash', async () => {
376+
mockFetch
377+
// fetchProtectedResourceMetadata(resource_metadata URL)
378+
.mockResolvedValueOnce({
379+
ok: true,
380+
json: () =>
381+
Promise.resolve({
382+
resource: 'https://example.com',
383+
authorization_servers: ['https://auth.example.com'],
384+
}),
385+
})
386+
// discoverAuthorizationServerMetadata(auth server well-known URL)
387+
.mockResolvedValueOnce({
388+
ok: true,
389+
json: () => Promise.resolve(mockAuthServerMetadata),
390+
});
391+
392+
const result = await OAuthUtils.discoverOAuthFromWWWAuthenticate(
393+
'Bearer realm="example", resource_metadata="https://example.com/.well-known/oauth-protected-resource"',
394+
'https://example.com/',
395+
);
396+
397+
expect(result).toEqual({
398+
authorizationUrl: 'https://auth.example.com/authorize',
399+
issuer: 'https://auth.example.com',
400+
tokenUrl: 'https://auth.example.com/token',
401+
scopes: ['read', 'write'],
402+
});
403+
});
404+
});
405+
339406
describe('extractBaseUrl', () => {
340407
it('should extract base URL from MCP server URL', () => {
341408
const result = OAuthUtils.extractBaseUrl('https://example.com/mcp/v1');

0 commit comments

Comments
 (0)