"""Generator: tools/schema/*.json → Python ToolSpecs + TypeScript types. Liest alle JSON-Dateien aus tools/schema/, erzeugt: 1. agent/browser_tool_defs.py — Liste von Bedrock-toolSpec-Dicts für den Broker 2. extension/src/shared/tool-schemas.generated.ts — TypeScript-Typen + Registry Aufruf: python tools/generate_tool_defs.py Im esbuild-Step der Extension als Pre-Build verdrahtet (package.json "prebuild"). """ import json import os from pathlib import Path SCHEMA_DIR = Path(__file__).parent / "schema" #: Wurzel dieses Repos (tools/ liegt direkt darunter). _ROOT = Path(__file__).parent.parent #: TypeScript-Ziel — immer vorhanden, dies ist das eigene Repo. TS_OUTPUT = _ROOT / "src" / "shared" / "tool-schemas.generated.ts" #: Python-Ziel im NACHBAR-Repo (agent/). Die Schemata beschreiben Browser-Tools #: und gehoeren damit zur Extension; der Python-Broker konsumiert nur das #: Erzeugnis, das dort eingecheckt ist. Liegen die Repos nicht nebeneinander, #: wird der Python-Teil uebersprungen statt abzubrechen — die Extension muss #: allein baubar bleiben. PY_OUTPUT = _ROOT.parent / "browser_tool_defs.py" def load_schemas() -> list[dict]: schemas = [] for f in sorted(SCHEMA_DIR.glob("*.json")): with open(f, encoding="utf-8") as fh: schemas.append(json.load(fh)) return schemas def generate_python(schemas: list[dict]) -> str: lines = [ '"""Auto-generated browser tool definitions for the Bedrock Converse API.', '', 'DO NOT EDIT — generated by tools/generate_tool_defs.py from tools/schema/*.json.', '"""', '', 'BROWSER_TOOL_SPECS: list[dict] = [', ] for s in schemas: spec = { "toolSpec": { "name": s["name"], "description": s["description"], "inputSchema": {"json": s["inputSchema"]}, } } lines.append(f" {json.dumps(spec, ensure_ascii=False)},") lines.append("]") lines.append("") lines.append("# Mapping: tool name -> requires_confirmation flag") lines.append("BROWSER_TOOL_CONFIRMATION: dict[str, bool] = {") for s in schemas: lines.append(f' "{s["name"]}": {str(s.get("requires_confirmation", False))},') lines.append("}") lines.append("") lines.append(f"BROWSER_TOOL_NAMES: list[str] = {json.dumps([s['name'] for s in schemas])}") lines.append("") return "\n".join(lines) def generate_typescript(schemas: list[dict]) -> str: lines = [ "/**", " * Auto-generated browser tool schemas and types.", " * DO NOT EDIT — generated by tools/generate_tool_defs.py from tools/schema/*.json.", " */", "", "export interface BrowserToolSchema {", " name: string;", " description: string;", " requires_confirmation: boolean;", " inputSchema: Record;", "}", "", "export const BROWSER_TOOL_SCHEMAS: BrowserToolSchema[] = [", ] for s in schemas: lines.append(f" {json.dumps(s, ensure_ascii=False)},") lines.append("];") lines.append("") lines.append("export const BROWSER_TOOL_NAMES = [") for s in schemas: lines.append(f' "{s["name"]}",') lines.append("] as const;") lines.append("") lines.append("export type BrowserToolName = typeof BROWSER_TOOL_NAMES[number];") lines.append("") lines.append("export const REQUIRES_CONFIRMATION: Record = {") for s in schemas: lines.append(f' "{s["name"]}": {str(s.get("requires_confirmation", False)).lower()},') lines.append("};") lines.append("") return "\n".join(lines) def main(): schemas = load_schemas() print(f"Loaded {len(schemas)} tool schemas from {SCHEMA_DIR}") py_code = generate_python(schemas) # NICHT nur auf das Verzeichnis pruefen — das existiert fast immer und der # Generator schrieb dann in ein beliebiges Nachbarverzeichnis (gemessen). # Der Marker macht daraus eine echte Aussage: liegt hier das Agenten-Repo? if (PY_OUTPUT.parent / "tools.py").is_file(): PY_OUTPUT.write_text(py_code, encoding="utf-8") print(f"Generated Python: {PY_OUTPUT} ({len(py_code)} bytes)") else: print(f"Skipped Python output: {PY_OUTPUT.parent} not found " f"(agent repo not checked out next to this one)") ts_code = generate_typescript(schemas) TS_OUTPUT.parent.mkdir(parents=True, exist_ok=True) TS_OUTPUT.write_text(ts_code, encoding="utf-8") print(f"Generated TypeScript: {TS_OUTPUT} ({len(ts_code)} bytes)") if __name__ == "__main__": main()