Conversation
- Add 17 new integration tests: CORS edge cases (disallowed origins, no-origin header, OPTIONS for disallowed origin), auth (401/pass-through), and error handling (400/502/404) for /v1/chat/completions Closes #14, closes #16 - Add ESLint with flat config, npm run lint script, and Lint job in CI Closes #15 - Improve README with quickstart section, npm install instructions, and corrected package name; add type column to env vars table Closes #17
- Implement streaming for POST /v1/chat/completions (issue #11): subscribe to opencode event stream, pipe message.part.updated deltas as SSE chat.completion.chunk events, finish on session.idle - Implement streaming for POST /v1/responses (issue #11): emit response.created / output_text.delta / response.completed events - Fix provider-agnostic system prompt hint (issue #12): remove 'OpenAI-compatible' wording so non-OpenAI models are not confused - Add TextEncoder and ReadableStream to ESLint globals - Add streaming integration tests (happy path, unknown model, session.error)
- Extract createSseQueue() helper, eliminating duplicated SSE queue pattern in /v1/chat/completions and /v1/responses streaming branches (closes #34) - Add tests for GET /v1/models happy path, empty providers, and error path (closes #33) - Add tests for POST /v1/responses: happy path, validation, streaming, session.error (closes #32) - Fix package.json description to be provider-agnostic (closes #35) - Add engines field declaring bun >=1.0.0 requirement (closes #35) - Line coverage: 55% -> 89%, function coverage: 83% -> 94%
- POST /v1/messages — Anthropic Messages API with streaming (SSE) - POST /v1beta/models/:model:generateContent — Gemini non-streaming - POST /v1beta/models/:model:streamGenerateContent — Gemini NDJSON streaming - New helpers: normalizeAnthropicMessages, normalizeGeminiContents, extractGeminiSystemInstruction, mapFinishReasonToAnthropic/Gemini - 35 new tests (77 -> 112 total, all passing) - Update README to document all supported API formats Closes #38, #39
- Lead with value proposition, ASCII diagram, and feature table - Quickstart reduced to 4 steps; works in under 60 seconds - SDK examples for OpenAI, Anthropic, Gemini (JS+Python), LangChain - UI integration guides: Open WebUI, Chatbox, Continue, Zed - Reference section kept concise; full prose docs moved inline - package.json: sharper description, 20 keywords covering all search terms (openai-compatible, anthropic, gemini, ollama, langchain, open-webui, llm-proxy, ai-gateway, local-llm, github-copilot, model-router, …)
…ic origin is configured
The Anthropic Messages API accepts the top-level `system` field as either a string OR an array of content blocks (per https://docs.anthropic.com/en/api/messages). The /v1/messages handler at index.js:1044-1047 only checks `typeof body.system === "string"` and silently drops the array form. Clients that follow the spec see their system prompt ignored by the proxy. Add and export a `normalizeAnthropicSystem` helper that accepts either form: for the array form, concatenates `type: "text"` content blocks (skipping falsy entries, non-text types, and non-string texts); returns null when no usable text is present so the call site can skip adding an empty system message. Use it at the call site in place of the inline string check. Adds 3 regression tests in index.test.js covering: - array-form system reaches buildSystemPrompt (discriminating) - multi-block text arrays are concatenated - helper edge cases (null/undefined, empty strings, non-text blocks, non-string/non-array inputs) Closes #46
…sponses API spec (#49) The /v1/responses streaming handler violates the OpenAI Responses API SSE lifecycle spec in two ways: 1. response.content_part.done is never emitted. Per the spec (https://platform.openai.com/docs/api-reference/responses-streaming), the event sequence for a text content part should be: content_part.added -> output_text.delta* -> output_text.done -> content_part.done -> output_item.done 2. response.output_text.done is emitted with text: "" instead of the accumulated output text. The spec requires the final content. Accumulate delta tokens in a local variable at the streaming call site, emit the missing response.content_part.done event with the accumulated text in part.text, and populate output_text.done.text with the same accumulated content. Gate the new content_part.done event on at least one delta having been received, keeping the content-part added/done lifecycle symmetric. Adds one regression test in index.test.js that asserts: - output_text.done.text equals the accumulated deltas - content_part.done event is present with part.text populated - correct ordering (output_text.done < content_part.done < output_item.done) Closes #48
* feat: add tool/function calling support (closes #50) Adds OpenAI-style function tools, Anthropic tools, and Gemini function declarations across all four API surfaces (/v1/chat/completions, /v1/responses, /v1/messages, /v1beta/models/:model:generateContent), both streaming and non-streaming. OpenCode's own agent loop always executes tools itself, server-side, and has no concept of a 'client-executed' tool call to hand off to a caller. To bridge that gap: - When a request includes tools, the proxy dynamically registers a small local MCP server (mcp-tool-bridge.js) whose tool list is exactly the caller's declared tool schemas, reused from a small fixed-size pool of slot names (OpenCode's server API has no endpoint to deregister an MCP server once added). - Only those tools are enabled for that one prompt call via the existing tools enable/disable map; every built-in OpenCode tool stays disabled, same as before. - As soon as the model proposes calling one of the bridge tools, the full call (name + arguments) is already present on OpenCode's event stream (ToolStatePending includes the parsed input even before execution starts) - the proxy captures it and immediately aborts the session before the bridge's no-op tools/call handler would ever be consulted, then translates the call into the caller's expected tool_calls / tool_use / functionCall shape instead of a text answer. Also extends the OpenAI/Anthropic/Gemini/Responses message normalizers to render prior tool_calls/tool_use/functionCall and their results/tool_result/functionResponse as descriptive text when replaying conversation history, so multi-turn tool use works end-to-end even though sessions are stateless per-request. - index.js: parseOpenAITools/parseAnthropicTools/parseGeminiTools + applyOpenAIToolChoice/applyAnthropicToolChoice/applyGeminiToolChoice, sanitizeToolName, tool bridge pool + registerToolBridge, unified runAgentTurn (event-driven turn execution shared by executePrompt and executePromptStreaming when tools are present), tool-call branches in all four response builders and SSE emitters. - mcp-tool-bridge.js: minimal MCP stdio JSON-RPC server exposing caller-supplied tool schemas; tools/call is a harmless no-op since the proxy aborts the session before it would ever be consulted. - index.test.js: unit tests for the new parse/tool_choice helpers and history round-tripping, plus end-to-end tool-calling tests for all four API formats (stream + non-stream). - README.md: documents the new tools/tool_choice/toolConfig request fields, how the bridge mechanism works, its current limitations, and the new OPENCODE_LLM_PROXY_TOOL_BRIDGE_POOL_SIZE env var. Testing: - npm test — 138 passed (116 existing + 22 new) - npm run lint — clean * docs: feature tool calling prominently in README, expand discoverability keywords - Move the Tool calling section up (right after Configuration) and add a runnable curl request/response example, instead of burying it near the bottom after How it works. - Add a Contents section now that the README has grown to 10+ sections. - Call out tool calling in the top-level tagline, architecture diagram, supported-formats table, and Why section (coding agents are now a first-class use case, not just chat clients). - Note in Install that copying just index.js doesn't get you tool calling (needs mcp-tool-bridge.js alongside it) - use the npm plugin instead. - package.json: mention tool/function calling in the description and add tool-calling/function-calling/tools/mcp/model-context-protocol/ coding-agent/ai-agent/agentic keywords for npm search discoverability. --------- Co-authored-by: Framewrk CI <ci@framewrklabs.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Promotes
devtomain. Includes:systemfield as content-block array (fix: accept Anthropic system field as content-block array #47, closes Anthropic /v1/messages silently drops system field when passed as content-block array #46)response.content_part.doneand populateoutput_text.done.textper the OpenAI Responses API SSE spec (fix: emit content_part.done and populate output_text.done.text per Responses API spec #49, closes /v1/responses streaming omits response.content_part.done and sends empty output_text.done.text #48)Testing
npm test— all tests passnpm run lint— clean