feat: multi-provider AI support for editor - #157
Conversation
✅ Deploy Preview for coasys-we ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
4130ec8 to
6f2bd7e
Compare
f6db722 to
56e7fdd
Compare
Multi-provider AI for the template editor. Adds provider management (Anthropic, OpenAI, Google Gemini, OpenRouter, Groq, Ollama, AD4M), health checks, model discovery, and localStorage persistence. Key changes: - providers.ts: provider catalogue, persistence, health probes, migration - aiInfra.ts: OpenAI-compat request path, SSE + non-streaming parsers, thinking tag stripping, inline tool call extraction (4 formats) - EditorStore.tsx: provider signals, health state, model list, sendViaProvider - AiPanel.tsx: unified provider quick-switch UI, auto-open on failure - host.ts: SessionPort provider surface (availableModels, healthStatus, etc.) - templateSurface.ts: classify provider fields as WIRING Local providers (Ollama, AD4M) use non-streaming for reliable tool calls. Qwen3 thinking tags stripped. Inline tool call fallback handles XML tags, code blocks, bare JSON, and direct patches format. 91 tests across aiProviders.test.ts (42) and aiInfra.test.ts (49). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
56e7fdd to
72139ea
Compare
Ollama's OpenAI-compatible /v1/chat/completions endpoint silently ignores the options.num_ctx parameter, hard-capping context at ~16K tokens. The WE schema context alone runs ~75K tokens — the prompt gets truncated and the model never sees tool definitions, so it dumps JSON as text instead of calling update_schema. The fix adds 'ollama' as a third protocol type that routes to the native /api/chat endpoint, which honours options.num_ctx correctly. Verified against real Qwen3.6-35B on Ollama: prompt_eval_count=75282, structured tool_calls returned with proper patches. Changes: - Add 'ollama' protocol to AiProtocol type (providers.ts, host.ts) - Add sendOllamaRequest() using native /api/chat with num_ctx=131072 - Add parseOllamaComplete() for native response format (arguments as objects, message at top level, done_reason instead of finish_reason) - Update sendProviderRequest() dispatcher to route protocol='ollama' - Migrate Ollama provider URL from /v1 suffix to bare origin - Update health check to construct /v1/models URL for Ollama protocol - Also bump AD4M executor num_ctx from 65536 to 131072 - 11 new parseOllamaComplete tests, 3 updated provider URL tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On multi-turn conversations, convertMessagesToOpenAI serializes
tool_calls[].function.arguments as JSON strings (OpenAI wire format).
Ollama's native /api/chat expects them as parsed objects — when it
encounters a string starting with '{', it tries to parse it as a raw
value and fails with:
"Value looks like object, but can't find closing '}' symbol"
Post-process the converted messages to JSON.parse string arguments
back to objects before sending to the native endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Drop context.json: pure encoding noise (em-dash/ellipsis/arrow unicode escaping), zero semantic change - Extract postProcessComplete() shared by parseOpenAIComplete and parseOllamaComplete — eliminates duplicated strip-think / fallback-extract / callback / stop-reason logic - Trim aiInfra.ts header: remove 'Why Ollama?' paragraph that duplicated the comment on sendOllamaRequest - Trim isLocalProvider JSDoc from 7 lines to 1 - Fix orphaned storedAd4mBaseUrl JSDoc in providers.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The ai-context generator failed on CI for two reasons: - editorStore.availableModels was undescribed in stores.ts (exit code 1) - Single-quoted strings containing apostrophes (editor's) broke esbuild parsing when new lines shifted the parser state — switch to double quotes Also regenerate contextData.ts to match generator output (the provider entries were manually added but the generator does not emit them — they are already in stores.ts descriptions which feed schemaContext). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jhweir
left a comment
There was a problem hiding this comment.
Hey, I've had a look over this and think it's best we approach it slightly differently. Given Ad4m now has most of the mutli-provider AI support we need and WE is already partly hooked up to that I think it would make more sense to fill the remaining gaps in Ad4m (mainly adding support for Anthropic) and then handle AI management in WE via the executor. We have an AI section in the main apps settings already so could expand the configuration options there and remove all the config from the template editor component. That way we avoid duplication and have one canonical way to adjust AI config in WE.
I've opened an AD4M PR to fill those remaining gaps here: coasys/ad4m#996
It's a draft while CI re-runs, but the code is complete. Would appreciate your review on it if you have time, since it builds on the tool-calling path you cherry-picked into /v1.
Let me know what you think. Once that's in I'll open a new WE PR to hook it up and bring across the stuff you've done here (health check and dynamic model list etc) into the core WE AI settings, then close this PR.
Summary
Add support for multiple LLM providers in the WE editor's AI chat panel. Previously hardcoded to Anthropic/Claude, the editor now supports any OpenAI-compatible endpoint alongside Anthropic's native API — including custom local endpoints (Ollama, vLLM, llama-server, LM Studio) and the AD4M Executor.
Changes
New:
providers.ts— provider model + persistenceAiProviderinterface with protocol discriminator (anthropic|openai)/v1(OpenAI-compat surface mount point)providerNeedsApiKey()— returns false for AD4M and custom endpointsfetchProviderModels()— queries/modelsendpoint; static fallback for AnthropicclaudeApiKeymigration from AgentSettingsRewritten:
aiInfra.ts— AI SDK + Ollama packageReplaced ~400 lines of hand-rolled SSE parsers and format converters with maintained libraries:
ai,@ai-sdk/openai,@ai-sdk/anthropic) — handles streaming, SSE parsing, wire format conversion for both Anthropic and OpenAI-compatible providersollama/browser) — handles native/api/chatwith propernum_ctxsizing,stream:false+temperature:0for reliable tool callsollama.version()for keyless providers (cached per origin)sendProviderRequest()unchanged — EditorStore needs zero changesNew:
aiToolCalling.e2e.test.ts— end-to-end tool-calling verificationupdate_schemapatches, dispatcher routes correctlyModified:
EditorStore.tsx— provider-based stateapiKeysignal withprovidersSignal,activeProviderIdSignal,activeProvidermemosetActiveProvider,updateProvider,addProvider,removeProvidersendViaProviderreplacessendViaClaudeAgentSettings.claudeApiKeyon initRebuilt:
AiPanel.tsx— unified provider config UIProviderConfigcomponent for every provider type (cloud, AD4M, custom) — same compact bar layout regardless of provider/modelsendpoint, auto-selects first model; shows "No models available" when endpoint returns nothingExtended:
host.ts— SessionPort + EditorAiProviderEditorAiProviderstructural type keeps editor decoupled from app-shellDesign decisions
isProviderReady()checksproviderNeedsApiKey()— AD4M and custom local endpoints only need a base URL.@sharedpath alias only resolves inside app-shell), soproviderNeedsApiKeyandfetchModelsare inlined inAiPanel.tsx./v1/chat/completionsendpoint ignoresoptions.num_ctx, silently truncating context to ~16K tokens — the WE schema prompt alone exceeds 50K tokens, so the model never sees tool definitions. The native/api/chatendpoint respectsnum_ctx. Additionally,stream:false+temperature:0prevents non-deterministic text-vs-tool output. Theollamanpm package handles all native API plumbing.Screenshot