openai-code
Version:
An unofficial proxy layer that lets you use Anthropic Claude Code with any OpenAI API backend.
162 lines (142 loc) • 4.91 kB
JavaScript
import { HttpsProxyAgent } from "https-proxy-agent";
import { getEnv } from "./env.mjs"
import { OpenAI } from "openai"
export function getOpenAICommonOptions() {
const options = {};
if (getEnv('PROXY_URL')) {
options.httpAgent = new HttpsProxyAgent(getEnv('PROXY_URL'));
}
return options;
}
export const getReasoningModel = () => getEnv('REASONING_MODEL') || "o3-mini"
const createOpenAiClient = () => {
const config = {
apiKey: getEnv('OPENAI_CODE_API_KEY'),
...getOpenAICommonOptions(),
}
const organization = getEnv('OPENAI_CODE_ORGANIZATION_ID')
const project = getEnv('OPENAI_CODE_PROJECT_ID')
const baseUrl = getEnv('OPENAI_CODE_BASE_URL')
if (baseUrl) {
config.baseURL = baseUrl
}
if (organization) {
config.organization = organization
}
if (project) {
config.project = project
}
return new OpenAI(config)
}
export async function sendOpenAIRequest(args) {
const openai = createOpenAiClient();
const openAIArgs = { ...args }
// remove temperature for models that don't support it
if (openAIArgs.model.startsWith('o')) {
if (openAIArgs.messages) {
openAIArgs.messages = openAIArgs.messages.map(message => {
if (message.role === "system") {
return { ...message, role: "developer" }
}
return message
})
}
}
if (!openAIArgs.tools) {
delete openAIArgs.tool_choice
}
// make the actual API call to OpenAI
return openai.chat.completions.create(openAIArgs)
}
export const answer = async (question, reasoningEffort = "low") => {
console.log(`Answering: ${question}`)
const completion = await sendOpenAIRequest({
model: getReasoningModel(),
reasoning_effort: reasoningEffort,
stream: false,
messages: [{ role: "developer", content: question }],
response_format: {
type: "json_object",
},
})
const answer = completion.choices[0].message.content
console.log(`Answer: ${answer}`)
try {
return JSON.parse(answer) || {}
} catch (error) {
return {}
}
}
export async function decide(question) {
console.log(`Deciding (boolean) on: ${question}`)
if (question.indexOf('"correct":') === -1) {
question += `\nMUST respond in parsable JSON. Answer boolean (correctness): { "correct": true }`
}
let decision = false
try {
const data = await answer(question, "low")
decision = data?.correct === true
} catch (error) {
decision = false
}
console.log(`Decision: ${decision ? 'Yes' : 'No'}`)
return decision
}
/** normalizes embeddings to unit vectors for cosine similarity via dot product */
export const normalizeEmbedding = (embedding) => {
const norm = Math.sqrt(embedding.reduce((sum, value) => sum + value * value, 0)) // calculate the L2 norm of the embedding
if (norm === 0) return embedding // prevent / 0
return embedding.map(value => value / norm) // divide each element by the norm to get a unit vector
}
export const createEmbedding = async (text) => {
const maxAttempts = 5;
let attempts = 0;
let content = text;
const safeLimit = 8191; // maximum tokens allowed
while (attempts < maxAttempts) {
try {
const openai = createOpenAiClient();
const response = await openai.embeddings.create({
model: getEnv('OPENAI_CODE_EMBEDDING_MODEL') || "text-embedding-3-small",
encoding_format: "float",
input: content,
});
const embedding = response.data[0].embedding;
return normalizeEmbedding(embedding);
} catch (error) {
// Check for errors indicating too many tokens in the context
if (
error.message.includes("maximum context length") ||
error.message.includes("requested")
) {
// Attempt to extract the requested token count from the error message
const match = error.message.match(/requested (\d+) tokens/);
const requestedTokens = match?.[1] ? Number.parseInt(match[1], 10) : null;
let newLength;
if (requestedTokens) {
// Compute a safe ratio using a safety margin (e.g., 0.95)
const ratio = safeLimit / requestedTokens;
const safeRatio = ratio * 0.95;
newLength = Math.floor(content.length * safeRatio);
} else {
// Fallback: reduce the content by a fixed 10%
newLength = Math.floor(content.length * 0.9);
}
// If the new length is zero or did not change, return null.
if (newLength === 0 || newLength === content.length) {
return null;
}
console.warn(
`Trimming content: reducing length from ${content.length} to ${newLength}.`
);
content = content.slice(0, newLength);
attempts++;
} else {
// For any non context-length error, return null immediately.
return null;
}
}
}
// If all attempts are exhausted, return null.
return null;
}