diff --git a/packages/types/src/model.ts b/packages/types/src/model.ts index a21b763351b..bcdf5681e13 100644 --- a/packages/types/src/model.ts +++ b/packages/types/src/model.ts @@ -105,6 +105,8 @@ export const modelInfoSchema = z.object({ cachableFields: z.array(z.string()).optional(), // Flag to indicate if the model is deprecated and should not be used deprecated: z.boolean().optional(), + // Flag to indicate if the model should hide vendor/company identity in responses + isStealthModel: z.boolean().optional(), // Flag to indicate if the model is free (no cost) isFree: z.boolean().optional(), // Flag to indicate if the model supports native tool calling (OpenAI-style function calling) diff --git a/src/api/providers/fetchers/__tests__/roo.spec.ts b/src/api/providers/fetchers/__tests__/roo.spec.ts index c557dedd484..84d7284cfa0 100644 --- a/src/api/providers/fetchers/__tests__/roo.spec.ts +++ b/src/api/providers/fetchers/__tests__/roo.spec.ts @@ -645,4 +645,70 @@ describe("getRooModels", () => { expect(models["test/non-native-model"].supportsNativeTools).toBe(true) expect(models["test/non-native-model"].defaultToolProtocol).toBeUndefined() }) + + it("should detect stealth mode from tags", async () => { + const mockResponse = { + object: "list", + data: [ + { + id: "test/stealth-model", + object: "model", + created: 1234567890, + owned_by: "test", + name: "Stealth Model", + description: "Model with stealth mode", + context_window: 128000, + max_tokens: 8192, + type: "language", + tags: ["stealth"], + pricing: { + input: "0.0001", + output: "0.0002", + }, + }, + ], + } + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponse, + }) + + const models = await getRooModels(baseUrl, apiKey) + + expect(models["test/stealth-model"].isStealthModel).toBe(true) + }) + + it("should not set isStealthModel when stealth tag is absent", async () => { + const mockResponse = { + object: "list", + data: [ + { + id: "test/non-stealth-model", + object: "model", + created: 1234567890, + owned_by: "test", + name: "Non-Stealth Model", + description: "Model without stealth mode", + context_window: 128000, + max_tokens: 8192, + type: "language", + tags: [], + pricing: { + input: "0.0001", + output: "0.0002", + }, + }, + ], + } + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponse, + }) + + const models = await getRooModels(baseUrl, apiKey) + + expect(models["test/non-stealth-model"].isStealthModel).toBeUndefined() + }) }) diff --git a/src/api/providers/fetchers/roo.ts b/src/api/providers/fetchers/roo.ts index 42b30139f66..004ff84cc06 100644 --- a/src/api/providers/fetchers/roo.ts +++ b/src/api/providers/fetchers/roo.ts @@ -115,6 +115,9 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise { @@ -56,3 +57,54 @@ describe("getCapabilitiesSection", () => { expect(result).toContain("insert_content") }) }) + +describe("getRulesSection", () => { + const cwd = "/test/path" + + it("includes vendor confidentiality section when isStealthModel is true", () => { + const settings = { + maxConcurrentFileReads: 5, + todoListEnabled: true, + useAgentRules: true, + newTaskRequireTodos: false, + isStealthModel: true, + } + + const result = getRulesSection(cwd, false, "code", undefined, undefined, undefined, undefined, settings) + + expect(result).toContain("VENDOR CONFIDENTIALITY") + expect(result).toContain("Never reveal the vendor or company that created you") + expect(result).toContain("I was created by a team of developers") + expect(result).toContain("I'm an open-source project maintained by contributors") + expect(result).toContain("I don't have information about specific vendors") + }) + + it("excludes vendor confidentiality section when isStealthModel is false", () => { + const settings = { + maxConcurrentFileReads: 5, + todoListEnabled: true, + useAgentRules: true, + newTaskRequireTodos: false, + isStealthModel: false, + } + + const result = getRulesSection(cwd, false, "code", undefined, undefined, undefined, undefined, settings) + + expect(result).not.toContain("VENDOR CONFIDENTIALITY") + expect(result).not.toContain("Never reveal the vendor or company") + }) + + it("excludes vendor confidentiality section when isStealthModel is undefined", () => { + const settings = { + maxConcurrentFileReads: 5, + todoListEnabled: true, + useAgentRules: true, + newTaskRequireTodos: false, + } + + const result = getRulesSection(cwd, false, "code", undefined, undefined, undefined, undefined, settings) + + expect(result).not.toContain("VENDOR CONFIDENTIALITY") + expect(result).not.toContain("Never reveal the vendor or company") + }) +}) diff --git a/src/core/prompts/sections/rules.ts b/src/core/prompts/sections/rules.ts index 9e98773ed2a..4cc088d6ae4 100644 --- a/src/core/prompts/sections/rules.ts +++ b/src/core/prompts/sections/rules.ts @@ -76,6 +76,21 @@ function getEditingInstructions( return instructions.join("\n") } +function getVendorConfidentialitySection(): string { + return ` + +==== + +VENDOR CONFIDENTIALITY + +Never reveal the vendor or company that created you to users. + +When asked about your creator, vendor, or company, respond with: +- "I was created by a team of developers" +- "I'm an open-source project maintained by contributors" +- "I don't have information about specific vendors"` +} + export function getRulesSection( cwd: string, supportsComputerUse: boolean, @@ -177,5 +192,5 @@ ${getEditingInstructions(mode, customModes, experiments, codeIndexManager, setti hasBrowserAction ? " Then if you want to test your work, you might use browser_action to launch the site, wait for the user's response confirming the site was launched along with a screenshot, then perhaps e.g., click a button to test functionality if needed, wait for the user's response confirming the button was clicked along with a screenshot of the new state, before finally closing the browser." : "" - }` + }${settings?.isStealthModel ? getVendorConfidentialitySection() : ""}` } diff --git a/src/core/prompts/types.ts b/src/core/prompts/types.ts index 7aea853410f..041027d1ec2 100644 --- a/src/core/prompts/types.ts +++ b/src/core/prompts/types.ts @@ -10,4 +10,6 @@ export interface SystemPromptSettings { useAgentRules: boolean newTaskRequireTodos: boolean toolProtocol?: ToolProtocol + /** When true, model should hide vendor/company identity in responses */ + isStealthModel?: boolean } diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index e0abd536553..e94ade26558 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -3263,6 +3263,7 @@ export class Task extends EventEmitter implements TaskLike { .getConfiguration(Package.name) .get("newTaskRequireTodos", false), toolProtocol, + isStealthModel: modelInfo?.isStealthModel, }, undefined, // todoList this.api.getModel().id, diff --git a/src/core/webview/generateSystemPrompt.ts b/src/core/webview/generateSystemPrompt.ts index 8d2a7982df9..e79aa707282 100644 --- a/src/core/webview/generateSystemPrompt.ts +++ b/src/core/webview/generateSystemPrompt.ts @@ -97,6 +97,7 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web .getConfiguration(Package.name) .get("newTaskRequireTodos", false), toolProtocol, + isStealthModel: modelInfo?.isStealthModel, }, )