Merge pull request #124 from salmanrajz/fix/recursive-schema-normalization

fix: make normalizeSchemaForOpenAI recursive for nested objects
This commit is contained in:
Kevin Codex
2026-04-02 17:03:37 +08:00
committed by GitHub
2 changed files with 115 additions and 68 deletions

View File

@@ -1,50 +1,53 @@
import { c as _c } from "react-compiler-runtime"; import React from 'react'
import React from 'react'; import { Box, Link, Text } from '../ink.js'
import { Box, Link, Text } from '../ink.js'; import { Select } from './CustomSelect/index.js'
import { Select } from './CustomSelect/index.js'; import { Dialog } from './design-system/Dialog.js'
import { Dialog } from './design-system/Dialog.js'; import { getAPIProvider } from '../utils/model/providers.js'
type Props = { type Props = {
onDone: () => void; onDone: () => void
};
export function CostThresholdDialog(t0) {
const $ = _c(7);
const {
onDone
} = t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <Box flexDirection="column"><Text>Learn more about how to monitor your spending:</Text><Link url="https://code.claude.com/docs/en/costs" /></Box>;
$[0] = t1;
} else {
t1 = $[0];
} }
let t2;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) { function getProviderLabel(): string {
t2 = [{ const provider = getAPIProvider()
value: "ok", switch (provider) {
label: "Got it, thanks!" case 'firstParty':
}]; return 'Anthropic API'
$[1] = t2; case 'bedrock':
} else { return 'AWS Bedrock'
t2 = $[1]; case 'vertex':
return 'Google Vertex'
case 'foundry':
return 'Azure Foundry'
case 'openai':
return 'OpenAI-compatible API'
case 'gemini':
return 'Gemini API'
default:
return 'API'
} }
let t3;
if ($[2] !== onDone) {
t3 = <Select options={t2} onChange={onDone} />;
$[2] = onDone;
$[3] = t3;
} else {
t3 = $[3];
} }
let t4;
if ($[4] !== onDone || $[5] !== t3) { export function CostThresholdDialog({ onDone }: Props): React.ReactNode {
t4 = <Dialog title="You've spent $5 on the Anthropic API this session." onCancel={onDone}>{t1}{t3}</Dialog>; const providerLabel = getProviderLabel()
$[4] = onDone; return (
$[5] = t3; <Dialog
$[6] = t4; title={`You've spent $5 on the ${providerLabel} this session.`}
} else { onCancel={onDone}
t4 = $[6]; >
<Box flexDirection="column">
<Text>Learn more about how to monitor your spending:</Text>
<Link url="https://code.claude.com/docs/en/costs" />
</Box>
<Select
options={[
{
value: 'ok',
label: 'Got it, thanks!',
},
]}
onChange={onDone}
/>
</Dialog>
)
} }
return t4;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIkJveCIsIkxpbmsiLCJUZXh0IiwiU2VsZWN0IiwiRGlhbG9nIiwiUHJvcHMiLCJvbkRvbmUiLCJDb3N0VGhyZXNob2xkRGlhbG9nIiwidDAiLCIkIiwiX2MiLCJ0MSIsIlN5bWJvbCIsImZvciIsInQyIiwidmFsdWUiLCJsYWJlbCIsInQzIiwidDQiXSwic291cmNlcyI6WyJDb3N0VGhyZXNob2xkRGlhbG9nLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBCb3gsIExpbmssIFRleHQgfSBmcm9tICcuLi9pbmsuanMnXG5pbXBvcnQgeyBTZWxlY3QgfSBmcm9tICcuL0N1c3RvbVNlbGVjdC9pbmRleC5qcydcbmltcG9ydCB7IERpYWxvZyB9IGZyb20gJy4vZGVzaWduLXN5c3RlbS9EaWFsb2cuanMnXG5cbnR5cGUgUHJvcHMgPSB7XG4gIG9uRG9uZTogKCkgPT4gdm9pZFxufVxuXG5leHBvcnQgZnVuY3Rpb24gQ29zdFRocmVzaG9sZERpYWxvZyh7IG9uRG9uZSB9OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIHJldHVybiAoXG4gICAgPERpYWxvZ1xuICAgICAgdGl0bGU9XCJZb3UndmUgc3BlbnQgJDUgb24gdGhlIEFudGhyb3BpYyBBUEkgdGhpcyBzZXNzaW9uLlwiXG4gICAgICBvbkNhbmNlbD17b25Eb25lfVxuICAgID5cbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8VGV4dD5MZWFybiBtb3JlIGFib3V0IGhvdyB0byBtb25pdG9yIHlvdXIgc3BlbmRpbmc6PC9UZXh0PlxuICAgICAgICA8TGluayB1cmw9XCJodHRwczovL2NvZGUuY2xhdWRlLmNvbS9kb2NzL2VuL2Nvc3RzXCIgLz5cbiAgICAgIDwvQm94PlxuICAgICAgPFNlbGVjdFxuICAgICAgICBvcHRpb25zPXtbXG4gICAgICAgICAge1xuICAgICAgICAgICAgdmFsdWU6ICdvaycsXG4gICAgICAgICAgICBsYWJlbDogJ0dvdCBpdCwgdGhhbmtzIScsXG4gICAgICAgICAgfSxcbiAgICAgICAgXX1cbiAgICAgICAgb25DaGFuZ2U9e29uRG9uZX1cbiAgICAgIC8+XG4gICAgPC9EaWFsb2c+XG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU9BLEtBQUssTUFBTSxPQUFPO0FBQ3pCLFNBQVNDLEdBQUcsRUFBRUMsSUFBSSxFQUFFQyxJQUFJLFFBQVEsV0FBVztBQUMzQyxTQUFTQyxNQUFNLFFBQVEseUJBQXlCO0FBQ2hELFNBQVNDLE1BQU0sUUFBUSwyQkFBMkI7QUFFbEQsS0FBS0MsS0FBSyxHQUFHO0VBQ1hDLE1BQU0sRUFBRSxHQUFHLEdBQUcsSUFBSTtBQUNwQixDQUFDO0FBRUQsT0FBTyxTQUFBQyxvQkFBQUMsRUFBQTtFQUFBLE1BQUFDLENBQUEsR0FBQUMsRUFBQTtFQUE2QjtJQUFBSjtFQUFBLElBQUFFLEVBQWlCO0VBQUEsSUFBQUcsRUFBQTtFQUFBLElBQUFGLENBQUEsUUFBQUcsTUFBQSxDQUFBQyxHQUFBO0lBTS9DRixFQUFBLElBQUMsR0FBRyxDQUFlLGFBQVEsQ0FBUixRQUFRLENBQ3pCLENBQUMsSUFBSSxDQUFDLDhDQUE4QyxFQUFuRCxJQUFJLENBQ0wsQ0FBQyxJQUFJLENBQUssR0FBdUMsQ0FBdkMsdUNBQXVDLEdBQ25ELEVBSEMsR0FBRyxDQUdFO0lBQUFGLENBQUEsTUFBQUUsRUFBQTtFQUFBO0lBQUFBLEVBQUEsR0FBQUYsQ0FBQTtFQUFBO0VBQUEsSUFBQUssRUFBQTtFQUFBLElBQUFMLENBQUEsUUFBQUcsTUFBQSxDQUFBQyxHQUFBO0lBRUtDLEVBQUEsSUFDUDtNQUFBQyxLQUFBLEVBQ1MsSUFBSTtNQUFBQyxLQUFBLEVBQ0o7SUFDVCxDQUFDLENBQ0Y7SUFBQVAsQ0FBQSxNQUFBSyxFQUFBO0VBQUE7SUFBQUEsRUFBQSxHQUFBTCxDQUFBO0VBQUE7RUFBQSxJQUFBUSxFQUFBO0VBQUEsSUFBQVIsQ0FBQSxRQUFBSCxNQUFBO0lBTkhXLEVBQUEsSUFBQyxNQUFNLENBQ0ksT0FLUixDQUxRLENBQUFILEVBS1QsQ0FBQyxDQUNTUixRQUFNLENBQU5BLE9BQUssQ0FBQyxHQUNoQjtJQUFBRyxDQUFBLE1BQUFILE1BQUE7SUFBQUcsQ0FBQSxNQUFBUSxFQUFBO0VBQUE7SUFBQUEsRUFBQSxHQUFBUixDQUFBO0VBQUE7RUFBQSxJQUFBUyxFQUFBO0VBQUEsSUFBQVQsQ0FBQSxRQUFBSCxNQUFBLElBQUFHLENBQUEsUUFBQVEsRUFBQTtJQWhCSkMsRUFBQSxJQUFDLE1BQU0sQ0FDQyxLQUFvRCxDQUFwRCxvREFBb0QsQ0FDaERaLFFBQU0sQ0FBTkEsT0FBSyxDQUFDLENBRWhCLENBQUFLLEVBR0ssQ0FDTCxDQUFBTSxFQVFDLENBQ0gsRUFqQkMsTUFBTSxDQWlCRTtJQUFBUixDQUFBLE1BQUFILE1BQUE7SUFBQUcsQ0FBQSxNQUFBUSxFQUFBO0lBQUFSLENBQUEsTUFBQVMsRUFBQTtFQUFBO0lBQUFBLEVBQUEsR0FBQVQsQ0FBQTtFQUFBO0VBQUEsT0FqQlRTLEVBaUJTO0FBQUEiLCJpZ25vcmVMaXN0IjpbXX0=

View File

@@ -235,20 +235,60 @@ function normalizeSchemaForOpenAI(
schema: Record<string, unknown>, schema: Record<string, unknown>,
strict = true, strict = true,
): Record<string, unknown> { ): Record<string, unknown> {
if (schema.type !== 'object' || !schema.properties) return schema if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
const properties = schema.properties as Record<string, unknown> return (schema ?? {}) as Record<string, unknown>
const existingRequired = Array.isArray(schema.required) ? schema.required as string[] : []
// OpenAI strict mode requires every property to be listed in required[].
// Gemini rejects schemas where required[] contains keys absent from properties,
// so only promote keys that actually exist in properties.
if (strict) {
const allKeys = Object.keys(properties)
const required = Array.from(new Set([...existingRequired, ...allKeys]))
return { ...schema, required }
} }
const record = { ...schema }
if (record.type === 'object' && record.properties) {
const properties = record.properties as Record<string, Record<string, unknown>>
const existingRequired = Array.isArray(record.required) ? record.required as string[] : []
// Recurse into each property
const normalizedProps: Record<string, unknown> = {}
for (const [key, value] of Object.entries(properties)) {
normalizedProps[key] = normalizeSchemaForOpenAI(
value as Record<string, unknown>,
strict,
)
}
record.properties = normalizedProps
if (strict) {
// OpenAI strict mode requires every property to be listed in required[]
const allKeys = Object.keys(normalizedProps)
record.required = Array.from(new Set([...existingRequired, ...allKeys]))
// OpenAI strict mode requires additionalProperties: false on all object
// schemas — override unconditionally to ensure nested objects comply.
record.additionalProperties = false
} else {
// For Gemini: keep only existing required keys that are present in properties // For Gemini: keep only existing required keys that are present in properties
const required = existingRequired.filter(k => k in properties) record.required = existingRequired.filter(k => k in normalizedProps)
return { ...schema, required } }
}
// Recurse into array items
if ('items' in record) {
if (Array.isArray(record.items)) {
record.items = (record.items as unknown[]).map(
item => normalizeSchemaForOpenAI(item as Record<string, unknown>, strict),
)
} else {
record.items = normalizeSchemaForOpenAI(record.items as Record<string, unknown>, strict)
}
}
// Recurse into combinators
for (const key of ['anyOf', 'oneOf', 'allOf'] as const) {
if (key in record && Array.isArray(record[key])) {
record[key] = (record[key] as unknown[]).map(
item => normalizeSchemaForOpenAI(item as Record<string, unknown>, strict),
)
}
}
return record
} }
function convertTools( function convertTools(
@@ -374,6 +414,7 @@ async function* openaiStreamToAnthropic(
const decoder = new TextDecoder() const decoder = new TextDecoder()
let buffer = '' let buffer = ''
try {
while (true) { while (true) {
const { done, value } = await reader.read() const { done, value } = await reader.read()
if (done) break if (done) break
@@ -529,6 +570,9 @@ async function* openaiStreamToAnthropic(
} }
} }
} }
} finally {
reader.releaseLock()
}
yield { type: 'message_stop' } yield { type: 'message_stop' }
} }