Browse Source

Add vendor confidentiality section to the system prompt for stealth models (#9742)

Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com>
Matt Rubens 1 month ago
parent
commit
aa507ad990

+ 2 - 0
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)

+ 66 - 0
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()
+	})
 })

+ 4 - 0
src/api/providers/fetchers/roo.ts

@@ -115,6 +115,9 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise<Mo
 				// default-native-tools implies tool-use support
 				const supportsNativeTools = tags.includes("tool-use") || hasDefaultNativeTools
 
+				// Determine if the model should hide vendor/company identity (stealth mode)
+				const isStealthModel = tags.includes("stealth")
+
 				// Parse pricing (API returns strings, convert to numbers)
 				const inputPrice = parseApiPrice(pricing.input)
 				const outputPrice = parseApiPrice(pricing.output)
@@ -139,6 +142,7 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise<Mo
 					isFree: tags.includes("free"),
 					defaultTemperature: model.default_temperature,
 					defaultToolProtocol,
+					isStealthModel: isStealthModel || undefined,
 				}
 
 				// Apply model-specific defaults (e.g., defaultToolProtocol)

+ 52 - 0
src/core/prompts/__tests__/sections.spec.ts

@@ -1,5 +1,6 @@
 import { addCustomInstructions } from "../sections/custom-instructions"
 import { getCapabilitiesSection } from "../sections/capabilities"
+import { getRulesSection } from "../sections/rules"
 import type { DiffStrategy, DiffResult, DiffItem } from "../../../shared/tools"
 
 describe("addCustomInstructions", () => {
@@ -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")
+	})
+})

+ 16 - 1
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() : ""}`
 }

+ 2 - 0
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
 }

+ 1 - 0
src/core/task/Task.ts

@@ -3263,6 +3263,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
 						.getConfiguration(Package.name)
 						.get<boolean>("newTaskRequireTodos", false),
 					toolProtocol,
+					isStealthModel: modelInfo?.isStealthModel,
 				},
 				undefined, // todoList
 				this.api.getModel().id,

+ 1 - 0
src/core/webview/generateSystemPrompt.ts

@@ -97,6 +97,7 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
 				.getConfiguration(Package.name)
 				.get<boolean>("newTaskRequireTodos", false),
 			toolProtocol,
+			isStealthModel: modelInfo?.isStealthModel,
 		},
 	)