Skip to content

fix(ai-chat): sanitize tool schemas for GitHub Copilot LSP#1279

Merged
datlechin merged 5 commits into
mainfrom
fix/copilot-tool-schema-sanitize
May 15, 2026
Merged

fix(ai-chat): sanitize tool schemas for GitHub Copilot LSP#1279
datlechin merged 5 commits into
mainfrom
fix/copilot-tool-schema-sanitize

Conversation

@datlechin
Copy link
Copy Markdown
Member

Problem

GitHub Copilot tool registration fails with schema validation errors:

Copilot tools registration failed: LSP server error (-32602): Schema validation failed:
- /tools/1/inputSchema/properties/schema/type: Expected string
- /tools/2/inputSchema/properties/max_rows/type: Expected string
- /tools/2/inputSchema/properties/schema/type: Expected string
...

Copilot's LSP schema validator requires every type to be a single string. Our shared ChatToolSchemaBuilder emits type: ["string", "null"] for optional fields (required by OpenAI strict mode), which Copilot rejects.

Provider research

Audited every AI provider for tool schema acceptance:

Provider type: [X, null] additionalProperties Sanitizer?
OpenAI Responses (strict mode) Required Required false None (works as-is)
Anthropic Claude Accepts Accepts None
Gemini function_declarations Rejected Rejected Already in GeminiProvider.sanitizeSchemaForGemini
GitHub Copilot LSP Rejected Accepts This PR
OpenAI-compatible (DeepSeek, OpenRouter, generic) Accepts Accepts None
Ollama Accepts Accepts None

The shared schema builder cannot be changed because OpenAI strict mode requires the current format. Each strict provider sanitizes at its egress point.

Fix

ChatToolSpec+Copilot.swift — replaced normalizeForCopilot (which only added an empty required array) with sanitizeForCopilot:

  1. Recursively walks the schema
  2. For each property whose type is [X, "null"]:
    • Rewrites to type: X (single string)
    • Strips null from any companion enum array
    • Removes the property from the parent object's required array (so Copilot understands the field is optional via standard JSON Schema semantics)
  3. Recurses into nested object schemas and array items

Plain type: "string" properties and required fields are untouched. additionalProperties is preserved (Copilot accepts it).

Tests

6 new tests in CopilotSchemaSanitizationTests:

  • rewritesOptionalScalar[X, null] becomes X, field dropped from required
  • preservesRequiredFields — non-nullable scalars stay in required
  • stripsNullFromEnum — enum arrays lose null members
  • mixedRequiredAndOptional — selective drop, doesn't affect non-nullable siblings
  • recursesIntoNested — nested object schemas also sanitized
  • realBuilderOutputIsValid — feeds an actual ChatToolSchemaBuilder output through and asserts no type arrays remain and optional fields are excluded from required

All pass on both arm64 and x86_64.

Test plan

  • Open AI Chat with GitHub Copilot provider selected
  • Send any message that triggers tool registration
  • Verify no "Copilot tools registration failed" log
  • Verify tools actually fire (list_tables, execute_query, etc.)
  • Other providers (Claude, OpenAI, Gemini) still work

@datlechin datlechin merged commit c1b7ee7 into main May 15, 2026
2 checks passed
@datlechin datlechin deleted the fix/copilot-tool-schema-sanitize branch May 15, 2026 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant