import sys
import os
from pathlib import Path
from typing import Dict, Any, Set
SCRIPT_DIR = Path(__file__).parent
PROJECT_ROOT = SCRIPT_DIR.parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from utils import load_spec, save_spec
from simplified_schemas import SIMPLIFIED_SCHEMAS
def find_string_schemas(spec: Dict[str, Any]) -> Set[str]:
schemas = spec.get('components', {}).get('schemas', {})
string_schemas = set()
for name, schema in schemas.items():
if isinstance(schema, dict) and schema.get('type') == 'string':
if 'enum' not in schema:
string_schemas.add(name)
return string_schemas
def inline_string_refs_in_untagged_unions(spec: Dict[str, Any], string_schemas: Set[str]) -> int:
schemas = spec.get('components', {}).get('schemas', {})
changes = 0
def rewrite_branches(branches: list):
nonlocal changes
for i, branch in enumerate(branches):
if not isinstance(branch, dict):
continue
ref = branch.get('$ref')
if ref and ref.startswith('#/components/schemas/'):
name = ref.split('/')[-1]
if name in string_schemas:
replacement = {'type': 'string'}
if 'description' in branch:
replacement['description'] = branch['description']
if 'title' in branch:
replacement['title'] = branch['title']
branches[i] = replacement
changes += 1
for schema in schemas.values():
if not isinstance(schema, dict):
continue
for key in ('anyOf', 'oneOf', 'allOf'):
if key in schema and isinstance(schema[key], list):
rewrite_branches(schema[key])
return changes
def find_model_fields_in_allof(spec: Dict[str, Any]) -> Set[str]:
schemas = spec.get('components', {}).get('schemas', {})
model_schemas = set()
for schema_name, schema in schemas.items():
if 'allOf' in schema:
for ref_item in schema.get('allOf', []):
if '$ref' in ref_item:
ref_path = ref_item['$ref'].split('/')[-1]
ref_schema = schemas.get(ref_path, {})
if 'properties' in ref_schema and 'model' in ref_schema.get('properties', {}):
model_schemas.add(schema_name)
print(f"Found {schema_name} inherits model from {ref_path}")
return model_schemas
def flatten_model_fields(spec: Dict[str, Any]) -> Dict[str, Any]:
schemas = spec.get('components', {}).get('schemas', {})
changes = []
for schema_name in SIMPLIFIED_SCHEMAS:
if schema_name in schemas:
old_schema = schemas[schema_name]
schemas[schema_name] = {
'type': 'string',
'description': old_schema.get('description', f'Model identifier as string')
}
changes.append(f"Replaced {schema_name} with simple string type")
for schema_name, schema in schemas.items():
if 'allOf' in schema:
has_model = False
for ref_item in schema.get('allOf', []):
if '$ref' in ref_item:
ref_name = ref_item['$ref'].split('/')[-1]
ref_schema = schemas.get(ref_name, {})
if 'properties' in ref_schema and 'model' in ref_schema.get('properties', {}):
has_model = True
break
if has_model:
for item in schema['allOf']:
if 'type' in item and item['type'] == 'object':
if 'properties' not in item:
item['properties'] = {}
item['properties']['model'] = {
'type': 'string',
'description': 'ID of the model to use'
}
changes.append(f"Injected model field into {schema_name}")
break
for schema_name, schema in schemas.items():
if 'properties' in schema:
props = schema['properties']
if 'model' in props:
model_prop = props['model']
if '$ref' in model_prop:
ref_name = model_prop['$ref'].split('/')[-1]
if ref_name in SIMPLIFIED_SCHEMAS:
props['model'] = {
'type': 'string',
'description': model_prop.get('description', 'ID of the model to use')
}
changes.append(f"Converted {schema_name}.model from $ref to string")
elif 'allOf' in model_prop or 'oneOf' in model_prop or 'anyOf' in model_prop:
props['model'] = {
'type': 'string',
'description': model_prop.get('description', 'ID of the model to use')
}
changes.append(f"Simplified {schema_name}.model from union to string")
for schema_name, schema in schemas.items():
if 'properties' in schema:
props = schema['properties']
if 'voice' in props:
voice_prop = props['voice']
if '$ref' in voice_prop:
ref_name = voice_prop['$ref'].split('/')[-1]
if ref_name == 'VoiceIdsShared':
props['voice'] = {
'type': 'string',
'description': voice_prop.get('description', 'The voice to use for audio generation')
}
changes.append(f"Converted {schema_name}.voice from $ref to string")
elif 'allOf' in voice_prop or 'oneOf' in voice_prop or 'anyOf' in voice_prop:
props['voice'] = {
'type': 'string',
'description': voice_prop.get('description', 'The voice to use for audio generation')
}
changes.append(f"Simplified {schema_name}.voice from union to string")
if 'ModelResponseProperties' in schemas:
model_props = schemas['ModelResponseProperties']
if 'properties' in model_props and 'model' in model_props['properties']:
model_props['properties']['model'] = {
'type': 'string',
'description': 'ID of the model to use'
}
changes.append("Fixed ModelResponseProperties.model to be string")
return changes
def main():
input_path = sys.argv[1] if len(sys.argv) > 1 else 'stainless.yaml'
output_path = sys.argv[2] if len(sys.argv) > 2 else 'target/specs/l1_model_fixed.yaml'
print(f"Loading spec from {input_path}")
spec = load_spec(input_path)
print("\nFinding model fields in allOf inheritance...")
model_schemas = find_model_fields_in_allof(spec)
print(f"Found {len(model_schemas)} schemas with inherited model fields")
print("\nFlattening model field definitions...")
changes = flatten_model_fields(spec)
print("\nInlining string-only schemas in untagged unions...")
string_schemas = find_string_schemas(spec)
inlined = inline_string_refs_in_untagged_unions(spec, string_schemas)
if inlined:
changes.append(f"Inlined {inlined} string schema references inside unions")
print("\nChanges made:")
for change in changes:
print(f" - {change}")
print(f"\nSaving fixed spec to {output_path}")
save_spec(spec, output_path)
simplified_schemas_list = list(SIMPLIFIED_SCHEMAS)
tracking_path = PROJECT_ROOT / 'target' / 'reports' / 'simplified_schemas.txt'
tracking_path.parent.mkdir(parents=True, exist_ok=True)
with open(tracking_path, 'w') as f:
for schema in sorted(simplified_schemas_list):
f.write(f"{schema}\n")
print(f"Tracking file saved to {tracking_path}")
report_path = PROJECT_ROOT / 'target' / 'reports' / 'l1_model_fields_fix_report.md'
with open(report_path, 'w') as f:
f.write("# Model Fields Fix Report\n\n")
f.write(f"- Input: {os.path.relpath(input_path)}\n")
f.write(f"- Output: {os.path.relpath(output_path)}\n")
f.write(f"- Total changes: {len(changes)}\n\n")
f.write("## Changes Applied\n\n")
for change in changes:
f.write(f"- {change}\n")
print(f"Report saved to {report_path}")
print(f"\nTotal changes: {len(changes)}")
if __name__ == "__main__":
main()