Repository navigation
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
Problem Summary:
OpenCode cannot successfully use Gemini models through certain OpenAI-compatible proxy APIs (e.g., api.v3.cm/v1/) when Function Calling is enabled. The API returns validation errors due to JSON Schema fields that are not supported by the proxy.
Error Messages:
Invalid JSON payload received. Unknown name "ref" at 'tools[0].function declarations[0].***.***.properties[0].***.***.properties[2].***.items': Cannot find field.
Invalid JSON payload received. Unknown name "propertyNames" at 'tools[0].function declarations[28].***.properties[0].value.any_of[1]': Cannot find field.
Root Cause:
OpenCode generates complete JSON Schema for Function Calling (using Vercel AI SDK), including advanced validation fields such as:
$ref/refpropertyNamesadditionalPropertiesallOf,anyOf,oneOf,notpatternProperties
However, many OpenAI-compatible proxy APIs do not support these fields and reject requests containing them, returning "Unknown name" errors.
Suggested Enhancement:
Option 1 (Preferred): Add JSON Schema Compatibility Mode
- Add a provider-level configuration option (e.g.,
compatibilityMode: "strict"orsimplifiedSchema: true) that instructs OpenCode to generate minimal JSON Schema compatible with basic OpenAI API spec - When enabled, exclude advanced validation fields and only include:
type,properties,items,required,description,enum,format
Option 2: Allow Custom Schema Sanitization Hook
- Expose a webhook or script hook that allows users to preprocess request bodies before sending to the API
- This enables users to implement their own schema cleaning logic without modifying OpenCode's core
Option 3: Document Workaround with Local Proxy
- Include official documentation for setting up a local proxy server that sanitizes JSON Schema before forwarding to incompatible APIs
- Provide example proxy server code and configuration templates
Additional Context:
- OpenCode is a compiled binary, so users cannot modify source code to work around this issue
- This specifically affects Gemini models and other models that heavily use Function Calling
- The workaround currently requires running a custom Node.js proxy server between OpenCode and the API (see attached solution guide)
Impact:
- Users cannot use Gemini models with Function Calling through incompatible proxy APIs
- Forces users to either switch providers or maintain custom proxy infrastructure
- Limits flexibility in API provider selection
Use Case:
Users want to use OpenCode with proxy APIs that have incomplete OpenAI compatibility, specifically for Gemini models that require Function Calling capabilities.
Proposed Workaround Solution
Since OpenCode is a compiled binary, I've implemented a local proxy server workaround that successfully resolves this issue. This solution filters incompatible JSON Schema fields before forwarding requests to the API.
Architecture
┌─────────────┐
│ OpenCode │
│ (Binary) │
└──────┬──────┘
│
│ 1. Send request (with full JSON Schema)
│
▼
┌─────────────────┐
│ Local Proxy │
│ (Node.js) │
└──────┬──────────┘
│
│ 2. Clean incompatible fields
│
▼
┌─────────────────┐
│ Proxy API │
│ (api.v3.cm) │
└─────────────────┘
Proxy Server Implementation
File: proxy-server.js
#!/usr/bin/env node
/**
* OpenCode Compatibility Proxy Server
* Filters incompatible JSON Schema fields for certain proxy APIs
*/
const http = require('http');
const https = require('https');
const url = require('url');
const zlib = require('zlib');
const TARGET_API = 'https://api.v3.cm/v1/';
const API_KEY = 'your_api_key_here';
const PORT = 8080;
/**
* Recursively clean JSON Schema, removing incompatible fields
*/
function cleanSchema(obj) {
if (!obj || typeof obj !== 'object') {
return obj;
}
// Remove incompatible fields
const incompatibleFields = ['$ref', 'ref', 'propertyNames', 'patternProperties', 'additionalProperties', 'allOf', 'anyOf', 'oneOf', 'not'];
if (Array.isArray(obj)) {
return obj.map(item => cleanSchema(item));
}
const cleaned = {};
for (const key in obj) {
if (incompatibleFields.includes(key)) {
console.log(`[Filter] Removing incompatible field: ${key}`);
continue;
}
// Special handling for properties
if (key === 'properties' && typeof obj[key] === 'object') {
cleaned[key] = {};
for (const prop in obj[key]) {
cleaned[key][prop] = cleanSchema(obj[key][prop]);
}
continue;
}
// Special handling for items
if (key === 'items') {
cleaned[key] = cleanSchema(obj[key]);
continue;
}
// Recursively process nested objects
cleaned[key] = cleanSchema(obj[key]);
}
return cleaned;
}
/**
* Clean function declarations in tools array
*/
function cleanTools(body) {
if (!body.tools) {
return body;
}
console.log('[Clean] Found tools field, starting cleanup...');
const cleanedTools = body.tools.map(tool => {
if (tool.function && tool.function.parameters) {
console.log(`[Clean] Processing function: ${tool.function.name}`);
return {
...tool,
function: {
...tool.function,
parameters: cleanSchema(tool.function.parameters)
}
};
}
return tool;
});
return {
...body,
tools: cleanedTools
};
}
/**
* Handle requests
*/
function handleRequest(req, res) {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
try {
const parsedBody = JSON.parse(body);
console.log(`[Request] Received ${req.method} ${req.url}`);
// Clean incompatible fields
const cleanedBody = cleanTools(parsedBody);
// Forward to target API
const targetUrl = url.parse(TARGET_API + req.url.slice(1));
const options = {
hostname: targetUrl.hostname,
port: targetUrl.port || 443,
path: targetUrl.path,
method: req.method,
rejectUnauthorized: false,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`,
'Accept-Encoding': 'identity',
'User-Agent': 'OpenCode-Proxy/1.0'
}
};
const proxyReq = https.request(options, (proxyRes) => {
const chunks = [];
proxyRes.on('data', chunk => {
chunks.push(chunk);
});
proxyRes.on('end', () => {
console.log(`[Response] Status: ${proxyRes.statusCode}`);
const buffer = Buffer.concat(chunks);
let responseBody = buffer.toString('utf-8');
// Handle compressed responses
const encoding = proxyRes.headers['content-encoding'];
if (encoding === 'gzip' || encoding === 'deflate') {
try {
if (encoding === 'gzip') {
responseBody = zlib.gunzipSync(buffer).toString('utf-8');
} else if (encoding === 'deflate') {
responseBody = zlib.inflateSync(buffer).toString('utf-8');
}
} catch (decompressError) {
responseBody = buffer.toString('utf-8');
}
}
const responseHeaders = { ...proxyRes.headers };
delete responseHeaders['content-encoding'];
delete responseHeaders['content-length'];
delete responseHeaders['transfer-encoding'];
res.writeHead(proxyRes.statusCode, responseHeaders);
res.end(responseBody);
});
});
proxyReq.on('error', (error) => {
console.error('[Error] Proxy request failed:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Proxy request failed', details: error.message }));
});
proxyReq.write(JSON.stringify(cleanedBody));
proxyReq.end();
} catch (error) {
console.error('[Error] Failed to parse request:', error);
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON', details: error.message }));
}
});
}
// Start server
const server = http.createServer(handleRequest);
server.listen(PORT, '127.0.0.1', () => {
console.log(`[Startup] Proxy server running on http://127.0.0.1:${PORT}`);
console.log(`[Config] Target API: ${TARGET_API}`);
});OpenCode Configuration
File: opencode.json
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"proxy": {
"npm": "@ai-sdk/openai-compatible",
"name": "proxy",
"options": {
"baseURL": "http://127.0.0.1:8080/",
"apiKey": "local-proxy"
},
"models": {
"gemini-3-pro-preview": {
"name": "Gemini-3-Pro-Preview",
"attachment": true,
"limit": { "context": 1048576, "output": 65535 },
"modalities": { "input": ["text"], "output": ["text"] }
}
}
}
}
}Usage
-
Start the proxy server:
node proxy-server.js
-
Start OpenCode:
opencode
-
Verify functionality:
- The proxy will log filtered fields
- Function Calling should work correctly with Gemini models
Key Features
- ✅ Recursive Schema Cleaning - Deep traversal of JSON Schema
- ✅ Compression Handling - Automatic detection and decompression
- ✅ SSL Certificate Bypass - Works with self-signed certificates
- ✅ Detailed Logging - Easy debugging and troubleshooting
- ✅ Transparent Proxy - Works seamlessly with OpenCode
Limitations
- ❌ Requires maintaining a separate Node.js process
- ❌ Adds slight latency due to proxy layer
- ❌ Not suitable for production environments without additional security measures
Note: This workaround has been tested and verified to work with api.v3.cm/v1/ and Gemini models with Function Calling enabled.
Activity
github-actions commentedon Jan 31, 2026
This issue might be a duplicate of existing issues. Please check:
Feel free to ignore if your specific use case with OpenAI-compatible proxies addresses a different aspect of the problem.
github-actions commentedon Apr 2, 2026
To stay organized issues are automatically closed after 90 days of no activity. If the issue is still relevant please open a new one.