Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions src/vs/workbench/contrib/cortexide/browser/actionIDs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// Normally you'd want to put these exports in the files that register them, but if you do that you'll get an import order error if you import them in certain cases.
// (importing them runs the whole file to get the ID, causing an import error). I guess it's best practice to separate out IDs, pretty annoying...

export const CORTEXIDE_ATTACH_FILE_TO_CHAT_ACTION_ID = 'cortexide.attachFileToChat';

export const CORTEXIDE_CTRL_L_ACTION_ID = 'cortexide.ctrlLAction';

export const CORTEXIDE_CTRL_K_ACTION_ID = 'cortexide.ctrlKAction';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,9 @@ const filterNonCodeContent = (text: string, languageId?: string): string => {
filteredLines.push(line);
}

return filteredLines.join('\n');
const result = filteredLines.join('\n');
// If filtering removed everything, keep the raw model output (issue #27).
return result || text;
};

// postprocesses the result
Expand Down
48 changes: 10 additions & 38 deletions src/vs/workbench/contrib/cortexide/browser/chatThreadService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { Disposable } from '../../../../base/common/lifecycle.js';
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';

import { URI } from '../../../../base/common/uri.js';

import { parseChatThreadsFromStorage } from '../common/chatThreadStorageReviver.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
import { chat_userMessageContent, isABuiltinToolName, builtinToolNames, localToolsetFor, READ_ONLY_SUBAGENT_TOOLS } from '../common/prompt/prompts.js';
Expand All @@ -24,6 +25,7 @@ import { IBackgroundAgentsService } from './backgroundAgentsService.js';
import { approvalTypeOfBuiltinToolName, BuiltinToolCallParams, BuiltinToolResultType, ToolCallParams, ToolName, ToolResult } from '../common/toolsServiceTypes.js';
import { checkToolAllowedInMode } from '../common/toolPermissions.js';
import { classifyCommandRisk, cwdEscapesWorkspace } from '../common/commandRisk.js';
import { formatTodoReminder } from '../common/todoReminder.js';
import { decideAutoApprove } from '../common/autoApprovePolicy.js';
import { AgentFileOpRecord, AgentFileOpType, FileOpIO, undoFileOpsAfterCheckpoint } from '../common/agentFileOps.js';
import { VSBuffer } from '../../../../base/common/buffer.js';
Expand Down Expand Up @@ -558,40 +560,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
// !!! this is important for properly restoring URIs and images from storage
// should probably re-use code from void/src/vs/base/common/marshalling.ts instead. but this is simple enough
private _convertThreadDataFromStorage(threadsStr: string): ChatThreads {
return JSON.parse(threadsStr, (key, value) => {
if (value && typeof value === 'object' && value.$mid === 1) { // $mid is the MarshalledId. $mid === 1 means it is a URI
return URI.from(value); // TODO URI.revive instead of this?
}
// Restore Uint8Array from base64 string for image data
// Only process 'data' keys that are directly under image attachment objects
// Check key === 'data' to match image attachment structure
if (key === 'data') {
if (typeof value === 'string' && value.startsWith('__base64__:')) {
// Handle base64 string format (the normal case)
try {
const base64 = value.substring(11); // Remove '__base64__:' prefix
const binaryString = atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
} catch (e) {
console.error('Failed to decode base64 image data in storage reviver', e);
return value; // Return original value on error
}
} else if (Array.isArray(value)) {
// Handle case where it's already an array but not Uint8Array
// Only convert if it looks like byte data (all numbers 0-255)
if (value.length > 0 && value.every((v: any) => typeof v === 'number' && v >= 0 && v <= 255)) {
return new Uint8Array(value as number[]);
}
}
// For objects, don't try to convert here - let it be handled later if needed
// This prevents infinite recursion and unexpected conversions
}
return value;
});
return parseChatThreadsFromStorage<ChatThreads>(threadsStr);
}

private _readAllThreads(): ChatThreads | null {
Expand Down Expand Up @@ -3726,7 +3695,8 @@ Output ONLY the JSON, no other text. Start with { and end with }.`
chatMode,
repoIndexerPromise: repoIndexerResults ? Promise.resolve(repoIndexerResults) : repoIndexerPromise,
subagentSystemPrompt: runCtx?.systemPromptOverride,
allowedToolNames: runCtx?.allowedToolNames
allowedToolNames: runCtx?.allowedToolNames,
todoReminder: formatTodoReminder(this._toolsService.getLatestTodos())
});
} catch (prepErr) {
// The first prompt assembly can throw (and has no prior messages to fall back to);
Expand Down Expand Up @@ -3818,7 +3788,8 @@ Output ONLY the JSON, no other text. Start with { and end with }.`
chatMode,
repoIndexerPromise: repoIndexerResults ? Promise.resolve(repoIndexerResults) : repoIndexerPromise,
subagentSystemPrompt: runCtx?.systemPromptOverride,
allowedToolNames: runCtx?.allowedToolNames
allowedToolNames: runCtx?.allowedToolNames,
todoReminder: formatTodoReminder(this._toolsService.getLatestTodos())
})
if (prep2.messages && prep2.messages.length > 0) {
messages = prep2.messages
Expand Down Expand Up @@ -3950,7 +3921,8 @@ Output ONLY the JSON, no other text. Start with { and end with }.`
chatMode,
repoIndexerPromise: repoIndexerResults ? Promise.resolve(repoIndexerResults) : repoIndexerPromise,
subagentSystemPrompt: runCtx?.systemPromptOverride,
allowedToolNames: runCtx?.allowedToolNames
allowedToolNames: runCtx?.allowedToolNames,
todoReminder: formatTodoReminder(this._toolsService.getLatestTodos())
});
messages = prepResult.messages;
separateSystemMessage = prepResult.separateSystemMessage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function uint8ArrayToBase64(data: Uint8Array): string {
}
}
import { getIsReasoningEnabledState, getReservedOutputTokenSpace, getModelCapabilities } from '../common/modelCapabilities.js';
import { effectiveSpecialToolFormat } from '../common/providerToolFormat.js';
import { reParsedToolXMLString, chat_systemMessage, chat_systemMessage_local } from '../common/prompt/prompts.js';
import { isCapableLocalModel } from '../common/routing/codingModelScore.js';
import { AnthropicLLMChatMessage, AnthropicReasoning, GeminiLLMChatMessage, LLMChatMessage, LLMFIMMessage, OpenAILLMChatMessage, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
Expand Down Expand Up @@ -1240,7 +1241,7 @@ const prepareMessages = (params: {
export interface IConvertToLLMMessageService {
readonly _serviceBrand: undefined;
prepareLLMSimpleMessages: (opts: { simpleMessages: SimpleLLMMessage[], systemMessage: string, modelSelection: ModelSelection | null, featureName: FeatureName }) => { messages: LLMChatMessage[], separateSystemMessage: string | undefined }
prepareLLMChatMessages: (opts: { chatMessages: ChatMessage[], chatMode: ChatMode, modelSelection: ModelSelection | null, repoIndexerPromise?: Promise<{ results: string[], metrics: any } | null>, subagentSystemPrompt?: string, allowedToolNames?: string[] }) => Promise<{ messages: LLMChatMessage[], separateSystemMessage: string | undefined }>
prepareLLMChatMessages: (opts: { chatMessages: ChatMessage[], chatMode: ChatMode, modelSelection: ModelSelection | null, repoIndexerPromise?: Promise<{ results: string[], metrics: any } | null>, subagentSystemPrompt?: string, allowedToolNames?: string[], todoReminder?: string }) => Promise<{ messages: LLMChatMessage[], separateSystemMessage: string | undefined }>
prepareFIMMessage(opts: { messages: LLMFIMMessage, modelSelection: ModelSelection | null, featureName: FeatureName, languageId?: string }): { prefix: string, suffix: string, stopTokens: string[] }
startRepoIndexerQuery: (chatMessages: ChatMessage[], chatMode: ChatMode) => Promise<{ results: string[], metrics: any } | null>
}
Expand Down Expand Up @@ -1488,7 +1489,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
systemMessage: enrichedSystemMessage,
aiInstructions,
supportsSystemMessage,
specialToolFormat,
specialToolFormat: effectiveSpecialToolFormat(specialToolFormat, isLocal),
supportsAnthropicReasoning: providerName === 'anthropic',
contextWindow: effectiveContextWindow,
reservedOutputTokenSpace: effectiveReservedOutput,
Expand Down Expand Up @@ -1519,7 +1520,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
}
}

prepareLLMChatMessages: IConvertToLLMMessageService['prepareLLMChatMessages'] = async ({ chatMessages, chatMode, modelSelection, repoIndexerPromise, subagentSystemPrompt, allowedToolNames }) => {
prepareLLMChatMessages: IConvertToLLMMessageService['prepareLLMChatMessages'] = async ({ chatMessages, chatMode, modelSelection, repoIndexerPromise, subagentSystemPrompt, allowedToolNames, todoReminder }) => {
if (modelSelection === null) return { messages: [], separateSystemMessage: undefined }

const { overridesOfModel } = this.cortexideSettingsService.state
Expand Down Expand Up @@ -1683,7 +1684,13 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
const modelSelectionOptions = this.cortexideSettingsService.state.optionsOfModelSelection['Chat'][validProviderName]?.[modelName]

// Get combined AI instructions
const aiInstructions = this._getCombinedAIInstructions();
let aiInstructions = this._getCombinedAIInstructions();
// Re-inject the agent's current todo list as fresh working memory each turn. Folded into the
// per-turn instructions (like rules) rather than the CACHED system message, so it stays current
// as steps complete. Empty -> caller passes undefined -> zero impact (e.g. normal/plan turns).
if (todoReminder) {
aiInstructions = aiInstructions ? `${aiInstructions}\n\n${todoReminder}` : todoReminder;
}
const isReasoningEnabled = getIsReasoningEnabledState('Chat', validProviderName, modelName, modelSelectionOptions, overridesOfModel)
const reservedOutputTokenSpace = getReservedOutputTokenSpace(validProviderName, modelName, { isReasoningEnabled, overridesOfModel })
let llmMessages = this._chatMessagesToSimpleMessages(chatMessages)
Expand Down Expand Up @@ -1789,7 +1796,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
// Local providers don't actually return native tool_calls (the calls arrive as XML/JSON text),
// so encode prior tool turns with the XML/text format to stay consistent with the system prompt
// + parser — otherwise turn 2+ of the agent loop loses all prior tool context (finding #8).
specialToolFormat: isLocalProviderForContext ? undefined : specialToolFormat,
specialToolFormat: effectiveSpecialToolFormat(specialToolFormat, isLocalProviderForContext),
supportsAnthropicReasoning: validProviderName === 'anthropic',
contextWindow,
reservedOutputTokenSpace,
Expand Down
Loading
Loading