""" Encode structured tool declaration to typescript style string. """ import dataclasses import json import logging from collections.abc import Sequence from typing import Any logger = logging.getLogger(__name__) _TS_INDENT = " " _TS_FIELD_DELIMITER = ",\n" class _SchemaRegistry: """Registry for schema definitions to handle $ref resolution""" def __init__(self): self.definitions = {} self.has_self_ref = False def register_definitions(self, defs: dict[str, Any]): """Register schema definitions from $defs section""" if not defs: return for def_name, def_schema in defs.items(): self.definitions[def_name] = def_schema def resolve_ref(self, ref: str) -> dict[str, Any]: """Resolve a reference to its schema definition""" if ref == "#": self.has_self_ref = True return {"$self_ref": True} elif ref.startswith("#/$defs/"): def_name = ref.split("/")[-1] if def_name not in self.definitions: raise ValueError(f"Reference not found: {ref}") return self.definitions[def_name] else: raise ValueError(f"Unsupported reference format: {ref}") def _format_description(description: str, indent: str = "") -> str: return "\n".join([ f"{indent}// {line}" if line else "" for line in description.split("\n") ]) class _BaseType: description: str constraints: dict[str, Any] def __init__( self, extra_props: dict[str, Any], *, allowed_constraint_keys: Sequence[str] = (), ): self.description = extra_props.get("description", "") self.constraints = { k: v for k, v in extra_props.items() if k in allowed_constraint_keys } def to_typescript_style(self, indent: str = "") -> str: raise NotImplementedError def format_docstring(self, indent: str) -> str: lines = [] if self.description: lines.append(_format_description(self.description, indent)) if self.constraints: constraints_str = ", ".join(f"{k}: {v}" for k, v in sorted( self.constraints.items(), key=lambda kv: kv[0])) lines.append(f"{indent}// {constraints_str}") return "".join(x + "\n" for x in lines) class _ParameterTypeScalar(_BaseType): type: str def __init__(self, type: str, extra_props: dict[str, Any] | None = None): self.type = type allowed_constraint_keys: list[str] = [] if self.type == "string": allowed_constraint_keys = ["maxLength", "minLength", "pattern"] elif self.type in ("number", "integer"): allowed_constraint_keys = ["maximum", "minimum"] super().__init__(extra_props or {}, allowed_constraint_keys=allowed_constraint_keys) def to_typescript_style(self, indent: str = "") -> str: # Map integer to number in TypeScript if self.type == "integer": return "number" return self.type class _ParameterTypeObject(_BaseType): properties: list["_Parameter"] additional_properties: Any | None = None def __init__(self, json_schema_object: dict[str, Any], registry: _SchemaRegistry | None = None): super().__init__(json_schema_object) self.properties = [] self.additional_properties = None if not json_schema_object: return if "$defs" in json_schema_object and registry: registry.register_definitions(json_schema_object["$defs"]) self.additional_properties = json_schema_object.get( "additionalProperties") if isinstance(self.additional_properties, dict): self.additional_properties = _parse_parameter_type( self.additional_properties, registry) if "properties" not in json_schema_object: return required_parameters = json_schema_object.get("required", []) optional_parameters = set( json_schema_object["properties"].keys()) - set(required_parameters) self.properties = [ _Parameter( name=name, type=_parse_parameter_type(prop, registry), optional=name in optional_parameters, default=prop.get("default") if isinstance(prop, dict) else None, ) for name, prop in json_schema_object["properties"].items() ] def to_typescript_style(self, indent: str = "") -> str: # sort by optional, make the required parameters first parameters = [p for p in self.properties if not p.optional] opt_params = [p for p in self.properties if p.optional] parameters = sorted(parameters, key=lambda p: p.name) parameters.extend(sorted(opt_params, key=lambda p: p.name)) param_strs = [] for p in parameters: one = p.to_typescript_style(indent=indent + _TS_INDENT) param_strs.append(one) if self.additional_properties is not None: ap_type_str = "any" if self.additional_properties is True: ap_type_str = "any" elif self.additional_properties is False: ap_type_str = "never" elif isinstance(self.additional_properties, _ParameterType): ap_type_str = self.additional_properties.to_typescript_style( indent=indent + _TS_INDENT) else: raise ValueError( f"Unknown additionalProperties: {self.additional_properties}" ) param_strs.append( f"{indent + _TS_INDENT}[k: string]: {ap_type_str}") if not param_strs: return "{}" params_str = _TS_FIELD_DELIMITER.join(param_strs) if params_str: # add new line before and after params_str = f"\n{params_str}\n" # always wrap with object return f"{{{params_str}{indent}}}" class _ParameterTypeArray(_BaseType): item: "_ParameterType" def __init__(self, json_schema_object: dict[str, Any], registry: _SchemaRegistry | None = None): super().__init__(json_schema_object, allowed_constraint_keys=("minItems", "maxItems")) if json_schema_object.get("items"): self.item = _parse_parameter_type(json_schema_object["items"], registry) else: self.item = _ParameterTypeScalar(type="any") def to_typescript_style(self, indent: str = "") -> str: item_docstring = self.item.format_docstring(indent + _TS_INDENT) if item_docstring: return ("Array<\n" + item_docstring + indent + _TS_INDENT + self.item.to_typescript_style(indent=indent + _TS_INDENT) + "\n" + indent + ">") else: return f"Array<{self.item.to_typescript_style(indent=indent)}>" class _ParameterTypeEnum(_BaseType): # support scalar types only enum: list[str | int | float | bool | None] def __init__(self, json_schema_object: dict[str, Any]): super().__init__(json_schema_object) self.enum = json_schema_object["enum"] # Validate enum values against declared type if present if "type" in json_schema_object: typ = json_schema_object["type"] if isinstance(typ, list): if len(typ) == 1: typ = typ[0] elif len(typ) == 2: if "null" not in typ: raise ValueError(f"Enum type {typ} is not supported") else: typ = typ[0] if typ[0] != "null" else typ[1] else: raise ValueError(f"Enum type {typ} is not supported") for val in self.enum: if val is None: continue if typ == "string" and not isinstance(val, str): raise ValueError(f"Enum value {val} is not a string") elif typ == "number" and not isinstance(val, (int, float)): raise ValueError(f"Enum value {val} is not a number") elif typ == "integer" and not isinstance(val, int): raise ValueError(f"Enum value {val} is not an integer") elif typ == "boolean" and not isinstance(val, bool): raise ValueError(f"Enum value {val} is not a boolean") def to_typescript_style(self, indent: str = "") -> str: return " | ".join( [f'"{e}"' if isinstance(e, str) else str(e) for e in self.enum]) class _ParameterTypeAnyOf(_BaseType): types: list["_ParameterType"] def __init__( self, json_schema_object: dict[str, Any], registry: _SchemaRegistry | None = None, ): super().__init__(json_schema_object) self.types = [ _parse_parameter_type(t, registry) for t in json_schema_object["anyOf"] ] def to_typescript_style(self, indent: str = "") -> str: return " | ".join( [t.to_typescript_style(indent=indent) for t in self.types]) class _ParameterTypeUnion(_BaseType): types: list[str] def __init__(self, json_schema_object: dict[str, Any]): super().__init__(json_schema_object) mapping = { "string": "string", "number": "number", "integer": "number", "boolean": "boolean", "null": "null", "object": "{}", "array": "Array", } self.types = [mapping[t] for t in json_schema_object["type"]] def to_typescript_style(self, indent: str = "") -> str: return " | ".join(self.types) class _ParameterTypeRef(_BaseType): ref_name: str is_self_ref: bool = False def __init__(self, json_schema_object: dict[str, Any], registry: _SchemaRegistry): super().__init__(json_schema_object) ref = json_schema_object["$ref"] resolved_schema = registry.resolve_ref(ref) if resolved_schema.get("$self_ref", False): self.ref_name = "parameters" self.is_self_ref = True else: self.ref_name = ref.split("/")[-1] def to_typescript_style(self, indent: str = "") -> str: return self.ref_name _ParameterType = (_ParameterTypeScalar | _ParameterTypeObject | _ParameterTypeArray | _ParameterTypeEnum | _ParameterTypeAnyOf | _ParameterTypeUnion | _ParameterTypeRef) @dataclasses.dataclass class _Parameter: """ A parameter in a function, or a field in a object. It consists of the type as well as the name. """ type: _ParameterType name: str = "_" optional: bool = True default: Any | None = None @classmethod def parse_extended(cls, attributes: dict[str, Any]) -> "_Parameter": if not attributes: raise ValueError("attributes is empty") return cls( name=attributes.get("name", "_"), type=_parse_parameter_type(attributes), optional=attributes.get("optional", False), default=attributes.get("default"), ) def to_typescript_style(self, indent: str = "") -> str: comments = self.type.format_docstring(indent) if self.default is not None: default_repr = (json.dumps(self.default, ensure_ascii=False) if not isinstance(self.default, (int, float, bool)) else repr(self.default)) comments += f"{indent}// Default: {default_repr}\n" return ( comments + f"{indent}{self.name}{'?' if self.optional else ''}: {self.type.to_typescript_style(indent=indent)}" ) def _parse_parameter_type( json_schema_object: dict[str, Any] | bool, registry: _SchemaRegistry | None = None) -> _ParameterType: if isinstance(json_schema_object, bool): if json_schema_object: return _ParameterTypeScalar(type="any") else: logger.warning( f"Warning: Boolean value {json_schema_object} is not supported, use null instead." ) return _ParameterTypeScalar(type="null") if "$ref" in json_schema_object and registry: return _ParameterTypeRef(json_schema_object, registry) if "anyOf" in json_schema_object: return _ParameterTypeAnyOf(json_schema_object, registry) elif "enum" in json_schema_object: return _ParameterTypeEnum(json_schema_object) elif "type" in json_schema_object: typ = json_schema_object["type"] if isinstance(typ, list): return _ParameterTypeUnion(json_schema_object) elif typ == "object": return _ParameterTypeObject(json_schema_object, registry) elif typ == "array": return _ParameterTypeArray(json_schema_object, registry) else: return _ParameterTypeScalar(typ, json_schema_object) elif json_schema_object == {}: return _ParameterTypeScalar(type="any") else: raise ValueError(f"Invalid JSON Schema object: {json_schema_object}") def _openai_function_to_typescript_style(function: dict[str, Any], ) -> str: """Convert OpenAI function definition (dict) to TypeScript style string.""" registry = _SchemaRegistry() parameters = function.get("parameters") or {} parsed = _ParameterTypeObject(parameters, registry) interfaces = [] root_interface_name = None if registry.has_self_ref: root_interface_name = "parameters" params_str = _TS_FIELD_DELIMITER.join([ p.to_typescript_style(indent=_TS_INDENT) for p in parsed.properties ]) params_str = f"\n{params_str}\n" if params_str else "" interface_def = f"interface {root_interface_name} {{{params_str}}}" interfaces.append(interface_def) definitions_copy = dict(registry.definitions) for def_name, def_schema in definitions_copy.items(): obj_type = _parse_parameter_type(def_schema, registry) params_str = obj_type.to_typescript_style() description_part = "" if obj_description := def_schema.get("description", ""): description_part = _format_description(obj_description) + "\n" interface_def = f"{description_part}interface {def_name} {params_str}" interfaces.append(interface_def) interface_str = "\n".join(interfaces) function_name = function.get("name", "function") if root_interface_name: type_def = f"type {function_name} = (_: {root_interface_name}) => any;" else: params_str = parsed.to_typescript_style() type_def = f"type {function_name} = (_: {params_str}) => any;" description = function.get("description") return "\n".join( filter( bool, [ interface_str, ((description and _format_description(description)) or ""), type_def, ], )) def encode_tools_to_typescript_style(tools: list[dict[str, Any]], ) -> str: """ Convert tools (list of dict) to TypeScript style string. Supports OpenAI format: {"type": "function", "function": {...}} Args: tools: List of tool definitions in dict format Returns: TypeScript style string representation of the tools """ if not tools: return "" functions = [] for tool in tools: tool_type = tool.get("type") if tool_type == "function": func_def = tool.get("function", {}) if func_def: functions.append( _openai_function_to_typescript_style(func_def)) else: # Skip unsupported tool types (like "_plugin") continue if not functions: return "" functions_str = "\n".join(functions) result = "# Tools\n\n" if functions_str: result += "## functions\nnamespace functions {\n" result += functions_str + "\n" result += "}\n" return result