Skip to content

Commit 64d2660

Browse files
committed
Merge remote-tracking branch 'origin/main' into roblou/official-hare
2 parents 9fc3e54 + 2e4678a commit 64d2660

File tree

12 files changed

+229
-41
lines changed

12 files changed

+229
-41
lines changed

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,10 @@
17021702
"when": "true",
17031703
"contents": "%github.copilot.viewsWelcome.codexPlaceholder%"
17041704
},
1705+
{
1706+
"view": "workbench.view.chat.sessions.openai-codex",
1707+
"contents": "%github.copilot.viewsWelcome.codexWelcomeView%"
1708+
},
17051709
{
17061710
"view": "copilot-agents-placeholder",
17071711
"when": "true",
@@ -3960,7 +3964,7 @@
39603964
{
39613965
"id": "codex-placeholder",
39623966
"name": "OpenAI Codex",
3963-
"when": "github.copilot.chat.codex.notInstalled && config.chat.experimental.codex.enabled",
3967+
"when": "github.copilot.chat.codex.showPlaceholder && config.chat.experimental.codex.enabled",
39643968
"icon": "$(file)"
39653969
},
39663970
{

package.nls.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,20 @@
7878
]
7979
},
8080
"github.copilot.viewsWelcome.codexPlaceholder": {
81-
"message": "[Start a coding session](command:github.copilot.chat.installAgent?%7B%22agent%22%3A%22codex%22%7D)\n\nThis will install [the OpenAI Codex extension](command:workbench.extensions.action.showExtensionsWithIds?%5B%5B%22openai.chatgpt%22%5D%5D).",
81+
"message": "[Start OpenAI Codex Session](command:github.copilot.chat.installAgent?%7B%22agent%22%3A%22codex%22%7D)\n\nThis will install [the OpenAI Codex extension](command:workbench.extensions.action.showExtensionsWithIds?%5B%5B%22openai.chatgpt%22%5D%5D).",
8282
"comment": [
8383
"{Locked='['}",
8484
"{Locked='](command:github.copilot.chat.installAgent?%7B%22agent%22%3A%22codex%22%7D)'}",
8585
"{Locked='](command:workbench.extensions.action.showExtensionsWithIds?%5B%5B%22openai.chatgpt%22%5D%5D)'}"
8686
]
8787
},
88+
"github.copilot.viewsWelcome.codexWelcomeView": {
89+
"message": "[Start OpenAI Codex Session](command:chatgpt.newCodexPanel)",
90+
"comment": [
91+
"{Locked='['}",
92+
"{Locked='](command:chatgpt.newCodexPanel)'}"
93+
]
94+
},
8895
"github.copilot.viewsWelcome.agentsPlaceholder": {
8996
"message": "Sign in to access GitHub Copilot CLI and Cloud Agents.\n\n[Sign in](command:workbench.action.chat.triggerSetupForceSignIn)",
9097
"comment": [

src/extension/chatSessions/vscode-node/copilotCloudSessionContentBuilder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export class ChatSessionContentBuilder {
159159
private async createResponseTurn(pullRequest: PullRequestSearchItem, logs: string, session: SessionInfo): Promise<ChatResponseTurn2 | undefined> {
160160
if (logs.trim().length > 0) {
161161
return await this.parseSessionLogsIntoResponseTurn(pullRequest, logs, session);
162-
} else if (session.state === 'in_progress') {
162+
} else if (session.state === 'in_progress' || session.state === 'queued') {
163163
// For in-progress sessions without logs, create a placeholder response
164164
const placeholderParts = [new ChatResponseProgressPart('Session is initializing...')];
165165
const responseResult: ChatResult = {};

src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -330,19 +330,20 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch
330330
pr: PullRequestSearchItem
331331
): ((stream: vscode.ChatResponseStream, token: vscode.CancellationToken) => Thenable<void>) | undefined {
332332
// Only the latest in-progress session gets activeResponseCallback
333-
const inProgressSession = sessions
333+
const pendingSession = sessions
334334
.slice()
335335
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
336-
.find(session => session.state === 'in_progress');
336+
.find(session => session.state === 'in_progress' || session.state === 'queued');
337337

338-
if (inProgressSession) {
339-
return this.createActiveResponseCallback(pr, inProgressSession.id);
338+
if (pendingSession) {
339+
return this.createActiveResponseCallback(pr, pendingSession.id);
340340
}
341341
return undefined;
342342
}
343343

344344
private createActiveResponseCallback(pr: PullRequestSearchItem, sessionId: string): (stream: vscode.ChatResponseStream, token: vscode.CancellationToken) => Thenable<void> {
345345
return async (stream: vscode.ChatResponseStream, token: vscode.CancellationToken) => {
346+
await this.waitForQueuedToInProgress(sessionId, token);
346347
return this.streamSessionLogs(stream, pr, sessionId, token);
347348
};
348349
}
@@ -841,6 +842,10 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch
841842
} while (waitForQueuedCount <= waitForQueuedMaxRetries && (!token || !token.isCancellationRequested));
842843

843844
if (!sessionInfo || sessionInfo.state !== 'queued') {
845+
if (sessionInfo?.state === 'in_progress') {
846+
this.logService.trace('Session already in progress');
847+
return sessionInfo;
848+
}
844849
// Failure
845850
this.logService.trace('Failed to find queued session');
846851
return;
@@ -859,6 +864,7 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch
859864
}
860865
await new Promise(resolve => setTimeout(resolve, pollInterval));
861866
}
867+
this.logService.error(`Timed out waiting for session ${sessionId} to transition from queued to in_progress.`);
862868
}
863869

864870
private async waitForNewSession(
@@ -1117,9 +1123,6 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch
11171123

11181124
const webviewUri = await toOpenPullRequestWebviewUri({ owner: pullRequest.repository.owner.login, repo: pullRequest.repository.name, pullRequestNumber: number });
11191125
const prLlmString = `The remote agent has begun work and has created a pull request. Details about the pull request are being shown to the user. If the user wants to track progress or iterate on the agent's work, they should use the pull request.`;
1120-
1121-
chatStream?.progress(vscode.l10n.t('Attaching to session'));
1122-
await this.waitForQueuedToInProgress(response.session_id, token);
11231126
return {
11241127
state: 'success',
11251128
number,

src/extension/contextKeys/vscode-node/placeholderView.contribution.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,38 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as vscode from 'vscode';
7+
import { IAuthenticationService } from '../../../platform/authentication/common/authentication';
78
import { IRunCommandExecutionService } from '../../../platform/commands/common/runCommandExecutionService';
9+
import { IConfigurationService } from '../../../platform/configuration/common/configurationService';
810
import { IEnvService } from '../../../platform/env/common/envService';
11+
import { Event } from '../../../util/vs/base/common/event';
912
import { Disposable } from '../../../util/vs/base/common/lifecycle';
1013

11-
const CodexPlaceholderKey = 'github.copilot.chat.codex.notInstalled';
14+
const ShowCodexPlaceholderKey = 'github.copilot.chat.codex.showPlaceholder';
1215

1316
export class PlaceholderViewContribution extends Disposable {
1417
constructor(
1518
@IRunCommandExecutionService private readonly _commandService: IRunCommandExecutionService,
1619
@IEnvService private readonly envService: IEnvService,
20+
@IAuthenticationService authenticationService: IAuthenticationService,
21+
@IConfigurationService configurationService: IConfigurationService
1722
) {
1823
super();
1924

25+
let curShouldShowPlaceholder: boolean | undefined = undefined;
2026
const updateContextKey = () => {
27+
const token = authenticationService.copilotToken;
28+
const enabledForUser = token && (token.codexAgentEnabled || configurationService.getNonExtensionConfig('chat.experimental.codex.enabled'));
2129
const codexExtension = vscode.extensions.getExtension('openai.chatgpt');
22-
void vscode.commands.executeCommand('setContext', CodexPlaceholderKey, !codexExtension);
30+
const shouldShowPlaceholder = enabledForUser && !codexExtension;
31+
if (curShouldShowPlaceholder !== shouldShowPlaceholder) {
32+
curShouldShowPlaceholder = shouldShowPlaceholder;
33+
void vscode.commands.executeCommand('setContext', ShowCodexPlaceholderKey, shouldShowPlaceholder);
34+
}
2335
};
2436

25-
updateContextKey();
2637
this._register(vscode.extensions.onDidChange(updateContextKey));
38+
this._register(Event.runAndSubscribe(authenticationService.onDidAuthenticationChange, updateContextKey));
2739

2840
this._register(vscode.commands.registerCommand('github.copilot.chat.installAgent', this.installAgentCommand, this));
2941
}

src/extension/externalAgents/node/oaiLanguageModelServer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export class OpenAILanguageModelServer extends Disposable {
167167
res,
168168
endpointRequestBody,
169169
headers,
170-
'vscode_codex'
170+
'codex_vscode'
171171
);
172172

173173
await streamingEndpoint.makeChatRequest2({

src/extension/tools/node/findFilesTool.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IPromptPathRepresentationService } from '../../../platform/prompts/comm
99
import { URI } from '../../../util/vs/base/common/uri';
1010

1111
import * as l10n from '@vscode/l10n';
12+
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
1213
import { ISearchService } from '../../../platform/search/common/searchService';
1314
import { IWorkspaceService } from '../../../platform/workspace/common/workspaceService';
1415
import { raceTimeoutAndCancellationError } from '../../../util/common/racePromise';
@@ -33,13 +34,16 @@ export class FindFilesTool implements ICopilotTool<IFindFilesToolParams> {
3334
@IInstantiationService private readonly instantiationService: IInstantiationService,
3435
@ISearchService private readonly searchService: ISearchService,
3536
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
37+
@IEndpointProvider private readonly endpointProvider: IEndpointProvider,
3638
) { }
3739

3840
async invoke(options: vscode.LanguageModelToolInvocationOptions<IFindFilesToolParams>, token: CancellationToken) {
3941
checkCancellation(token);
42+
const endpoint = options.model && (await this.endpointProvider.getChatEndpoint(options.model));
43+
const modelFamily = endpoint?.family;
4044

4145
// The input _should_ be a pattern matching inside a workspace, folder, but sometimes we get absolute paths, so try to resolve them
42-
const pattern = inputGlobToPattern(options.input.query, this.workspaceService);
46+
const pattern = inputGlobToPattern(options.input.query, this.workspaceService, modelFamily);
4347

4448
// try find text with a timeout of 20s
4549
const timeoutInMs = 20_000;

src/extension/tools/node/findTextInFilesTool.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as l10n from '@vscode/l10n';
77
import { BasePromptElementProps, PromptElement, PromptElementProps, PromptPiece, PromptReference, PromptSizing, TextChunk } from '@vscode/prompt-tsx';
88
import type * as vscode from 'vscode';
99
import { OffsetLineColumnConverter } from '../../../platform/editing/common/offsetLineColumnConverter';
10+
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
1011
import { IPromptPathRepresentationService } from '../../../platform/prompts/common/promptPathRepresentationService';
1112
import { ISearchService } from '../../../platform/search/common/searchService';
1213
import { IWorkspaceService } from '../../../platform/workspace/common/workspaceService';
@@ -41,11 +42,15 @@ export class FindTextInFilesTool implements ICopilotTool<IFindTextInFilesToolPar
4142
@IInstantiationService private readonly instantiationService: IInstantiationService,
4243
@ISearchService private readonly searchService: ISearchService,
4344
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
45+
@IEndpointProvider private readonly endpointProvider: IEndpointProvider,
4446
) { }
4547

4648
async invoke(options: vscode.LanguageModelToolInvocationOptions<IFindTextInFilesToolParams>, token: CancellationToken) {
49+
const endpoint = options.model && (await this.endpointProvider.getChatEndpoint(options.model));
50+
const modelFamily = endpoint?.family;
51+
4752
// The input _should_ be a pattern matching inside a workspace, folder, but sometimes we get absolute paths, so try to resolve them
48-
const patterns = options.input.includePattern ? inputGlobToPattern(options.input.includePattern, this.workspaceService) : undefined;
53+
const patterns = options.input.includePattern ? inputGlobToPattern(options.input.includePattern, this.workspaceService, modelFamily) : undefined;
4954

5055
checkCancellation(token);
5156
const askedForTooManyResults = options.input.maxResults && options.input.maxResults > MaxResultsCap;

src/extension/tools/node/test/findFiles.spec.tsx

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { afterEach, beforeEach, expect, suite, test } from 'vitest';
77
import type * as vscode from 'vscode';
8+
import { IEndpointProvider } from '../../../../platform/endpoint/common/endpointProvider';
89
import { RelativePattern } from '../../../../platform/filesystem/common/fileTypes';
910
import { AbstractSearchService, ISearchService } from '../../../../platform/search/common/searchService';
1011
import { ITestingServicesAccessor, TestingServiceCollection } from '../../../../platform/test/node/services';
@@ -18,6 +19,7 @@ import { IInstantiationService } from '../../../../util/vs/platform/instantiatio
1819
import { createExtensionUnitTestingServices } from '../../../test/node/services';
1920
import { CopilotToolMode } from '../../common/toolsRegistry';
2021
import { FindFilesTool, IFindFilesToolParams } from '../findFilesTool';
22+
import { createMockEndpointProvider, mockLanguageModelChat } from './searchToolTestUtils';
2123

2224
suite('FindFiles', () => {
2325
let accessor: ITestingServicesAccessor;
@@ -34,38 +36,78 @@ suite('FindFiles', () => {
3436
accessor.dispose();
3537
});
3638

37-
function setup(expected: vscode.GlobPattern) {
39+
function setup(expected: vscode.GlobPattern, includeExtraPattern = true, modelFamily?: string) {
40+
if (modelFamily) {
41+
collection.define(IEndpointProvider, createMockEndpointProvider(modelFamily));
42+
}
43+
3844
const patterns: vscode.GlobPattern[] = [expected];
39-
if (typeof expected === 'string' && !expected.endsWith('/**')) {
40-
patterns.push(expected + '/**');
41-
} else if (typeof expected !== 'string' && !expected.pattern.endsWith('/**')) {
42-
patterns.push(new RelativePattern(expected.baseUri, expected.pattern + '/**'));
45+
if (includeExtraPattern) {
46+
if (typeof expected === 'string' && !expected.endsWith('/**')) {
47+
patterns.push(expected + '/**');
48+
} else if (typeof expected !== 'string' && !expected.pattern.endsWith('/**')) {
49+
patterns.push(new RelativePattern(expected.baseUri, expected.pattern + '/**'));
50+
}
4351
}
4452
collection.define(ISearchService, new TestSearchService(patterns));
4553
accessor = collection.createTestingAccessor();
4654
}
4755

4856
test('passes through simple query', async () => {
49-
setup('test/**/*.ts');
57+
setup('test/**/*.ts', false);
5058

5159
const tool = accessor.get(IInstantiationService).createInstance(FindFilesTool);
5260
await tool.invoke({ input: { query: 'test/**/*.ts' }, toolInvocationToken: null!, }, CancellationToken.None);
5361
});
5462

5563
test('handles absolute path with glob', async () => {
56-
setup(new RelativePattern(URI.file(workspaceFolder), 'test/**/*.ts'));
64+
setup(new RelativePattern(URI.file(workspaceFolder), 'test/**/*.ts'), false);
5765

5866
const tool = accessor.get(IInstantiationService).createInstance(FindFilesTool);
5967
await tool.invoke({ input: { query: `${workspaceFolder}/test/**/*.ts` }, toolInvocationToken: null!, }, CancellationToken.None);
6068
});
6169

6270
test('handles absolute path to folder', async () => {
63-
setup(new RelativePattern(URI.file(workspaceFolder), ''));
71+
setup(new RelativePattern(URI.file(workspaceFolder), ''), false);
6472

6573
const tool = accessor.get(IInstantiationService).createInstance(FindFilesTool);
6674
await tool.invoke({ input: { query: workspaceFolder }, toolInvocationToken: null!, }, CancellationToken.None);
6775
});
6876

77+
suite('gpt-4.1 model glob pattern', () => {
78+
test('adds extra pattern for gpt-4.1 model with simple query', async () => {
79+
setup('src', true, 'gpt-4.1');
80+
81+
const tool = accessor.get(IInstantiationService).createInstance(FindFilesTool);
82+
const result = await tool.invoke({ input: { query: 'src' }, toolInvocationToken: null!, model: mockLanguageModelChat }, CancellationToken.None);
83+
expect(result).toBeDefined();
84+
});
85+
86+
test('adds extra pattern for gpt-4.1 with string query ending in /**', async () => {
87+
setup('src/**', true, 'gpt-4.1');
88+
89+
const tool = accessor.get(IInstantiationService).createInstance(FindFilesTool);
90+
const result = await tool.invoke({ input: { query: 'src/**' }, toolInvocationToken: null!, model: mockLanguageModelChat }, CancellationToken.None);
91+
expect(result).toBeDefined();
92+
});
93+
94+
test('adds extra pattern for gpt-4.1 with RelativePattern', async () => {
95+
setup(new RelativePattern(URI.file(workspaceFolder), 'src'), true, 'gpt-4.1');
96+
97+
const tool = accessor.get(IInstantiationService).createInstance(FindFilesTool);
98+
const result = await tool.invoke({ input: { query: `${workspaceFolder}/src` }, toolInvocationToken: null!, model: mockLanguageModelChat }, CancellationToken.None);
99+
expect(result).toBeDefined();
100+
});
101+
102+
test('does not duplicate extra pattern when RelativePattern already ends with /**', async () => {
103+
setup(new RelativePattern(URI.file(workspaceFolder), 'src/**'), true, 'gpt-4.1');
104+
105+
const tool = accessor.get(IInstantiationService).createInstance(FindFilesTool);
106+
const result = await tool.invoke({ input: { query: `${workspaceFolder}/src/**` }, toolInvocationToken: null!, model: mockLanguageModelChat }, CancellationToken.None);
107+
expect(result).toBeDefined();
108+
});
109+
});
110+
69111
suite('resolveInput', () => {
70112
beforeEach(() => {
71113
setup('hello');

0 commit comments

Comments
 (0)