diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 2ac94df..3621043 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -36,8 +36,8 @@ jobs: - name: Install TinyGo run: | - wget https://github.com/tinygo-org/tinygo/releases/download/v0.34.0/tinygo_0.34.0_amd64.deb - sudo dpkg -i tinygo_0.34.0_amd64.deb + wget https://github.com/tinygo-org/tinygo/releases/download/v0.35.0/tinygo_0.35.0_amd64.deb + sudo dpkg -i tinygo_0.35.0_amd64.deb - name: Install extism-js run: | diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 589e5e4..96de13c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -36,8 +36,8 @@ jobs: - name: Install TinyGo run: | - wget https://github.com/tinygo-org/tinygo/releases/download/v0.34.0/tinygo_0.34.0_amd64.deb - sudo dpkg -i tinygo_0.34.0_amd64.deb + wget https://github.com/tinygo-org/tinygo/releases/download/v0.35.0/tinygo_0.35.0_amd64.deb + sudo dpkg -i tinygo_0.35.0_amd64.deb - name: Install extism-js run: | diff --git a/justfile b/justfile index dfd7f74..c63b305 100644 --- a/justfile +++ b/justfile @@ -13,6 +13,7 @@ prepare: build: #!/usr/bin/env bash set -eou pipefail + for dir in servlets/*/; do cd "$dir" echo "Building $dir" @@ -21,6 +22,9 @@ build: cd ../.. done + cd simulations/describe-output + make build + push: #!/usr/bin/env bash set -eou pipefail diff --git a/servlets/amadeus-flight-api/xtp.toml b/servlets/amadeus-flight-api/xtp.toml index dc0a988..efe11d3 100755 --- a/servlets/amadeus-flight-api/xtp.toml +++ b/servlets/amadeus-flight-api/xtp.toml @@ -15,3 +15,7 @@ name = "amadeus-flight-api" # xtp plugin init runs this script before running the format script prepare = "sh prepare.sh && go get ./..." + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/assembly-ai/xtp.toml b/servlets/assembly-ai/xtp.toml index e2f4802..ecdbfd2 100644 --- a/servlets/assembly-ai/xtp.toml +++ b/servlets/assembly-ai/xtp.toml @@ -15,3 +15,7 @@ format = "go fmt && go mod tidy && goimports -w main.go" # xtp plugin init runs this script before running the format script prepare = "sh prepare.sh && go get ./..." + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/crypto-hash/xtp.toml b/servlets/crypto-hash/xtp.toml index 3e3ebdc..20eec68 100644 --- a/servlets/crypto-hash/xtp.toml +++ b/servlets/crypto-hash/xtp.toml @@ -15,3 +15,7 @@ name = "crypto-hash" # xtp plugin init runs this script before running the format script prepare = "bash prepare.sh && go get ./..." + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/currency-converter/xtp.toml b/servlets/currency-converter/xtp.toml index abd1f70..71c19d4 100755 --- a/servlets/currency-converter/xtp.toml +++ b/servlets/currency-converter/xtp.toml @@ -20,3 +20,8 @@ name = "currency-converter" [[test]] name = "basic tests" with = "../../test/testsuite/dist/test.wasm" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" + diff --git a/servlets/eval-py/xtp.toml b/servlets/eval-py/xtp.toml index 61e9703..04d5806 100755 --- a/servlets/eval-py/xtp.toml +++ b/servlets/eval-py/xtp.toml @@ -19,3 +19,7 @@ name = "eval-py" [[test]] name = "basic tests" with = "../../test/testsuite/dist/test.wasm" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/eval_js/xtp.toml b/servlets/eval_js/xtp.toml index 3601e4b..914a962 100755 --- a/servlets/eval_js/xtp.toml +++ b/servlets/eval_js/xtp.toml @@ -19,3 +19,7 @@ name = "eval_js" [[test]] name = "basic tests" with = "../../test/testsuite/dist/test.wasm" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/fetch/xtp.toml b/servlets/fetch/xtp.toml index ddf29e3..ba1bf48 100755 --- a/servlets/fetch/xtp.toml +++ b/servlets/fetch/xtp.toml @@ -19,3 +19,7 @@ prepare = "bash prepare.sh" [[test]] name = "basic tests" with = "../../test/testsuite/dist/test.wasm" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/filesystem/xtp.toml b/servlets/filesystem/xtp.toml index f45626d..2409ae1 100755 --- a/servlets/filesystem/xtp.toml +++ b/servlets/filesystem/xtp.toml @@ -15,3 +15,7 @@ format = "cargo fmt" # xtp plugin init runs this script before running the format script prepare = "bash prepare.sh" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/github/issues.go b/servlets/github/issues.go index 87a6068..cd2e9a2 100644 --- a/servlets/github/issues.go +++ b/servlets/github/issues.go @@ -44,7 +44,7 @@ var ( "title": prop("string", "The title of the issue"), "body": prop("string", "The body of the issue"), "state": prop("string", "The state of the issue"), - "assignees": arrprop("string", "The assignees of the issue", "string"), + "assignees": arrprop("The assignees of the issue", "string"), "milestone": prop("integer", "The milestone of the issue"), }, "required": []string{"owner", "repo", "title", "body"}, @@ -89,7 +89,7 @@ var ( "title": prop("string", "The title of the issue"), "body": prop("string", "The body of the issue"), "state": prop("string", "The state of the issue"), - "assignees": arrprop("string", "The assignees of the issue", "string"), + "assignees": arrprop("The assignees of the issue", "string"), "milestone": prop("integer", "The milestone of the issue"), }, "required": []string{"owner", "repo", "issue"}, diff --git a/servlets/github/main.go b/servlets/github/main.go index 025b2e6..311bc98 100755 --- a/servlets/github/main.go +++ b/servlets/github/main.go @@ -182,9 +182,9 @@ func prop(tpe, description string) SchemaProperty { return SchemaProperty{Type: tpe, Description: description} } -func arrprop(tpe, description, itemstpe string) SchemaProperty { +func arrprop(description, itemstpe string) SchemaProperty { items := schema{"type": itemstpe} - return SchemaProperty{Type: tpe, Description: description, Items: &items} + return SchemaProperty{Type: "array", Description: description, Items: &items} } type schema = map[string]interface{} diff --git a/servlets/github/xtp.toml b/servlets/github/xtp.toml index 5f67306..3f9744f 100755 --- a/servlets/github/xtp.toml +++ b/servlets/github/xtp.toml @@ -15,3 +15,7 @@ name = "github" # xtp plugin init runs this script before running the format script prepare = "sh prepare.sh && go get ./..." + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/google-maps-image/xtp.toml b/servlets/google-maps-image/xtp.toml index 3b68a23..321a9b6 100755 --- a/servlets/google-maps-image/xtp.toml +++ b/servlets/google-maps-image/xtp.toml @@ -15,3 +15,7 @@ name = "google-maps-image" # xtp plugin init runs this script before running the format script prepare = "sh prepare.sh && go get ./..." + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/google-maps/src/tools.ts b/servlets/google-maps/src/tools.ts index 5143283..bfbbba6 100644 --- a/servlets/google-maps/src/tools.ts +++ b/servlets/google-maps/src/tools.ts @@ -48,8 +48,14 @@ export const SEARCH_PLACES_TOOL: ToolDescription = { location: { type: "object", properties: { - latitude: { type: "number" }, - longitude: { type: "number" } + latitude: { + type: "number", + description: "Latitude coordinate for the center point" + }, + longitude: { + type: "number", + description: "Longitude coordinate for the center point" + } }, description: "Optional center point for the search" }, @@ -114,8 +120,14 @@ export const ELEVATION_TOOL: ToolDescription = { items: { type: "object", properties: { - latitude: { type: "number" }, - longitude: { type: "number" } + latitude: { + type: "number", + description: "Latitude coordinate of the location" + }, + longitude: { + type: "number", + description: "Longitude coordinate of the location" + } }, required: ["latitude", "longitude"] }, diff --git a/servlets/google-maps/xtp.toml b/servlets/google-maps/xtp.toml index 18a8810..e9781d1 100755 --- a/servlets/google-maps/xtp.toml +++ b/servlets/google-maps/xtp.toml @@ -15,3 +15,7 @@ name = "google-maps" # xtp plugin init runs this script before running the format script prepare = "bash prepare.sh" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/greet/xtp.toml b/servlets/greet/xtp.toml index 1f66886..fba534f 100755 --- a/servlets/greet/xtp.toml +++ b/servlets/greet/xtp.toml @@ -18,4 +18,8 @@ name = "greet" [[test]] name = "basic tests" -with = "../../test/testsuite/dist/test.wasm" \ No newline at end of file +with = "../../test/testsuite/dist/test.wasm" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/historical-flight-api/xtp.toml b/servlets/historical-flight-api/xtp.toml index e0953e6..a237824 100755 --- a/servlets/historical-flight-api/xtp.toml +++ b/servlets/historical-flight-api/xtp.toml @@ -15,3 +15,7 @@ name = "historical-flight-api" # xtp plugin init runs this script before running the format script prepare = "bash prepare.sh && go get ./..." + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/ipfs/xtp.toml b/servlets/ipfs/xtp.toml index 7cff1e8..152b158 100755 --- a/servlets/ipfs/xtp.toml +++ b/servlets/ipfs/xtp.toml @@ -15,3 +15,7 @@ name = "ipfs" # xtp plugin init runs this script before running the format script prepare = "bash prepare.sh" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/json-schema/xtp.toml b/servlets/json-schema/xtp.toml index 862abea..d1a019f 100644 --- a/servlets/json-schema/xtp.toml +++ b/servlets/json-schema/xtp.toml @@ -18,4 +18,8 @@ name = "json-schema" [[test]] name = "basic tests" -with = "../../test/testsuite/dist/test.wasm" \ No newline at end of file +with = "../../test/testsuite/dist/test.wasm" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/notion/src/lib.rs b/servlets/notion/src/lib.rs index 60299c1..8a33f2f 100755 --- a/servlets/notion/src/lib.rs +++ b/servlets/notion/src/lib.rs @@ -602,20 +602,68 @@ pub(crate) fn describe() -> Result { input_schema: serde_json::json!({ "type": "object", "properties": { - "parent": { - "type": "object", - "description": "Parent object of the database" - }, - "title": { - "type": "array", - "description": "Title of database as it appears in Notion. An array of rich text objects.", - "items": rich_text_schema - }, - "properties": { - "type": "object", - "description": "Property schema of database. The keys are the names of properties as they appear in Notion and the values are property schema objects." - } - }, + "parent": { + "type": "object", + "description": "Parent object of the database" + }, + "title": { + "type": "array", + "description": "Title of database as it appears in Notion. An array of rich text objects.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of rich text object" + }, + "text": { + "type": "object", + "description": "Text content and its attributes", + "properties": { + "content": { + "type": "string", + "description": "The actual text content" + }, + "link": { + "type": "object", + "description": "Link data if the text is a link" + } + } + }, + "annotations": { + "type": "object", + "description": "Formatting annotations for the text", + "properties": { + "bold": { + "type": "boolean", + "description": "Whether the text is bold" + }, + "code": { + "type": "boolean", + "description": "Whether the text is code format" + }, + "italic": { + "type": "boolean", + "description": "Whether the text is italicized" + }, + "strikethrough": { + "type": "boolean", + "description": "Whether the text has a strikethrough" + }, + "underline": { + "type": "boolean", + "description": "Whether the text is underlined" + } + } + } + } + } + }, + "properties": { + "type": "object", + "description": "Property schema of database. The keys are the names of properties as they appear in Notion and the values are property schema objects." + } + }, "required": ["parent", "title", "properties"], "additionalProperties": false }).as_object().unwrap().clone(), @@ -678,26 +726,74 @@ pub(crate) fn describe() -> Result { input_schema: serde_json::json!({ "type": "object", "properties": { - "parent": { - "type": "object", - "description": "Parent object that specifies the page to comment on", - "properties": { - "page_id": { - "type": "string", - "description": "The ID of the page to comment on. ".to_string() + COMMON_ID_DESCRIPTION - } - } - }, - "discussion_id": { - "type": "string", - "description": "The ID of an existing discussion thread to add a comment to. ".to_string() + COMMON_ID_DESCRIPTION - }, - "rich_text": { - "type": "array", - "description": "Array of rich text objects representing the comment content.", - "items": rich_text_schema - } - }, + "parent": { + "type": "object", + "description": "Parent object that specifies the page to comment on", + "properties": { + "page_id": { + "type": "string", + "description": "The ID of the page to comment on. ".to_string() + COMMON_ID_DESCRIPTION + } + } + }, + "discussion_id": { + "type": "string", + "description": "The ID of an existing discussion thread to add a comment to. ".to_string() + COMMON_ID_DESCRIPTION + }, + "rich_text": { + "type": "array", + "description": "Array of rich text objects representing the comment content.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of rich text object" + }, + "text": { + "type": "object", + "description": "Text content and its attributes", + "properties": { + "content": { + "type": "string", + "description": "The actual text content" + }, + "link": { + "type": "object", + "description": "Link data if the text is a link" + } + } + }, + "annotations": { + "type": "object", + "description": "Text formatting options", + "properties": { + "bold": { + "type": "boolean", + "description": "Whether the text is bold" + }, + "code": { + "type": "boolean", + "description": "Whether the text is in code format" + }, + "italic": { + "type": "boolean", + "description": "Whether the text is italicized" + }, + "strikethrough": { + "type": "boolean", + "description": "Whether the text has a strikethrough" + }, + "underline": { + "type": "boolean", + "description": "Whether the text is underlined" + } + } + } + } + } + } + }, "required": ["rich_text"], "additionalProperties": false }).as_object().unwrap().clone(), @@ -709,47 +805,49 @@ pub(crate) fn describe() -> Result { input_schema: serde_json::json!({ "type": "object", "properties": { - "query": { - "type": "string", - "description": "Text to search for in page or database titles" - }, - "filter": { - "type": "object", - "description": "Filter results by object type (page or database)", - "properties": { - "property": { - "type": "string", - "description": "Must be 'object'" - }, - "value": { - "type": "string", - "description": "Either 'page' or 'database'" - } - } - }, - "sort": { - "type": "object", - "description": "Sort configuration for search results", - "properties": { - "direction": { - "type": "string", - "enum": ["ascending", "descending"] - }, - "timestamp": { - "type": "string", - "enum": ["last_edited_time"] - } - } - }, - "start_cursor": { - "type": "string", - "description": "Pagination cursor" - }, - "page_size": { - "type": "integer", - "description": "Number of results to return (max 100)" - } - }, + "query": { + "type": "string", + "description": "Text to search for in page or database titles" + }, + "filter": { + "type": "object", + "description": "Filter results by object type (page or database)", + "properties": { + "property": { + "type": "string", + "description": "Must be 'object'" + }, + "value": { + "type": "string", + "description": "Either 'page' or 'database'" + } + } + }, + "sort": { + "type": "object", + "description": "Sort configuration for search results", + "properties": { + "direction": { + "type": "string", + "enum": ["ascending", "descending"], + "description": "Sort direction - ascending or descending order" + }, + "timestamp": { + "type": "string", + "enum": ["last_edited_time"], + "description": "Timestamp field to sort by - currently only supports last_edited_time" + } + } + }, + "start_cursor": { + "type": "string", + "description": "Pagination cursor" + }, + "page_size": { + "type": "integer", + "description": "Number of results to return (max 100)" + } + }, "additionalProperties": false }).as_object().unwrap().clone(), }, diff --git a/servlets/notion/xtp.toml b/servlets/notion/xtp.toml index 053712a..01f9e6c 100755 --- a/servlets/notion/xtp.toml +++ b/servlets/notion/xtp.toml @@ -15,3 +15,7 @@ name = "notion" # xtp plugin init runs this script before running the format script prepare = "bash prepare.sh" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/obsidian/plugin/plugin.py b/servlets/obsidian/plugin/plugin.py index a7a39ee..97443f2 100755 --- a/servlets/obsidian/plugin/plugin.py +++ b/servlets/obsidian/plugin/plugin.py @@ -170,6 +170,7 @@ def describe() -> ListToolsResult: description="Lists all files and directories in the root directory of your Obsidian vault.", inputSchema={ "type": "object", + "properties": {}, }, ), ToolDescription( diff --git a/servlets/obsidian/xtp.toml b/servlets/obsidian/xtp.toml index c6ca99b..2eda563 100755 --- a/servlets/obsidian/xtp.toml +++ b/servlets/obsidian/xtp.toml @@ -15,3 +15,7 @@ name = "obsidian" # xtp plugin init runs this script before running the format script prepare = "bash prepare.sh && uv sync" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/qr-code/xtp.toml b/servlets/qr-code/xtp.toml index 4ebf340..fe7cb61 100755 --- a/servlets/qr-code/xtp.toml +++ b/servlets/qr-code/xtp.toml @@ -19,3 +19,7 @@ format = "cargo fmt" [[test]] name = "basic tests" with = "../../test/testsuite/dist/test.wasm" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/slack/xtp.toml b/servlets/slack/xtp.toml index 3675319..4614a4c 100755 --- a/servlets/slack/xtp.toml +++ b/servlets/slack/xtp.toml @@ -15,3 +15,7 @@ name = "slack" # xtp plugin init runs this script before running the format script prepare = "bash prepare.sh" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/tenor-gifs/xtp.toml b/servlets/tenor-gifs/xtp.toml index 8ce7a52..9f743e1 100644 --- a/servlets/tenor-gifs/xtp.toml +++ b/servlets/tenor-gifs/xtp.toml @@ -18,4 +18,8 @@ name = "tenor-gifs" [[test]] name = "basic tests" -with = "../../test/testsuite/dist/test.wasm" \ No newline at end of file +with = "../../test/testsuite/dist/test.wasm" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/text-to-speech/xtp.toml b/servlets/text-to-speech/xtp.toml index 2374d1e..33458c7 100755 --- a/servlets/text-to-speech/xtp.toml +++ b/servlets/text-to-speech/xtp.toml @@ -15,3 +15,7 @@ name = "text-to-speech" # xtp plugin init runs this script before running the format script prepare = "bash prepare.sh" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/time/xtp.toml b/servlets/time/xtp.toml index b5fe1aa..baee6fc 100755 --- a/servlets/time/xtp.toml +++ b/servlets/time/xtp.toml @@ -15,3 +15,7 @@ name = "time" # xtp plugin init runs this script before running the format script prepare = "bash prepare.sh" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/trello/xtp.toml b/servlets/trello/xtp.toml index 4b1ec41..857db1f 100644 --- a/servlets/trello/xtp.toml +++ b/servlets/trello/xtp.toml @@ -15,3 +15,7 @@ name = "trello" # xtp plugin init runs this script before running the format script prepare = "sh prepare.sh && go get ./..." + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/wolfram-alpha/src/lib.rs b/servlets/wolfram-alpha/src/lib.rs index 0e1b97e..3667307 100755 --- a/servlets/wolfram-alpha/src/lib.rs +++ b/servlets/wolfram-alpha/src/lib.rs @@ -47,22 +47,26 @@ pub(crate) fn call(input: types::CallToolRequest) -> Result Result { - Ok(types::ToolDescription { - name: "wolfram_llm".to_string(), - description: "Query the Wolfram Alpha LLM API".to_string(), - input_schema: serde_json::json!({ - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "the question or prompt to send to Wolfram Alpha's LLM" - } - }, - "required": ["query"] - }) - .as_object() - .unwrap() - .clone(), +pub(crate) fn describe() -> Result { + Ok(types::ListToolsResult { + tools: vec![ + types::ToolDescription { + name: "wolfram_llm".to_string(), + description: "Query the Wolfram Alpha LLM API".to_string(), + input_schema: serde_json::json!({ + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "the question or prompt to send to Wolfram Alpha's LLM" + } + }, + "required": ["query"] + }) + .as_object() + .unwrap() + .clone(), + } + ], }) } diff --git a/servlets/wolfram-alpha/src/pdk.rs b/servlets/wolfram-alpha/src/pdk.rs index 10f2106..c7d199b 100755 --- a/servlets/wolfram-alpha/src/pdk.rs +++ b/servlets/wolfram-alpha/src/pdk.rs @@ -75,7 +75,13 @@ pub mod types { use super::*; #[derive( - Default, serde::Serialize, serde::Deserialize, extism_pdk::FromBytes, extism_pdk::ToBytes, + Default, + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + extism_pdk::FromBytes, + extism_pdk::ToBytes, )] #[encoding(Json)] pub struct BlobResourceContents { @@ -95,7 +101,13 @@ pub mod types { } #[derive( - Default, serde::Serialize, serde::Deserialize, extism_pdk::FromBytes, extism_pdk::ToBytes, + Default, + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + extism_pdk::FromBytes, + extism_pdk::ToBytes, )] #[encoding(Json)] pub struct CallToolRequest { @@ -109,7 +121,13 @@ pub mod types { } #[derive( - Default, serde::Serialize, serde::Deserialize, extism_pdk::FromBytes, extism_pdk::ToBytes, + Default, + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + extism_pdk::FromBytes, + extism_pdk::ToBytes, )] #[encoding(Json)] pub struct CallToolResult { @@ -126,7 +144,13 @@ pub mod types { } #[derive( - Default, serde::Serialize, serde::Deserialize, extism_pdk::FromBytes, extism_pdk::ToBytes, + Default, + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + extism_pdk::FromBytes, + extism_pdk::ToBytes, )] #[encoding(Json)] pub struct Content { @@ -158,7 +182,13 @@ pub mod types { } #[derive( - Default, serde::Serialize, serde::Deserialize, extism_pdk::FromBytes, extism_pdk::ToBytes, + Default, + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + extism_pdk::FromBytes, + extism_pdk::ToBytes, )] #[encoding(Json)] pub enum ContentType { @@ -172,7 +202,29 @@ pub mod types { } #[derive( - Default, serde::Serialize, serde::Deserialize, extism_pdk::FromBytes, extism_pdk::ToBytes, + Default, + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + extism_pdk::FromBytes, + extism_pdk::ToBytes, + )] + #[encoding(Json)] + pub struct ListToolsResult { + /// The list of ToolDescription objects provided by this servlet. + #[serde(rename = "tools")] + pub tools: Vec, + } + + #[derive( + Default, + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + extism_pdk::FromBytes, + extism_pdk::ToBytes, )] #[encoding(Json)] pub struct Params { @@ -186,7 +238,13 @@ pub mod types { } #[derive( - Default, serde::Serialize, serde::Deserialize, extism_pdk::FromBytes, extism_pdk::ToBytes, + Default, + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + extism_pdk::FromBytes, + extism_pdk::ToBytes, )] #[encoding(Json)] pub enum Role { @@ -198,7 +256,13 @@ pub mod types { } #[derive( - Default, serde::Serialize, serde::Deserialize, extism_pdk::FromBytes, extism_pdk::ToBytes, + Default, + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + extism_pdk::FromBytes, + extism_pdk::ToBytes, )] #[encoding(Json)] pub struct TextAnnotation { @@ -218,7 +282,13 @@ pub mod types { } #[derive( - Default, serde::Serialize, serde::Deserialize, extism_pdk::FromBytes, extism_pdk::ToBytes, + Default, + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + extism_pdk::FromBytes, + extism_pdk::ToBytes, )] #[encoding(Json)] pub struct TextResourceContents { @@ -238,7 +308,13 @@ pub mod types { } #[derive( - Default, serde::Serialize, serde::Deserialize, extism_pdk::FromBytes, extism_pdk::ToBytes, + Default, + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + extism_pdk::FromBytes, + extism_pdk::ToBytes, )] #[encoding(Json)] pub struct ToolDescription { diff --git a/servlets/wolfram-alpha/xtp.toml b/servlets/wolfram-alpha/xtp.toml index c684174..79515f7 100755 --- a/servlets/wolfram-alpha/xtp.toml +++ b/servlets/wolfram-alpha/xtp.toml @@ -15,3 +15,7 @@ name = "wolfram-alpha" # xtp plugin init runs this script before running the format script prepare = "bash prepare.sh" + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/servlets/wordpress/xtp.toml b/servlets/wordpress/xtp.toml index 3619052..9457ebd 100644 --- a/servlets/wordpress/xtp.toml +++ b/servlets/wordpress/xtp.toml @@ -15,3 +15,7 @@ name = "wordpress" # xtp plugin init runs this script before running the format script prepare = "sh prepare.sh && go get ./..." + +[[test]] +name = "describe output" +with = "../../simulations/describe-output/dist/plugin.wasm" \ No newline at end of file diff --git a/simulations/describe-output/.gitignore b/simulations/describe-output/.gitignore index ef7d91f..76add87 100644 --- a/simulations/describe-output/.gitignore +++ b/simulations/describe-output/.gitignore @@ -1 +1,2 @@ -plugin.wasm +node_modules +dist \ No newline at end of file diff --git a/simulations/describe-output/LICENSE b/simulations/describe-output/LICENSE index 4440418..53f0fa6 100644 --- a/simulations/describe-output/LICENSE +++ b/simulations/describe-output/LICENSE @@ -1,9 +1,7 @@ -BSD 3-Clause License +Copyright 2024, The Extism Authors. -Copyright (c) 2024, Extism - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. @@ -12,17 +10,17 @@ modification, are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/simulations/describe-output/Makefile b/simulations/describe-output/Makefile index 8e7dd3a..728cf3a 100644 --- a/simulations/describe-output/Makefile +++ b/simulations/describe-output/Makefile @@ -1,5 +1,6 @@ build: - tinygo build -target wasi -o plugin.wasm main.go + npm ci + npm run build -test: - extism call plugin.wasm greet --input "world" --wasi +test: build + xtp plugin test ../../servlets/crypto-hash/dist/plugin.wasm --with ./dist/plugin.wasm diff --git a/simulations/describe-output/README.md b/simulations/describe-output/README.md index 934481a..b5e60cb 100644 --- a/simulations/describe-output/README.md +++ b/simulations/describe-output/README.md @@ -1,5 +1,4 @@ -# Extism Go PDK Plugin +# Extism TS PDK Plugin -See more documentation at https://github.com/extism/go-pdk and +See more documentation at https://github.com/extism/js-pdk and [join us on Discord](https://extism.org/discord) for more help. - diff --git a/simulations/describe-output/esbuild.js b/simulations/describe-output/esbuild.js new file mode 100644 index 0000000..04cb6e8 --- /dev/null +++ b/simulations/describe-output/esbuild.js @@ -0,0 +1,12 @@ +const esbuild = require('esbuild'); + +esbuild + .build({ + entryPoints: ['src/index.ts'], + outdir: 'dist', + bundle: true, + sourcemap: true, + minify: false, // might want to use true for production build + format: 'cjs', // needs to be CJS for now + target: ['es2020'] // don't go over es2020 because quickjs doesn't support it + }) \ No newline at end of file diff --git a/simulations/describe-output/go.mod b/simulations/describe-output/go.mod deleted file mode 100644 index 322e54c..0000000 --- a/simulations/describe-output/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module github.com/extism/go-pdk-template - -go 1.22.1 - -toolchain go1.23.3 - -require github.com/extism/go-pdk v1.0.2 - -require github.com/dylibso/xtp-test-go v0.0.2 // indirect diff --git a/simulations/describe-output/go.sum b/simulations/describe-output/go.sum deleted file mode 100644 index 535a995..0000000 --- a/simulations/describe-output/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -github.com/dylibso/xtp-test-go v0.0.2 h1:CW+Wl7JY/l30WA4nMZhZrwYC4cZbTvA+5cdJoPV3+Uc= -github.com/dylibso/xtp-test-go v0.0.2/go.mod h1:YBGHZkaFOWNH3geVBx3/byJmd5XPa+Nz8XNwKahnT3Q= -github.com/extism/go-pdk v1.0.2 h1:UB7oTW3tw2zoMlsUdBEDAAbhQg9OudzgNeyCwQYZ730= -github.com/extism/go-pdk v1.0.2/go.mod h1:Gz+LIU/YCKnKXhgge8yo5Yu1F/lbv7KtKFkiCSzW/P4= diff --git a/simulations/describe-output/main.go b/simulations/describe-output/main.go deleted file mode 100644 index 9af20a9..0000000 --- a/simulations/describe-output/main.go +++ /dev/null @@ -1,196 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "strings" - - xtptest "github.com/dylibso/xtp-test-go" -) - -type ListToolsResult struct { - Tools []ToolDescription `json:"tools"` -} - -type ToolDescription struct { - Description string `json:"description"` - InputSchema interface{} `json:"inputSchema"` - Name string `json:"name"` -} - -func validateProperty(toolName, propName string, prop interface{}) error { - propMap, ok := prop.(map[string]interface{}) - if !ok { - return fmt.Errorf("tool %s: property %s must be an object", toolName, propName) - } - - // Check for required property fields - requiredFields := []string{"type", "description"} - for _, field := range requiredFields { - val, exists := propMap[field] - if !exists { - return fmt.Errorf("tool %s: property %s missing required field %s", toolName, propName, field) - } - - strVal, ok := val.(string) - if !ok || strVal == "" { - return fmt.Errorf("tool %s: property %s.%s must be a non-empty string", toolName, propName, field) - } - } - - return nil -} - -func validateToolDescription(tool ToolDescription) error { - // Validate Name - if tool.Name == "" { - return fmt.Errorf("tool name is required") - } - if strings.TrimSpace(tool.Name) != tool.Name { - return fmt.Errorf("tool name cannot have leading or trailing whitespace: %q", tool.Name) - } - - // Validate Description - if tool.Description == "" { - return fmt.Errorf("tool %s: description is required", tool.Name) - } - if strings.TrimSpace(tool.Description) != tool.Description { - return fmt.Errorf("tool %s: description cannot have leading or trailing whitespace", tool.Name) - } - - // Validate InputSchema structure - schema, ok := tool.InputSchema.(map[string]interface{}) - if !ok { - return fmt.Errorf("tool %s: inputSchema must be an object", tool.Name) - } - - // Validate schema type exists - typeVal, hasType := schema["type"] - if !hasType { - return fmt.Errorf("tool %s: missing inputSchema.type", tool.Name) - } - typeStr, ok := typeVal.(string) - if !ok || typeStr == "" { - return fmt.Errorf("tool %s: inputSchema.type must be a non-empty string", tool.Name) - } - - // If type is object, validate properties and required fields - if typeStr == "object" { - properties, err := parseProperties(schema) - if err != nil { - return fmt.Errorf("tool %s: %w", tool.Name, err) - } - - required, err := parseRequiredFields(schema) - if err != nil { - return fmt.Errorf("tool %s: %w", tool.Name, err) - } - - // If properties is nil, required should also be empty - if properties == nil && len(required) > 0 { - return fmt.Errorf("tool %s: cannot have required fields without properties", tool.Name) - } - - if properties != nil { - // Validate each property - for propName, prop := range properties { - if err := validateProperty(tool.Name, propName, prop); err != nil { - return err - } - } - var missingFields []string - for _, field := range required { - if _, exists := properties[field]; !exists { - missingFields = append(missingFields, field) - } - } - if len(missingFields) > 0 { - return fmt.Errorf("tool %s: required fields missing from properties: %s", - tool.Name, - strings.Join(missingFields, ", ")) - } - } - } - - return nil -} - -// parseProperties returns the properties map if it exists and is valid -func parseProperties(schema map[string]interface{}) (map[string]interface{}, error) { - props, exists := schema["properties"] - if !exists || props == nil { - return nil, nil - } - - properties, ok := props.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("properties must be an object") - } - - return properties, nil -} - -// parseRequiredFields returns the required fields if they exist and are valid -func parseRequiredFields(schema map[string]interface{}) ([]string, error) { - req, exists := schema["required"] - if !exists || req == nil { - return []string{}, nil - } - - reqArray, ok := req.([]interface{}) - if !ok { - return []string{}, fmt.Errorf("required fields must be an array") - } - - required := make([]string, 0, len(reqArray)) - seenFields := make(map[string]bool) - - for _, field := range reqArray { - fieldStr, ok := field.(string) - if !ok { - return []string{}, fmt.Errorf("required field names must be strings") - } - - // Check for duplicate required fields - if seenFields[fieldStr] { - return []string{}, fmt.Errorf("duplicate required field: %s", fieldStr) - } - seenFields[fieldStr] = true - - required = append(required, fieldStr) - } - - return required, nil -} - -//go:export test -func test() int32 { - output := xtptest.CallBytes("describe", nil) - xtptest.AssertNe("describe returned output", string(output), "") - - var result ListToolsResult - if err := json.Unmarshal(output, &result); err != nil { - xtptest.Assert("describe returns valid JSON", false, fmt.Sprintf("invalid JSON: %v", err)) - return 0 - } - - if len(result.Tools) == 0 { - xtptest.Assert("describe provides at least one tool", false, "describe must provide at least one tool") - return 0 - } - - xtptest.Group("validate tool descriptions", func() { - for i, tool := range result.Tools { - testName := fmt.Sprintf("tool[%d](%s) is valid", i, tool.Name) - if err := validateToolDescription(tool); err != nil { - xtptest.Assert(testName, false, err.Error()) - return - } - xtptest.Assert(testName, true, "tool validation passed") - } - }) - - return 0 -} - -func main() {} diff --git a/simulations/describe-output/package-lock.json b/simulations/describe-output/package-lock.json new file mode 100644 index 0000000..c7242c6 --- /dev/null +++ b/simulations/describe-output/package-lock.json @@ -0,0 +1,566 @@ +{ + "name": "describe-output-simulation", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "describe-output-simulation", + "version": "1.0.0", + "license": "BSD-3-Clause", + "dependencies": { + "@dylibso/xtp-test": "^0.0.12", + "ajv": "^8.17.1" + }, + "devDependencies": { + "@extism/js-pdk": "^1.1.1", + "esbuild": "^0.19.6", + "prettier": "^3.3.2", + "prettier-plugin-organize-imports": "^4.1.0", + "typescript": "^5.3.2" + } + }, + "node_modules/@dylibso/xtp-test": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@dylibso/xtp-test/-/xtp-test-0.0.12.tgz", + "integrity": "sha512-SQcOtmlR1M20fmiCpubUnHbNp9UhfGBVEB2lMSKsow2vnFrKJnto95fHuaGfqYyggDmR0hs6vz/HirJtDQjJDA==", + "license": "BSD-3-Clause" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@extism/js-pdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@extism/js-pdk/-/js-pdk-1.1.1.tgz", + "integrity": "sha512-VZLn/dX0ttA1uKk2PZeR/FL3N+nA1S5Vc7E5gdjkR60LuUIwCZT9cYON245V4HowHlBA7YOegh0TLjkx+wNbrA==", + "dev": true, + "license": "BSD-Clause-3", + "dependencies": { + "urlpattern-polyfill": "^8.0.2" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "git+ssh://git@github.com/dylibso/fast-uri.git#4842a4b77c727b9b1ea9ea95911c834a80d9e9ed", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-organize-imports": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz", + "integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": ">=2.0", + "typescript": ">=2.9", + "vue-tsc": "^2.1.0" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz", + "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/simulations/describe-output/package.json b/simulations/describe-output/package.json new file mode 100644 index 0000000..46b615d --- /dev/null +++ b/simulations/describe-output/package.json @@ -0,0 +1,26 @@ +{ + "name": "describe-output-simulation", + "version": "1.0.0", + "description": "", + "main": "src/index.ts", + "scripts": { + "build": "tsc --noEmit && node esbuild.js && extism-js dist/index.js -i src/index.d.ts -o dist/plugin.wasm" + }, + "keywords": [], + "author": "", + "license": "BSD-3-Clause", + "devDependencies": { + "@extism/js-pdk": "^1.1.1", + "esbuild": "^0.19.6", + "typescript": "^5.3.2", + "prettier": "^3.3.2", + "prettier-plugin-organize-imports": "^4.1.0" + }, + "dependencies": { + "@dylibso/xtp-test": "^0.0.12", + "ajv": "^8.17.1" + }, + "overrides": { + "fast-uri": "github:dylibso/fast-uri#remove-unicode-flags-from-regex" + } +} diff --git a/simulations/describe-output/src/index.d.ts b/simulations/describe-output/src/index.d.ts new file mode 100644 index 0000000..5deefd3 --- /dev/null +++ b/simulations/describe-output/src/index.d.ts @@ -0,0 +1,14 @@ +declare module "main" { + export function test(): I32; +} + +declare module "xtp:test" { + interface harness { + assert(name: PTR, value: I64, reason: PTR): void; + call(func: PTR, input: PTR): PTR; + time(func: PTR, input: PTR): I64; + group(name: PTR): void; + reset(): void; + mock_input(): PTR; + } +} \ No newline at end of file diff --git a/simulations/describe-output/src/index.ts b/simulations/describe-output/src/index.ts new file mode 100644 index 0000000..f7b70a9 --- /dev/null +++ b/simulations/describe-output/src/index.ts @@ -0,0 +1,103 @@ +import { Test } from "@dylibso/xtp-test"; +import Ajv from "ajv"; +import draft7 from 'ajv/lib/refs/json-schema-draft-07.json'; + +export function test() { + const result: ListToolsResult = Test.call("describe", "").json(); + + for (const tool of result.tools) { + if (!tool?.name) { + Test.assert(`Tool name is provided`, false, tool.name); + continue; + } + + Test.group(tool.name, () => { + const nameIsNotEmpty = !!tool.name && typeof tool.name === "string" && tool.name.length > 0; + Test.assert(`Tool name is not empty: ${tool.name}`, nameIsNotEmpty, tool.name); + + const nameHasNoWhitespace = tool.name.trim() === tool.name; + Test.assert(`Tool name has no leading/trailing whitespace`, nameHasNoWhitespace, tool.name); + + const descriptionIsNotEmpty = !!tool.description && typeof tool.description === "string" && tool.description.length > 0; + Test.assert(`Tool description is not empty: ${tool.description}`, descriptionIsNotEmpty, tool.description); + + const descHasNoWhitespace = tool.description.trim() === tool.description; + Test.assert(`Tool description has no leading/trailing whitespace`, descHasNoWhitespace, tool.description); + + const inputSchemaIsAnObject = !!tool.inputSchema && typeof tool.inputSchema === "object"; + Test.assert(`Tool input schema is an object`, inputSchemaIsAnObject, ''); + + const inputSchemaHasProperties = !!tool.inputSchema && !!(tool.inputSchema as any).properties && typeof (tool.inputSchema as any).properties === "object"; + Test.assert(`Tool inputSchema.properties must be an object`, inputSchemaHasProperties, ''); + + const inputSchemaHasTypeObject = !!tool.inputSchema && !!(tool.inputSchema as any).type && (tool.inputSchema as any).type === "object"; + Test.assert(`Tool inputSchema.type must be 'object'`, inputSchemaHasTypeObject, ''); + + validateInputSchema(tool); + }); + } +} + +interface ListToolsResult { + /** The list of ToolDescription objects provided by this servlet. */ + tools: ToolDescription[]; +} + +interface ToolDescription { + /** A description of the tool */ + description: string; + + /** The JSON schema describing the argument input */ + inputSchema: unknown; + + /** The name of the tool. It should match the plugin / binding name. */ + name: string; +} + +function validateInputSchema(tool: ToolDescription) { + const ajv = new Ajv({ + strict: false, + strictTypes: true, + meta: true, + allowUnionTypes: true, + allErrors: true, + }); + + // the meta schema is draft-07 with some modifications + const metaschema = { + ...draft7, + $id: 'tool-input', + + // input schema can't have additional properties + additionalProperties: false, + + // properties must have type and description + properties: { + ...draft7.properties, + properties: { + type: "object", + additionalProperties: { + allOf: [ + { "$ref": "#" }, + { type: "object", required: ["type", "description"] } + ] + } + } + } + }; + + const validate = ajv.compile(metaschema); + const valid = validate(tool.inputSchema); + if (!valid) { + Test.assert(`Tool input schema is valid`, false, JSON.stringify(validate.errors, null, 2)); + return + } + + try { + ajv.compile(tool.inputSchema as any); + } catch (e: any) { + Test.assert(`Tool input schema compiles`, false, e.message); + } + + Test.assert(`Tool input schema is valid`, true, ''); +} diff --git a/simulations/describe-output/tsconfig.json b/simulations/describe-output/tsconfig.json new file mode 100644 index 0000000..659e178 --- /dev/null +++ b/simulations/describe-output/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2020", // Specify ECMAScript target version + "module": "commonjs", // Specify module code generation + "lib": [ + "es2020" + ], // Specify a list of library files to be included in the compilation + "types": [ + "@extism/js-pdk", + "./src/index.d.ts" + ], // Specify a list of type definition files to be included in the compilation + "strict": true, // Enable all strict type-checking options + "esModuleInterop": true, // Enables compatibility with Babel-style module imports + "skipLibCheck": true, // Skip type checking of declaration files + "allowJs": true, // Allow JavaScript files to be compiled + "noEmit": true, // Do not emit outputs (no .js or .d.ts files) + "resolveJsonModule": true, // Include modules imported with .json extension + }, + "include": [ + "src/**/*.ts" // Include all TypeScript files in src directory + ], + "exclude": [ + "node_modules" // Exclude the node_modules directory + ] +}