diff --git a/cad-agent-studio/src/lib/cad-generator-hardcoding.test.ts b/cad-agent-studio/src/lib/cad-generator-hardcoding.test.ts index 728c76c..e7bb3dd 100644 --- a/cad-agent-studio/src/lib/cad-generator-hardcoding.test.ts +++ b/cad-agent-studio/src/lib/cad-generator-hardcoding.test.ts @@ -295,12 +295,27 @@ test("text-to-cad build123d guidance is available from the original skill", asyn }); test("text-to-cad CAD-SkillX references are available through documentation lookup", async () => { - const result = await readTextToCadDocumentation("references/cad-skillx/planning/mounting-plate.planning.md"); + const result = await readTextToCadDocumentation("references/cad-skillx/planning/mounting-plate.planning.md", { + cadSkillxEnabled: true, + }); assert.equal(result.ok, true); if (!result.ok) return; assert.equal(result.content.includes("Mounting Plate Planning Skill"), true); }); +test("text-to-cad CAD-SkillX references are disabled by the code switch", async () => { + const result = await readTextToCadDocumentation("references/cad-skillx/planning/mounting-plate.planning.md"); + assert.equal(result.ok, false); + if (result.ok) return; + assert.equal(result.error.includes("disabled"), true); + + const skill = await readTextToCadDocumentation("skill"); + assert.equal(skill.ok, true); + if (!skill.ok) return; + assert.equal(skill.content.includes("skill-pack-curated-20260807"), false); + assert.equal(skill.content.includes("CAD-SkillX generated optimization references are disabled"), true); +}); + test("text-to-cad documentation lookup rejects paths outside CAD-SkillX references", async () => { const result = await readTextToCadDocumentation("references/cad-skillx/../build123d-modeling.md"); assert.equal(result.ok, false); @@ -317,7 +332,7 @@ test("new build123d generation is bound to the router token and exact API docs", const source = fs.readFileSync(chatSourcePath, "utf8"); assert.equal(source.includes("read_text_to_cad_docs: tool"), true); assert.equal(source.includes("read_build123d_api: tool"), true); - assert.equal(source.includes("loadRequiredTextToCadDocumentation()"), true); + assert.equal(source.includes("loadRequiredTextToCadDocumentation({ cadSkillxEnabled: useCadSkillx })"), true); assert.equal(source.includes("Studio-loaded text-to-cad/build123d base documentation"), true); assert.equal(source.includes("build123d generation requires text-to-cad docs"), false); assert.equal(source.includes("routeToken: z.string().optional()"), true); @@ -326,6 +341,19 @@ test("new build123d generation is bound to the router token and exact API docs", assert.equal(source.includes("服务端已自动补读缺失 API"), true); }); +test("CAD-SkillX code switch is off and not exposed as a UI toggle", () => { + const uiSource = fs.readFileSync(path.join(process.cwd(), "src", "components", "agent-studio.tsx"), "utf8"); + const routeSource = fs.readFileSync(path.join(process.cwd(), "src", "app", "api", "chat", "route.ts"), "utf8"); + const chatSource = fs.readFileSync(chatSourcePath, "utf8"); + assert.equal(uiSource.includes("cad-agent-studio.cad-skillx-enabled"), false); + assert.equal(uiSource.includes("cadSkillxEnabled"), false); + assert.equal(routeSource.includes("cadSkillxEnabled"), false); + assert.equal(chatSource.includes("const CAD_SKILLX_ENABLED = false;"), true); + assert.equal(chatSource.includes("const useCadSkillx = CAD_SKILLX_ENABLED;"), true); + assert.equal(chatSource.includes("CAD-SkillX generated optimization references are disabled"), true); + assert.equal(chatSource.includes("readTextToCadDocumentation(document, { cadSkillxEnabled: useCadSkillx })"), true); +}); + test("build123d API preflight ignores non-build123d imported helpers", () => { const candidates = build123dCallCandidates([ "from pathlib import Path", diff --git a/cad-agent-studio/src/lib/chat.ts b/cad-agent-studio/src/lib/chat.ts index b9af748..2d1d9aa 100644 --- a/cad-agent-studio/src/lib/chat.ts +++ b/cad-agent-studio/src/lib/chat.ts @@ -386,6 +386,7 @@ const SIMPLECADAPI_REFERENCE_ROOT = path.join(SIMPLECADAPI_SKILL_ROOT, "referenc const SIMPLECADAPI_REQUIRED_DOCS = ["skill", "api/README.md", "stdlib/README.md"]; const TEXT_TO_CAD_SKILL_ROOT = path.join(process.cwd(), "..", "text-to-cad", "skills", "cad"); const TEXT_TO_CAD_REQUIRED_DOCS = ["skill", "references/build123d-modeling.md", "references/step-generation.md"]; +const CAD_SKILLX_ENABLED = false; type SimpleCadApiDocumentationResult = | { @@ -512,7 +513,21 @@ type TextToCadDocumentationResult = | { ok: true; document: string; path: string; content: string } | { ok: false; document: string; error: string; suggestedDocuments: string[] }; -export async function readTextToCadDocumentation(document: string): Promise { +type TextToCadDocumentationOptions = { + cadSkillxEnabled?: boolean; +}; + +function stripCadSkillxReferences(content: string) { + return content + .replace(/\n?\n?/g, "\n\n") + .trimEnd(); +} + +export async function readTextToCadDocumentation( + document: string, + options: TextToCadDocumentationOptions = {}, +): Promise { + const cadSkillxEnabled = options.cadSkillxEnabled ?? CAD_SKILLX_ENABLED; const normalized = normalizeTextToCadDocument(document); const fixedDocuments: Record = { skill: path.join(TEXT_TO_CAD_SKILL_ROOT, "SKILL.md"), @@ -535,6 +550,17 @@ export async function readTextToCadDocumentation(document: string): Promise key !== "skill"), + ], + }; + } if (!normalized || !target) { return { ok: false, @@ -550,11 +576,19 @@ export async function readTextToCadDocumentation(document: string): Promise readTextToCadDocumentation(document, options)), + ); const unavailable = results.filter((result) => !result.ok); if (unavailable.length) { throw new Error(`Studio could not load required text-to-cad documentation: ${unavailable.map((result) => result.document).join(", ")}.`); @@ -667,7 +703,16 @@ export function build123dCallCandidates(source: string) { return [...new Set([...candidates, ...memberReferences])].slice(0, 64); } -function buildSystemPrompt(viewerContext: unknown[], attachments: unknown[], taskContext = "") { +function buildSystemPrompt( + viewerContext: unknown[], + attachments: unknown[], + taskContext = "", + options: { cadSkillxEnabled?: boolean } = {}, +) { + const cadSkillxEnabled = options.cadSkillxEnabled ?? CAD_SKILLX_ENABLED; + const build123dDocumentationInstruction = cadSkillxEnabled + ? "When route_cad_request returns build123d_python, Studio automatically loads the text-to-cad skill, build123d-modeling, and step-generation documents into the route result. Do not repeat those reads. If the request matches a CAD-SkillX entry listed there, use read_text_to_cad_docs only for the relevant Planning/Functional/Atomic reference. Before writing source, call read_build123d_api once with every build123d symbol you plan to use. It reads the active build123d runtime's actual signature and docstring. Follow those signatures exactly: do not guess argument order, namespaces, or keyword names. Use build123d enum members exactly, for example Align.CENTER and Mode.SUBTRACT, not strings such as \"CENTER\"." + : "When route_cad_request returns build123d_python, Studio automatically loads the text-to-cad skill, build123d-modeling, and step-generation documents into the route result. Do not repeat those reads. CAD-SkillX generated optimization references are disabled for this request: do not call read_text_to_cad_docs for references/cad-skillx/... and do not use CAD-SkillX Planning/Functional/Atomic guidance. Before writing source, call read_build123d_api once with every build123d symbol you plan to use. It reads the active build123d runtime's actual signature and docstring. Follow those signatures exactly: do not guess argument order, namespaces, or keyword names. Use build123d enum members exactly, for example Align.CENTER and Mode.SUBTRACT, not strings such as \"CENTER\"."; return [ "You are the CAD Agent Studio assistant.", "You help create and modify CAD models through a real server-side CAD generation tool.", @@ -676,7 +721,7 @@ function buildSystemPrompt(viewerContext: unknown[], attachments: unknown[], tas "Do not rely on hardcoded templates, canned examples, mock data, fake filenames, or imaginary viewer state.", "For every new text/image CAD model, call route_cad_request once before writing native source. CAD Router selects SimpleCADAPI only for catalogued standard primary parts; every other new part is text-to-cad/build123d. Use its returned sourceKind in generate_cad. Do not choose the backend yourself and do not read backend documentation for routing.", "When route_cad_request returns simplecadapi_python, first read the original SimpleCADAPI docs through read_simplecadapi_docs: skill, api/README.md, and stdlib/README.md. Then read the exact original API or stdlib Markdown page for every SimpleCADAPI function used in the source. Follow the documented signatures literally; do not invent namespaces or API names. Documentation paths come from the README links, not Python namespaces: for example simplecadapi.ql.value is documented as api/value.md, not ql/value.md. The server rejects source that calls a nonexistent SimpleCADAPI attribute.", - "When route_cad_request returns build123d_python, Studio automatically loads the text-to-cad skill, build123d-modeling, and step-generation documents into the route result. Do not repeat those reads. If the request matches a CAD-SkillX entry listed there, use read_text_to_cad_docs only for the relevant Planning/Functional/Atomic reference. Before writing source, call read_build123d_api once with every build123d symbol you plan to use. It reads the active build123d runtime's actual signature and docstring. Follow those signatures exactly: do not guess argument order, namespaces, or keyword names. Use build123d enum members exactly, for example Align.CENTER and Mode.SUBTRACT, not strings such as \"CENTER\".", + build123dDocumentationInstruction, "When STEP files are attached, the source is teacher and acceptance truth only. Use reconstruct_uploaded_step; never import the teacher as model geometry.", "All uploaded files are available to you. Images are attached directly to the latest user message when the selected provider/model supports vision. STEP/STP and binary files are available through inspect_uploaded_file.", "When the user asks to model from an uploaded image, inspect the image directly from the image part. If the selected provider/model cannot process images, say that clearly and ask the user to switch to a vision-capable OpenAI-compatible model.", @@ -936,6 +981,7 @@ export async function streamAgentResponse({ selectedTaskId?: string; conversationId?: string; }) { + const useCadSkillx = CAD_SKILLX_ENABLED; const requestedConversationId = String(conversationId || "").trim(); const normalizedConversationId = requestedConversationId ? safeConversationId(requestedConversationId) : ""; const requestMessages = messages; @@ -1058,7 +1104,7 @@ export async function streamAgentResponse({ build123dApiSymbolsRead.clear(); routeDecisions.clear(); const build123dBaseDocs = decision.selectedBackend === "build123d" - ? await loadRequiredTextToCadDocumentation() + ? await loadRequiredTextToCadDocumentation({ cadSkillxEnabled: useCadSkillx }) : []; for (const document of build123dBaseDocs) { textToCadDocsRead.add(document.document); @@ -1077,6 +1123,7 @@ export async function streamAgentResponse({ ...(build123dBaseDocs.length ? { backendContext: { source: "Studio-loaded text-to-cad/build123d base documentation", + cadSkillxEnabled: useCadSkillx, documents: build123dBaseDocs.map(({ document, content }) => ({ document, content })), }, } : {}), @@ -1102,13 +1149,15 @@ export async function streamAgentResponse({ description: [ "Read an additional original text-to-cad/build123d reference when the Studio-loaded base documentation identifies a need.", "The base skill, build123d modeling, and STEP generation references are already returned by route_cad_request for build123d routes.", - "Use this for relevant Markdown files below references/cad-skillx/ or optional positioning/inspection references.", + useCadSkillx + ? "Use this for relevant Markdown files below references/cad-skillx/ or optional positioning/inspection references." + : "CAD-SkillX optimization references are disabled; use this only for optional non-cad-skillx references such as positioning or inspection.", ].join(" "), inputSchema: z.object({ document: z.string().describe("Listed text-to-cad skill document key or references/cad-skillx/... Markdown path."), }), execute: async ({ document }) => { - const result = await readTextToCadDocumentation(document); + const result = await readTextToCadDocumentation(document, { cadSkillxEnabled: useCadSkillx }); if (result.ok) textToCadDocsRead.add(result.document); return result; }, @@ -1600,7 +1649,9 @@ export async function streamAgentResponse({ }); const result = streamText({ model: languageModel, - system: buildSystemPrompt(viewerContext, uploadedAttachments, taskContext), + system: buildSystemPrompt(viewerContext, uploadedAttachments, taskContext, { + cadSkillxEnabled: useCadSkillx, + }), tools: cadTools, stopWhen: isStepCount(16), messages: modelMessages,