(modelEnum, modelUid, { toolPreamble, forceDefault, nativeMode, nativeAllowlist, nativeEnvironment } = {})
| 388 | } |
| 389 | |
| 390 | function buildCascadeConfig(modelEnum, modelUid, { toolPreamble, forceDefault, nativeMode, nativeAllowlist, nativeEnvironment } = {}) { |
| 391 | // CascadeConversationalPlannerConfig.planner_mode (field 4) uses |
| 392 | // codeium_common.ConversationalPlannerMode: |
| 393 | // 0 UNSPECIFIED 1 DEFAULT 2 READ_ONLY 3 NO_TOOL |
| 394 | // 4 EXPLORE 5 PLANNING 6 AUTO |
| 395 | // |
| 396 | // Default: NO_TOOL (3). DEFAULT keeps the IDE agent loop alive, which |
| 397 | // is exactly the behaviour the v2.0.65 native bridge wants — the planner |
| 398 | // reflexively proposes view_file / run_command / grep_search_v2 / find, |
| 399 | // and the bridge translates those proposals back into OpenAI tool_calls |
| 400 | // for the caller to execute. Without the bridge, DEFAULT mode produced: |
| 401 | // - stall_warm bursts (15–25s silent tool-execution trajectory steps) |
| 402 | // - "Cascade cannot create /tmp/windsurf-workspace/foo because it |
| 403 | // already exists" on request bursts that reuse the same filename |
| 404 | // - /tmp/windsurf-workspace path leaks inside the chat body |
| 405 | // The bridge tames these by (a) populating CascadeToolConfig.tool_allowlist |
| 406 | // so only the kinds the caller actually has are enabled, (b) injecting |
| 407 | // observation steps via additional_steps[9] so the planner doesn't |
| 408 | // re-execute server-side, (c) ALWAYS sanitising paths on the way out. |
| 409 | // |
| 410 | // When toolPreamble is provided (NO_TOOL emulation path), we inject it |
| 411 | // into the system prompt's tool_calling_section via SectionOverrideConfig |
| 412 | // (OVERRIDE mode). This is far more reliable than user-message injection |
| 413 | // because NO_TOOL mode's system prompt tells the model "you have no |
| 414 | // tools" — which overpowers anything we put in the user message. The |
| 415 | // section override replaces that section directly so the model sees our |
| 416 | // emulated tool definitions at the system-prompt level. |
| 417 | // |
| 418 | // Mode selection: |
| 419 | // nativeMode=true → DEFAULT (1) + tool_allowlist |
| 420 | // forceDefault=true (vision) → DEFAULT (1) without bridge config |
| 421 | // else → NO_TOOL (3) + tool preamble in section overrides |
| 422 | const mode = (nativeMode || forceDefault) ? 1 : 3; |
| 423 | const convParts = [writeVarintField(4, mode)]; |
| 424 | |
| 425 | // ── System prompt section overrides ────────────────────────────────── |
| 426 | // |
| 427 | // CascadeConversationalPlannerConfig section override fields: |
| 428 | // field 10: tool_calling_section |
| 429 | // field 12: additional_instructions_section |
| 430 | // |
| 431 | // Key insight: NO_TOOL mode (planner_mode=3) SUPPRESSES the |
| 432 | // tool_calling_section entirely — SectionOverrideConfig on field 10 is |
| 433 | // injected but never rendered to the model. Verified 2026-04-12: even |
| 434 | // with OVERRIDE mode on field 10, the model said "I don't have access |
| 435 | // to tools" and ignored the emulated definitions. |
| 436 | // |
| 437 | // We deliver tool definitions exclusively via |
| 438 | // additional_instructions_section (field 12, OVERRIDE) which IS |
| 439 | // rendered regardless of planner mode. The earlier code also wrote the |
| 440 | // same blob to field 10 as belt-and-suspenders, but with a 30+ tool |
| 441 | // Claude Code request that doubled the proto-level system payload and |
| 442 | // pushed total LS panel state past the ~30KB ceiling — directly causing |
| 443 | // the "tools work locally but not in cloud" symptom users reported. |
| 444 | // Field 10 is now intentionally left untouched. |
| 445 | if (toolPreamble) { |
| 446 | // ── Client provided OpenAI tools[] ── |
| 447 | // Primary (and only) delivery: additional_instructions_section |
no test coverage detected