[WRONG BRANCH] feat: add GitHub Copilot App support via OpenAI-compatible chat completions - #278
Conversation
Expose POST /v1/chat/completions so GitHub Copilot App and other OpenAI-compatible clients can use OpenCodex as a Model provider via GET /v1/models + chat completions, reusing the existing Responses path.
|
This pull request currently targets Its title has been prefixed with @HaydernCenterpoint Please retarget this PR to the This pull request is being kept as a draft automatically. Once the target branch is corrected, it will be marked ready for review again. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: da10542afd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| logCtx.providerAdapter = route.provider.adapter; | ||
| logCtx.requestedModel = requestedModel; | ||
| logCtx.provider = route.providerName; | ||
| if (route.provider.adapter === "openai-responses") { |
There was a problem hiding this comment.
Preserve chat limits for API-key Responses routes
When a client targets an openai-responses provider with API-key auth (for example the built-in openai-apikey provider), this condition still runs and deletes max_output_tokens, temperature, top_p, stop, and user even though the comment only applies to the ChatGPT forward backend. As a result, a Chat Completions request with max_tokens or stop against openai-apikey/... is silently replayed without those controls, which can change behavior and cost. Gate this cleanup on the forward ChatGPT backend instead of every openai-responses adapter.
Useful? React with 👍 / 👎.
| let toolIndex = nextToolIndex > 0 ? nextToolIndex - 1 : 0; | ||
| const itemId = typeof data.item_id === "string" ? data.item_id : undefined; | ||
| if (itemId) { | ||
| // Some bridges put call_id on the item, not item_id; keep last index. | ||
| } |
There was a problem hiding this comment.
Map tool argument deltas by item id
When the Responses stream has more than one function call open, response.function_call_arguments.delta carries item_id identifying which call the bytes belong to, but this code ignores it and always emits the delta for the most recently added tool. If two tool calls are added before or interleaved while arguments stream, the Chat Completions client receives arguments on the wrong tool_calls[index], so subsequent tool execution uses corrupted inputs. Store the item.id→index mapping from response.output_item.added and use it here.
Useful? React with 👍 / 👎.
| const frame = chunkBase(id, model, created); | ||
| frame.choices = [{ index: 0, delta: { content: `\n\n[error] ${message}` }, finish_reason: "stop" }]; | ||
| emit(frame); | ||
| emit("[DONE]"); |
There was a problem hiding this comment.
Surface failed streams as errors
When an adapter or native relay emits a response.failed terminal event after an HTTP 200 SSE has started, this path turns the failure into ordinary assistant content and then sends finish_reason: "stop" plus [DONE]. Non-streaming Chat Completions requests folded through collectChatCompletion therefore return a 200 chat.completion containing [error] ..., and streaming clients also see a successful assistant turn instead of an error. Emit an OpenAI-style error frame/state here so upstream failures are not treated as model output.
Useful? React with 👍 / 👎.
| if (typeof raw.user === "string") body.user = raw.user; | ||
| if (typeof raw.parallel_tool_calls === "boolean") body.parallel_tool_calls = raw.parallel_tool_calls; | ||
| if (typeof raw.prompt_cache_key === "string") body.prompt_cache_key = raw.prompt_cache_key; | ||
| if (raw.metadata !== undefined) body.metadata = raw.metadata; |
There was a problem hiding this comment.
Strip metadata before ChatGPT forward replay
When a Chat Completions client includes metadata and routes to the default ChatGPT-backed openai provider, this copies the field into the internal Responses body; the ChatGPT forward backend uses a strict Responses allowlist and rejects metadata, so otherwise valid chats fail with an upstream 400. The native-route cleanup strips other unsupported forwarded fields, but metadata needs the same treatment for the forward provider while remaining available for API-key Responses routes.
Useful? React with 👍 / 👎.
Summary
POST /v1/chat/completionsso GitHub Copilot App can use OpenCodex as a custom Model provider.docs/github-copilot-app.md) and cover discovery/chat endpoint behavior with tests.This is a client integration for GitHub Copilot App (BYOK / Model providers). It is separate from the existing experimental upstream
github-copilotprovider.How to use
ocx start)http://127.0.0.1:10100/v1provider/model)Test plan
bun test tests/chat-completions-endpoint.test.tsbun test tests/server-auth.test.ts -t "root fallback"GET /v1/modelsreturns OpenAI list shapePOST /v1/chat/completionsstream + non-stream succeed for configured providers/v1and selecting an authenticated model