Move 121/210 parameter-level Schema field overlays from schema_hints/metadata JSON into in-code ParamDecl declarations on DeclareLeafMetadata commands. The declared values are emitted as dws.schema.* annotations at assembly time via ApplyParamDecls, outranking tool_schema_hint (rank 620 > 500) so the hint overlay becomes redundant once the declaration is in place. Key mechanism changes: - Add SchemaDecl.Parameters []ParamDecl with property/required/ interface_type/description/required_when/enum fields - Add cli.ParamDecl type carried inside ContractFinalPayload - ApplyParamDecls emits dws.schema.* annotations from the payload at assembly time (no sync.Map, no tree-rebuild key issue) - Add cli.AnnotateRuntimeFlagInterfaceType (41 overlays needed it) - Add cli.AnnotateRuntimeFlagRequiredValue for explicit true/false - Compatibility alias check tolerates dws.schema.* annotation differences between primary and alias commands - Remove runtime source_hash recomputation (87ms/997k allocs saved; enforced by check-generated-drift.sh at build time instead) - Add shortcut.Flag.RequiredWhen and wire through FromShortcut - Add boolFlag OR semantics to fix confirmationBypass disagreement - Add bindKey default kebab-to-camel for forgotten Bind - Add schema consumption benchmarks (catalog decode 1.5s/817MB, shortcut load 1.9ms/3MB — three orders of magnitude apart) - Add catalog codegen feasibility probe (34 tools: 0.06s compile, 31ns access, 87KB linked — extrapolates to 2.1MB for 845 tools) Migrated products (29 tools, 121 fields): aisearch(1), chat(12), contact(4), doc(3), drive(1), hrbrain(10), mail(2), report(1), sheet(3), todo(6) Remaining 89 fields across 11 products blocked by: - RPCName not found as string literal (19 tools, shortcut/variable) - No matching DeclareLeafMetadata near callMCPTool (11 tools) - No single RPCName for multi-step commands (drive.upload etc.) All tests green: corecmd, cli, helpers. Generation and drift clean.
103 lines
3.8 KiB
Python
103 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""One-shot feasibility probe: render one product's catalog shard as Go literals.
|
|
|
|
Answers whether the shortcut storage model (compiled-in struct literals, zero
|
|
runtime parse) is viable for the 845-tool Schema catalog, by measuring compile
|
|
time, binary size and access cost on a single product first.
|
|
|
|
Deliberately drops field_provenance: it is 50% of the bytes and no runtime
|
|
consumer reads it, so a codegen design would keep it in JSON for audit only.
|
|
Keeping it here would make the size numbers pessimistic and the design wrong.
|
|
"""
|
|
import json
|
|
import sys
|
|
|
|
|
|
def q(s: str) -> str:
|
|
return json.dumps(s if s is not None else "", ensure_ascii=False)
|
|
|
|
|
|
def strs(v) -> str:
|
|
if not v:
|
|
return "nil"
|
|
return "[]string{" + ", ".join(q(x) for x in v) + "}"
|
|
|
|
|
|
def render_param(name, p) -> str:
|
|
fields = [
|
|
f"Name: {q(p.get('name') or name)}",
|
|
f"Type: {q(p.get('type'))}",
|
|
f"Description: {q(p.get('description'))}",
|
|
f"Property: {q(p.get('property'))}",
|
|
f"Required: {str(bool(p.get('required'))).lower()}",
|
|
f"CLIRequired: {str(bool(p.get('cli_required'))).lower()}",
|
|
f"RequiredWhen: {q(p.get('required_when'))}",
|
|
f"Format: {q(p.get('format'))}",
|
|
f"Enum: {strs(p.get('enum'))}",
|
|
]
|
|
return "{" + ", ".join(fields) + "}"
|
|
|
|
|
|
def render_tool(canonical, t) -> str:
|
|
params = t.get("parameters") or {}
|
|
lines = [
|
|
f"\t{q(canonical)}: {{",
|
|
f"\t\tName: {q(t.get('name'))},",
|
|
f"\t\tCanonicalPath: {q(t.get('canonical_path'))},",
|
|
f"\t\tCLIPath: {q(t.get('cli_path'))},",
|
|
f"\t\tPrimaryCLIPath: {q(t.get('primary_cli_path'))},",
|
|
f"\t\tProductID: {q(t.get('product_id'))},",
|
|
f"\t\tGroup: {q(t.get('group'))},",
|
|
f"\t\tTitle: {q(t.get('title'))},",
|
|
f"\t\tDescription: {q(t.get('description'))},",
|
|
f"\t\tAliases: {strs(t.get('aliases'))},",
|
|
f"\t\tIsAlias: {str(bool(t.get('is_alias'))).lower()},",
|
|
f"\t\tEffect: {q(t.get('effect'))},",
|
|
f"\t\tRisk: {q(t.get('risk'))},",
|
|
f"\t\tConfirmation: {q(t.get('confirmation'))},",
|
|
f"\t\tIdempotency: {q(t.get('idempotency'))},",
|
|
f"\t\tInterfaceMode: {q(t.get('interface_mode'))},",
|
|
f"\t\tAvailability: {q(t.get('availability'))},",
|
|
f"\t\tAgentSummary: {q(t.get('agent_summary'))},",
|
|
f"\t\tUseWhen: {strs(t.get('use_when'))},",
|
|
f"\t\tAvoidWhen: {strs(t.get('avoid_when'))},",
|
|
f"\t\tExamples: {strs(t.get('examples'))},",
|
|
f"\t\tHasParameters: {str(bool(t.get('has_parameters'))).lower()},",
|
|
f"\t\tParameterCount: {int(t.get('parameter_count') or 0)},",
|
|
]
|
|
if params:
|
|
lines.append("\t\tParameters: map[string]Param{")
|
|
for pname in sorted(params):
|
|
lines.append(f"\t\t\t{q(pname)}: {render_param(pname, params[pname])},")
|
|
lines.append("\t\t},")
|
|
lines.append("\t},")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main() -> int:
|
|
src, product, out = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
tools = json.load(open(src, encoding="utf-8"))["tools"]
|
|
body = "\n".join(render_tool(c, tools[c]) for c in sorted(tools))
|
|
with open(out, "w", encoding="utf-8") as f:
|
|
f.write(f"""// Copyright 2026 Alibaba Group
|
|
// Code generated by scripts/dev/catalog_codegen_probe.py; DO NOT EDIT.
|
|
//
|
|
// Feasibility probe for storing the Schema catalog as compiled-in Go literals
|
|
// instead of embedded JSON, mirroring how internal/shortcut stores its 376
|
|
// definitions. field_provenance is omitted: no runtime consumer reads it.
|
|
|
|
package catalogcodegen
|
|
|
|
// Tools holds the {product} product's leaf ToolSpecs as static data, so there is
|
|
// no decode step on the first lookup.
|
|
var Tools = map[string]Tool{{
|
|
{body}
|
|
}}
|
|
""")
|
|
print(f"generated {out}: {len(tools)} tools")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|