Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6be9580
Try adopting native sanitizer instead of dompurify
mjbvz Jun 5, 2026
95744c7
Set AI_AGENT env var for agent terminals (#320129)
meganrogge Jun 5, 2026
c83b87f
Fix terminal "Rerun Task" closing the task without restarting it (#32…
Copilot Jun 5, 2026
841ce52
Merge pull request #320026 from microsoft/dev/mjbvz/psychiatric-kingf…
mjbvz Jun 5, 2026
cf16646
fix: surface missing Git LFS git errors (#319973)
KirtiRamchandani Jun 5, 2026
f2aa048
use nonce when comparing customizations changes (#320102)
aeschli Jun 5, 2026
cbb9055
sessions: split session model from view service (#320139)
sandy081 Jun 5, 2026
c4fa031
Scope notification.acceptPrimaryAction keybinding to screen reader us…
Copilot Jun 5, 2026
0712539
fix(terminal): catch ENOPRO in getCwdResource when file:// provider a…
mutl3y Jun 5, 2026
72496e4
Fix ordering of cost strings (#320150)
lramos15 Jun 5, 2026
868350b
fix(explorer): guard file-explorer scroll handler against transient t…
maruthang Jun 5, 2026
101c205
Use copilot/copilot-utility-small for terminal-tool steering messages…
meganrogge Jun 5, 2026
a9efb8d
Cap get_terminal_output first-poll and non-prefix responses to a tail…
meganrogge Jun 5, 2026
99d6bf7
Add context size picker for Claude Agent models (#320166)
TylerLeonhardt Jun 5, 2026
c2d6b5a
Reinforce timeout + do-not-poll guidance in terminal tool description…
meganrogge Jun 5, 2026
43ec3d9
Preserve symlink paths in terminal sandbox policies (#320172)
dileepyavan Jun 5, 2026
8131d06
Bind MCP auth grants to the server URL to require re-consent on URL c…
TylerLeonhardt Jun 5, 2026
0a43759
Also drop keypress/link events if webview isn't focused
mjbvz Jun 5, 2026
b1bbbe5
Fix semantic token rendering glitch with injected text (#320178)
alexdima Jun 5, 2026
f027331
chat: add experimental default chat provider setting for VS Code wind…
connor4312 Jun 5, 2026
18bda6b
Merge pull request #320180 from microsoft/dev/mjbvz/unexpected-rhinoc…
mjbvz Jun 5, 2026
373aead
Add inline 'Create pull request' confirmation for completed tasks wit…
osortega Jun 5, 2026
23b9618
communing with the codebase (#320185)
justschen Jun 5, 2026
00a11ae
Add "vp" (Vite+) to npm.scriptRunner enum (#320183)
meganrogge Jun 5, 2026
bf6c2ae
Browser `type` tool improvements (#320186)
kycutler Jun 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/skills/sessions/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ Then read the relevant spec for the area you are changing (see table below). If
- **Modifying workbench code**: Prefer extending/wrapping workbench classes in the sessions layer over modifying shared workbench components.
- **Timeouts as fixes**: Never use `setTimeout`/`disposableTimeout`/arbitrary delays to fix bugs or implement behaviour. They are race-prone guesses that mask the real ordering/state problem. Drive logic off deterministic signals instead — observables (`autorun`/`derived`), explicit events (`onDidChange*`), lifecycle phases, or awaiting the actual async operation.
- **Stashed state read back later (side-channels)**: Never stash a value on a service during one method call and read it back from a separate query later, assuming it is still valid (e.g. a `Set`/flag set in `openSession` and consumed by a `shouldX()` pull-API). This is fragile temporal coupling. Instead, make it reactive state that is set **atomically together with its source of truth** and consumed reactively. Example: per-activation intent like "open in background / preserve focus" is exposed as an `IObservable` set in the **same transaction** as `activeSession` (via a single internal setter so it can never go stale), and read with `.read(reader)` in the consumer's `autorun` — never via a consume-once getter.
- **Blocking on a "pending/waiting" state instead of creating + upgrading**: When an entity (e.g. a draft session) depends on something that registers asynchronously, don't withhold creation behind a pending/waiting state. Prefer creating immediately with the best available data, then **replace/upgrade** it once the awaited dependency arrives (driven by an `onDidChange*`/observable signal), cancelling the upgrade if the user changes the inputs meanwhile. Do **not** bound the upgrade with a timeout or even a lifecycle milestone like `LifecyclePhase.Eventually` — an agent host connects lazily and can surface its session types after `Eventually` has already fired, which would lock in the wrong fallback. Let the upgrade listener live for the consumer's lifetime instead.
- **Persisting a picker value only on manual selection**: A picker that writes storage only when the user actively changes the dropdown will lose any auto-selected/defaulted value on reload (the stored preference is empty, so it falls back to a default). Persist on **every** change of the underlying value — exactly like a normal picker — so the last effective value always survives reload. Example: `SessionTypePicker` writes `{ providerId, sessionTypeId }` from both the manual pick handler and the active-session `refresh`, through the single `_writeStoredPick` persistence point.
- **Pre-validating a stored preference against not-yet-ready state in an event handler**: Don't re-check a restored preference against currently-available data (e.g. validating the stored session-type pick against `getSessionTypesForFolder` inside `onDidSelectWorkspace`) and null it out on mismatch. During reload the awaited provider hasn't registered yet, so a perfectly valid preference is discarded and the wrong default is locked in. Pass the preference straight through to the single place that reconciles preference-vs-availability (which creates now and upgrades later).
- **Blocking on a "pending/waiting" state instead of creating + upgrading**: When an entity (e.g. a draft session) depends on something that registers asynchronously, don't withhold creation behind a pending/waiting state. Prefer creating immediately with the best available data, then **replace/upgrade** it once the awaited dependency arrives (driven by an `onDidChange*`/observable signal), cancelling the upgrade if the user changes the inputs meanwhile. Do **not** bound the upgrade with a timeout or even a lifecycle milestone like `LifecyclePhase.Eventually` — an agent host connects lazily and can surface its session types arbitrarily late, which would lock in the wrong fallback. Let the upgrade listener live for the consumer's lifetime instead.

## Capturing Feedback (meta-rule)

Expand Down
17 changes: 0 additions & 17 deletions extensions/copilot/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion extensions/copilot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,12 @@
"icon": "$(git-pull-request)",
"category": "GitHub Pull Request"
},
{
"command": "github.copilot.chat.cloudSessions.createPullRequestForTask",
"title": "%github.copilot.command.cloudSessions.createPullRequestForTask.title%",
"icon": "$(git-pull-request-create)",
"category": "GitHub Pull Request"
},
{
"command": "github.copilot.chat.cloudSessions.openRepository",
"title": "%github.copilot.command.cloudSessions.openRepository.title%",
Expand Down Expand Up @@ -5403,6 +5409,11 @@
"command": "github.copilot.chat.checkoutPullRequestReroute",
"when": "chatSessionType == copilot-cloud-agent && !github.vscode-pull-request-github.activated && gitOpenRepositoryCount != 0",
"group": "navigation@0"
},
{
"command": "github.copilot.chat.cloudSessions.createPullRequestForTask",
"when": "chatSessionType == copilot-cloud-agent && github.copilot.chat.cloudTaskCanCreatePullRequest && !isSessionsWindow",
"group": "navigation@0"
}
],
"agents/changes/actions/primary": [
Expand Down Expand Up @@ -7024,7 +7035,6 @@
"applicationinsights": "^2.9.7",
"best-effort-json-parser": "^1.2.1",
"diff": "^8.0.3",
"dompurify": "^3.4.8",
"express": "^5.2.1",
"ignore": "^7.0.5",
"isbinaryfile": "^5.0.4",
Expand Down
1 change: 1 addition & 0 deletions extensions/copilot/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@
"github.copilot.chat.createPullRequestCopilotCLIAgentSession.createPR": "Create Pull Request",
"github.copilot.chat.createDraftPullRequestCopilotCLIAgentSession.createDraftPR": "Create Draft Pull Request",
"github.copilot.command.checkoutPullRequestReroute.title": "Checkout",
"github.copilot.command.cloudSessions.createPullRequestForTask.title": "Create Pull Request",
"github.copilot.command.cloudSessions.openRepository.title": "Browse repositories...",
"github.copilot.command.cloudSessions.clearCaches.title": "Clear Cloud Agent Caches",
"github.copilot.command.applyCopilotCLIAgentSessionChanges": "Apply Changes to Workspace",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface SessionState {
folderInfo: ClaudeFolderInfo | undefined;
usageHandler: UsageHandler | undefined;
reasoningEffort: EffortLevel | undefined;
contextSize: number | undefined;
traceContext: TraceContext | undefined;
turnId: string | undefined;
}
Expand Down Expand Up @@ -106,6 +107,18 @@ export interface IClaudeSessionStateService {
*/
setReasoningEffortForSession(sessionId: string, effort: EffortLevel | undefined): void;

/**
* Gets the context size for a session (user's per-request selection from the model picker).
* When set, the proxy reports this value as the endpoint's modelMaxPromptTokens so internal
* accounting reflects the chosen tier.
*/
getContextSizeForSession(sessionId: string): number | undefined;

/**
* Sets the context size for a session.
*/
setContextSizeForSession(sessionId: string, contextSize: number | undefined): void;

/**
* Gets the OTel trace context for a session (used to parent chat spans to invoke_agent).
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type * as vscode from 'vscode';
import { IEndpointProvider } from '../../../../platform/endpoint/common/endpointProvider';
import { ILogService } from '../../../../platform/log/common/logService';
import { IChatEndpoint } from '../../../../platform/networking/common/networking';
import { formatPricingLabel, getModelCapabilitiesDescription } from '../../../conversation/common/languageModelAccess';
import { formatPricingLabel, formatTokenCount, getModelCapabilitiesDescription } from '../../../conversation/common/languageModelAccess';
import { createServiceIdentifier } from '../../../../util/common/services';
import { Emitter } from '../../../../util/vs/base/common/event';
import { Disposable } from '../../../../util/vs/base/common/lifecycle';
Expand All @@ -17,6 +17,7 @@ import { tryParseClaudeModelId } from './claudeModelId';
import type { EffortLevel } from '@anthropic-ai/claude-agent-sdk';

export const CLAUDE_REASONING_EFFORT_PROPERTY = 'reasoningEffort';
export const CLAUDE_CONTEXT_SIZE_PROPERTY = 'contextSize';

export interface IClaudeCodeModels {
readonly _serviceBrand: undefined;
Expand Down Expand Up @@ -214,33 +215,57 @@ export function pickReasoningEffort(endpoint: IChatEndpoint | undefined, request
}

function buildConfigurationSchema(endpoint: IChatEndpoint): vscode.LanguageModelConfigurationSchema | undefined {
const properties: Record<string, NonNullable<vscode.LanguageModelConfigurationSchema['properties']>[string]> = {};

// Thinking effort
const effortLevels = endpoint.supportsReasoningEffort?.filter(
(level): level is typeof SUPPORTED_EFFORT_LEVELS[number] =>
(SUPPORTED_EFFORT_LEVELS as readonly string[]).includes(level)
);
if (!effortLevels) {
return;
if (effortLevels && effortLevels.length > 0) {
const defaultEffort = effortLevels.includes('high') ? 'high' : undefined;
properties[CLAUDE_REASONING_EFFORT_PROPERTY] = {
type: 'string',
title: l10n.t('Thinking Effort'),
enum: effortLevels,
enumItemLabels: effortLevels.map(level => level.charAt(0).toUpperCase() + level.slice(1)),
enumDescriptions: effortLevels.map(level => {
switch (level) {
case 'low': return l10n.t('Faster responses with less reasoning');
case 'medium': return l10n.t('Balanced reasoning and speed');
case 'high': return l10n.t('Greater reasoning depth but slower');
}
}),
default: defaultEffort,
group: 'navigation',
};
}

const defaultEffort = effortLevels.includes('high') ? 'high' : undefined;

return {
properties: {
[CLAUDE_REASONING_EFFORT_PROPERTY]: {
type: 'string',
title: l10n.t('Thinking Effort'),
enum: effortLevels,
enumItemLabels: effortLevels.map(level => level.charAt(0).toUpperCase() + level.slice(1)),
enumDescriptions: effortLevels.map(level => {
switch (level) {
case 'low': return l10n.t('Faster responses with less reasoning');
case 'medium': return l10n.t('Balanced reasoning and speed');
case 'high': return l10n.t('Greater reasoning depth but slower');
}
}),
default: defaultEffort,
group: 'navigation',
}
}
};
// Context size — only when CAPI provides a default context max, indicating
// a meaningful distinction between default and long context tiers.
const pricing = endpoint.tokenPricing;
const defaultContextMax = pricing?.default.contextMax;
const fullMax = endpoint.modelMaxPromptTokens;
if (defaultContextMax && defaultContextMax < fullMax) {
const hasLongContextSurcharge = !!pricing?.longContext;
properties[CLAUDE_CONTEXT_SIZE_PROPERTY] = {
type: 'number',
title: l10n.t('Context Size'),
enum: [defaultContextMax, fullMax],
enumItemLabels: [formatTokenCount(defaultContextMax), formatTokenCount(fullMax)],
enumDescriptions: [
l10n.t('Default'),
hasLongContextSurcharge
? l10n.t('Longer sessions')
: l10n.t('Longer sessions without compaction'),
],
default: defaultContextMax,
group: 'tokens',
};
}

if (Object.keys(properties).length === 0) {
return undefined;
}
return { properties };
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,17 @@ export class ClaudeLanguageModelServer extends Disposable {
tokenSource.cancel();
});

// If the user picked a context tier in the model picker, surface that
// value directly as the prompt token budget. CAPI's `tokenPricing.default.contextMax`
// is documented as a prompt-token limit (matching `endpoint.modelMaxPromptTokens`),
// and CAPI bills the tier based on actual prompt size, so no other signaling is
// required. Clamp to the default budget so the override never lowers it.
const sessionContextSize = sessionId ? this.sessionStateService.getContextSizeForSession(sessionId) : undefined;
const defaultPromptBudget = DEFAULT_MAX_TOKENS - DEFAULT_MAX_OUTPUT_TOKENS;
const modelMaxPromptTokens = sessionContextSize !== undefined
? Math.max(defaultPromptBudget, sessionContextSize)
: defaultPromptBudget;

const endpointRequestBody = requestBody as IEndpointBody;
const streamingEndpoint = this.instantiationService.createInstance(
ClaudeStreamingPassThroughEndpoint,
Expand All @@ -200,7 +211,7 @@ export class ClaudeLanguageModelServer extends Disposable {
headers,
'vscode_claude_code',
{
modelMaxPromptTokens: DEFAULT_MAX_TOKENS - DEFAULT_MAX_OUTPUT_TOKENS,
modelMaxPromptTokens,
maxOutputTokens: DEFAULT_MAX_OUTPUT_TOKENS
},
sessionId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class ClaudeSessionStateService extends Disposable implements IClaudeSess
folderInfo: existing?.folderInfo,
usageHandler: existing?.usageHandler,
reasoningEffort: existing?.reasoningEffort,
contextSize: existing?.contextSize,
traceContext: existing?.traceContext,
turnId: existing?.turnId,
});
Expand All @@ -69,6 +70,7 @@ export class ClaudeSessionStateService extends Disposable implements IClaudeSess
folderInfo: existing?.folderInfo,
usageHandler: existing?.usageHandler,
reasoningEffort: existing?.reasoningEffort,
contextSize: existing?.contextSize,
traceContext: existing?.traceContext,
turnId: existing?.turnId,
});
Expand All @@ -88,6 +90,7 @@ export class ClaudeSessionStateService extends Disposable implements IClaudeSess
folderInfo: existing?.folderInfo,
usageHandler: existing?.usageHandler,
reasoningEffort: existing?.reasoningEffort,
contextSize: existing?.contextSize,
traceContext: existing?.traceContext,
turnId: existing?.turnId,
});
Expand All @@ -109,6 +112,7 @@ export class ClaudeSessionStateService extends Disposable implements IClaudeSess
folderInfo,
usageHandler: existing?.usageHandler,
reasoningEffort: existing?.reasoningEffort,
contextSize: existing?.contextSize,
traceContext: existing?.traceContext,
turnId: existing?.turnId,
});
Expand All @@ -128,6 +132,7 @@ export class ClaudeSessionStateService extends Disposable implements IClaudeSess
folderInfo: existing?.folderInfo,
usageHandler: handler,
reasoningEffort: existing?.reasoningEffort,
contextSize: existing?.contextSize,
traceContext: existing?.traceContext,
turnId: existing?.turnId,
});
Expand All @@ -149,6 +154,29 @@ export class ClaudeSessionStateService extends Disposable implements IClaudeSess
folderInfo: existing?.folderInfo,
usageHandler: existing?.usageHandler,
reasoningEffort: effort,
contextSize: existing?.contextSize,
traceContext: existing?.traceContext,
turnId: existing?.turnId,
});
}

getContextSizeForSession(sessionId: string): number | undefined {
return this._sessionState.get(sessionId)?.contextSize;
}

setContextSizeForSession(sessionId: string, contextSize: number | undefined): void {
const existing = this._sessionState.get(sessionId);
if (existing?.contextSize === contextSize) {
return;
}
this._sessionState.set(sessionId, {
modelId: existing?.modelId,
permissionMode: existing?.permissionMode ?? 'acceptEdits',
capturingToken: existing?.capturingToken,
folderInfo: existing?.folderInfo,
usageHandler: existing?.usageHandler,
reasoningEffort: existing?.reasoningEffort,
contextSize,
traceContext: existing?.traceContext,
turnId: existing?.turnId,
});
Expand All @@ -167,6 +195,7 @@ export class ClaudeSessionStateService extends Disposable implements IClaudeSess
folderInfo: existing?.folderInfo,
usageHandler: existing?.usageHandler,
reasoningEffort: existing?.reasoningEffort,
contextSize: existing?.contextSize,
traceContext,
turnId: existing?.turnId,
});
Expand All @@ -185,6 +214,7 @@ export class ClaudeSessionStateService extends Disposable implements IClaudeSess
folderInfo: existing?.folderInfo,
usageHandler: existing?.usageHandler,
reasoningEffort: existing?.reasoningEffort,
contextSize: existing?.contextSize,
traceContext: existing?.traceContext,
turnId,
});
Expand Down
Loading
Loading