
ToolSpec.pudu
Pudu83 lines3.3 KB
1/** @Domain.Catalog.ToolSpec.Aggregate — tool definitions held as data */2module PuduLangMcp.Domain.Catalog.ToolSpec34import Std.Json as Json5import PuduLangMcp.Utils.JsonAccess as Access67/** @Domain.Catalog.ToolSpec.Kind — JSON kind of one argument */8export type Kind = TextKind | IntegerKind | BooleanKind910/** @Domain.Catalog.ToolSpec.Property — one declared tool argument */11export type Property = { name: Str, kind: Kind, description: Str, required: Bool, minimum: Option[Int], maximum: Option[Int] }1213/** @Domain.Catalog.ToolSpec.Annotations — behaviour hints every tool declares */14export type Annotations = { readOnly: Bool, destructive: Bool, idempotent: Bool, openWorld: Bool }1516/** @Domain.Catalog.ToolSpec.ToolSpec — one tool described once */17export type ToolSpec = {18 name: Str,19 title: Str,20 description: Str,21 properties: Array[Property],22 exactlyOne: Array[Str],23 annotations: Annotations24}252627export fn text(name: Str, description: Str, required: Bool) -> Property {28 Property{name: name, kind: TextKind, description: description, required: required, minimum: None, maximum: None}29}303132export fn integer(name: Str, description: Str, required: Bool, minimum: Int, maximum: Int) -> Property {33 Property{name: name, kind: IntegerKind, description: description, required: required, minimum: Some(minimum), maximum: Some(maximum)}34}353637export fn kindName(kind: &Kind) -> Str {38 match kind {39 case TextKind => "string"40 case IntegerKind => "integer"41 case BooleanKind => "boolean"42 }43}444546export fn toJson(spec: &ToolSpec) -> Json.Json {47 Json.object(&[48 ("name", Json.Text(spec.name)),49 ("title", Json.Text(spec.title)),50 ("description", Json.Text(spec.description)),51 ("inputSchema", schema(spec)),52 ("annotations", Json.object(&[53 ("title", Json.Text(spec.title)),54 ("readOnlyHint", Json.Boolean(spec.annotations.readOnly)),55 ("destructiveHint", Json.Boolean(spec.annotations.destructive)),56 ("idempotentHint", Json.Boolean(spec.annotations.idempotent)),57 ("openWorldHint", Json.Boolean(spec.annotations.openWorld))58 ]))59 ])60}616263fn schema(spec: &ToolSpec) -> Json.Json {64 var fields = [("type", Json.Text("object")), ("additionalProperties", Json.Boolean(false))]65 if !spec.properties.isEmpty() {66 fields = fields.push(("properties", Json.object(&spec.properties.map(|held: Property| (held.name, propertyJson(&held))))))67 }68 let required = spec.properties.filter(|held: Property| held.required).map(|held: Property| held.name)69 if !required.isEmpty() { fields = fields.push(("required", Access.texts(&required))) }70 if !spec.exactlyOne.isEmpty() {71 fields = fields.push(("oneOf", Json.list(&spec.exactlyOne.map(|name: Str| Json.object(&[("required", Access.texts(&[name]))])))))72 }73 Json.object(&fields)74}757677fn propertyJson(held: &Property) -> Json.Json {78 var fields = [("type", Json.Text(kindName(&held.kind))), ("description", Json.Text(held.description))]79 if let Some(low) = held.minimum { fields = fields.push(("minimum", Json.Number(low))) }80 if let Some(high) = held.maximum { fields = fields.push(("maximum", Json.Number(high))) }81 Json.object(&fields)82}83