WHAT: Fix critical CLI crash with content.filter() error and implement OpenAI Responses API integration with comprehensive testing WHY: CLI was crashing with 'TypeError: undefined is not an object (evaluating "content.filter")' when using OpenAI models, preventing users from making API calls. Additionally needed proper Responses API support with reasoning tokens. HOW: • Fix content extraction from OpenAI response structure in legacy path • Add JSON/Zod schema detection in responsesAPI adapter • Create comprehensive test suite for both integration and production scenarios • Document the new adapter architecture and usage CRITICAL FIXES: • claude.ts: Extract content from response.choices[0].message.content instead of undefined response.content • responsesAPI.ts: Detect if schema is already JSON (has 'type' property) vs Zod schema before conversion FILES: • src/services/claude.ts - Critical bug fix for OpenAI response content extraction • src/services/adapters/responsesAPI.ts - Robust schema detection for tool parameters • src/test/integration-cli-flow.test.ts - Integration tests for full flow • src/test/chat-completions-e2e.test.ts - End-to-end Chat Completions compatibility tests • src/test/production-api-tests.test.ts - Production API tests with environment configuration • docs/develop/modules/openai-adapters.md - New adapter system documentation • docs/develop/README.md - Updated development documentation
5.0 KiB
OpenAI Adapter Layer
This module explains how Kode’s Anthropic-first conversation engine can selectively route requests through OpenAI Chat Completions or the new Responses API without exposing that complexity to the rest of the system. The adapter layer only runs when USE_NEW_ADAPTERS !== 'false' and a ModelProfile is available.
Goals
- Preserve Anthropic-native data structures (
AssistantMessage,MessageParam, tool blocks) everywhere outside the adapter layer. - Translate those structures into a provider-neutral
UnifiedRequestParamsshape so different adapters can share logic. - Map the unified format onto each provider’s transport (Chat Completions vs Responses API) and back into Anthropic-style
AssistantMessageobjects.
Request Flow
-
Anthropic Messages → Unified Params
queryOpenAI(src/services/claude.ts) converts the existing Anthropic message history into OpenAI-style role/content pairs viaconvertAnthropicMessagesToOpenAIMessages, flattens system prompts, and builds aUnifiedRequestParamsbundle (seesrc/types/modelCapabilities.ts). This bundle captures:messages: already normalized to OpenAI format but still provider-neutral inside the adapters.systemPrompt: array of strings, preserving multi-block Anthropic system prompts.tools: tool metadata (names, descriptions, JSON schema) fetched once so adapters can reshape it.maxTokens,stream,reasoningEffort,verbosity,previousResponseId, andtemperatureflags.
-
Adapter Selection
ModelAdapterFactoryinspects theModelProfileand capability table (src/constants/modelCapabilities.ts) to choose either:ChatCompletionsAdapterfor classic/chat/completionsstyle providers.ResponsesAPIAdapterwhen the provider natively supports/responses.
-
Adapter-Specific Request Construction
- Chat Completions (
src/services/adapters/chatCompletions.ts)- Reassembles a single message list including system prompts.
- Picks the correct max-token field (
max_tokensvsmax_completion_tokens). - Attaches OpenAI function-calling tool descriptors, optional
stream_options, reasoning effort, and verbosity when supported. - Handles model quirks (e.g., removes unsupported fields for
o1models).
- Responses API (
src/services/adapters/responsesAPI.ts)- Converts chat-style messages into
inputitems (message blocks, function-call outputs, images). - Moves system prompts into the
instructionsstring. - Uses
max_output_tokens, always enables streaming, and addsincludeentries for reasoning envelopes. - Emits the flat
toolsarray expected by/responses,tool_choice,parallel_tool_calls, state IDs, verbosity controls, etc.
- Converts chat-style messages into
- Chat Completions (
-
Transport
Both adapters delegate the actual network call to helpers insrc/services/openai.ts:- Chat Completions requests use
getCompletionWithProfile(legacy path) or the same helperqueryOpenAIpreviously relied on. - Responses API requests go through
callGPT5ResponsesAPI, which POSTs the adapter-built payload and returns the rawResponseobject for streaming support.
- Chat Completions requests use
Response Flow
-
Raw Response → Unified Response
ChatCompletionsAdapter.parseResponsepulls the firstchoice, extracts tool calls, and normalizes usage counts.ResponsesAPIAdapter.parseResponsedistinguishes between streaming vs JSON responses:- Streaming: incrementally decode SSE chunks, concatenate
response.output_text.delta, and capture completed tool calls. - JSON: fold
outputmessage items into text blocks, gather tool-call items, and preserveusage/response.idfor stateful follow-ups.
- Streaming: incrementally decode SSE chunks, concatenate
- Both return a
UnifiedResponsecontainingcontent,toolCalls, token usage, and optionalresponseId.
-
Unified Response → Anthropic AssistantMessage
Back inqueryOpenAI, the unified response is wrapped in Anthropic’s schema:contentbecomes Ink-ready blocks, tool calls becometool_useentries, and usage numbers flow intoAssistantMessage.message.usage. Consumers (UI, TaskTool, etc.) continue to see only Anthropic-style messages.
Legacy Fallbacks
- If
USE_NEW_ADAPTERS === 'false'or noModelProfileis available, the system bypasses adapters entirely and hitsgetCompletionWithProfile/getGPT5CompletionWithProfile. These paths still rely on helper utilities insrc/services/openai.ts. ResponsesAPIAdapteralso carries compatibility flags (e.g.,previousResponseId,parallel_tool_calls) so a single unified params structure works across official OpenAI and third-party providers.
When to Extend This Layer
- New OpenAI-style providers: add capability metadata and, if necessary, a specialized adapter that extends
ModelAPIAdapter. - Model-specific quirks: keep conversions inside the adapter so upstream Anthropic abstractions stay untouched.
- Stateful Responses: leverage the
responseIdsurfaced byUnifiedResponseto support follow-up calls that requireprevious_response_id.