From cfa05362b3543f0b7f510c6d6d538e0f69a188e6 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 14:13:51 -0400 Subject: [PATCH 01/25] feat(asyncapi): scaffold typespec-asyncapi emitter package --- package.json | 3 +- specs/emitters/typespec-asyncapi/lib/main.tsp | 1 + specs/emitters/typespec-asyncapi/package.json | 38 + .../emitters/typespec-asyncapi/tsconfig.json | 14 + .../typespec-asyncapi/vitest.config.ts | 7 + yarn.lock | 1876 ++++++++++++++++- 6 files changed, 1932 insertions(+), 7 deletions(-) create mode 100644 specs/emitters/typespec-asyncapi/lib/main.tsp create mode 100644 specs/emitters/typespec-asyncapi/package.json create mode 100644 specs/emitters/typespec-asyncapi/tsconfig.json create mode 100644 specs/emitters/typespec-asyncapi/vitest.config.ts diff --git a/package.json b/package.json index 2186dd0702..2a78fea89f 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "type": "module", "description": "SignalWire documentation", "workspaces": [ - "specs" + "specs", + "specs/emitters/*" ], "scripts": { "dev": "docker compose -f docker-compose.dev.yml up --build --watch", diff --git a/specs/emitters/typespec-asyncapi/lib/main.tsp b/specs/emitters/typespec-asyncapi/lib/main.tsp new file mode 100644 index 0000000000..06847d5a6d --- /dev/null +++ b/specs/emitters/typespec-asyncapi/lib/main.tsp @@ -0,0 +1 @@ +import "../dist/src/index.js"; diff --git a/specs/emitters/typespec-asyncapi/package.json b/specs/emitters/typespec-asyncapi/package.json new file mode 100644 index 0000000000..bb56f0016c --- /dev/null +++ b/specs/emitters/typespec-asyncapi/package.json @@ -0,0 +1,38 @@ +{ + "name": "@signalwire/typespec-asyncapi", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "dist/src/index.js", + "tspMain": "lib/main.tsp", + "exports": { + ".": { + "typespec": "./lib/main.tsp", + "types": "./dist/src/index.d.ts", + "default": "./dist/src/index.js" + }, + "./testing": { + "types": "./dist/src/testing/index.d.ts", + "default": "./dist/src/testing/index.js" + } + }, + "scripts": { + "build": "tsc -p .", + "watch": "tsc -p . --watch", + "test": "vitest run", + "test:watch": "vitest" + }, + "peerDependencies": { + "@typespec/compiler": "1.11.0" + }, + "dependencies": { + "yaml": "^2.3.1" + }, + "devDependencies": { + "@typespec/compiler": "1.11.0", + "@asyncapi/parser": "^3.0.0", + "ajv": "^8.12.0", + "typescript": "^5.5.4", + "vitest": "^2.0.0" + } +} diff --git a/specs/emitters/typespec-asyncapi/tsconfig.json b/specs/emitters/typespec-asyncapi/tsconfig.json new file mode 100644 index 0000000000..7412f2f8bb --- /dev/null +++ b/specs/emitters/typespec-asyncapi/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "Node16", + "moduleResolution": "Node16", + "target": "es2022", + "strict": true, + "rootDir": ".", + "outDir": "./dist", + "sourceMap": true, + "declaration": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/specs/emitters/typespec-asyncapi/vitest.config.ts b/specs/emitters/typespec-asyncapi/vitest.config.ts new file mode 100644 index 0000000000..8b5840acac --- /dev/null +++ b/specs/emitters/typespec-asyncapi/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["test/**/*.test.ts"], + }, +}); diff --git a/yarn.lock b/yarn.lock index d32742fa28..823bc98aae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,38 @@ # yarn lockfile v1 +"@asyncapi/parser@^3.0.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@asyncapi/parser/-/parser-3.6.0.tgz#89b52d79e8f8856c6eecc849d5f7931d5863b0d4" + integrity sha512-6S0Yr8vI418a1IrpGsOYbfWVo9+aHvSqN2oSkiY0YJltS/C7oDOt9e0mo6hSld8bg+EeKrtgkVmpW4obh1JFvA== + dependencies: + "@asyncapi/specs" "^6.11.1" + "@openapi-contrib/openapi-schema-to-json-schema" "~3.2.0" + "@stoplight/json" "3.21.0" + "@stoplight/json-ref-readers" "^1.2.2" + "@stoplight/json-ref-resolver" "^3.1.5" + "@stoplight/spectral-core" "^1.18.3" + "@stoplight/spectral-functions" "^1.7.2" + "@stoplight/spectral-parsers" "^1.0.2" + "@stoplight/spectral-ref-resolver" "^1.0.3" + "@stoplight/types" "^13.12.0" + "@types/json-schema" "^7.0.11" + "@types/urijs" "^1.19.19" + ajv "^8.17.1" + ajv-errors "^3.0.0" + ajv-formats "^2.1.1" + avsc "^5.7.5" + js-yaml "^4.1.1" + jsonpath-plus "^10.0.7" + node-fetch "2.6.7" + +"@asyncapi/specs@^6.11.1": + version "6.11.1" + resolved "https://registry.yarnpkg.com/@asyncapi/specs/-/specs-6.11.1.tgz#c629fe962a241a983f883a56b0e9c901a311becb" + integrity sha512-A3WBLqAKGoJ2+6FWFtpjBlCQ1oFCcs4GxF7zsIGvNqp/klGUHjlA3aAcZ9XMMpLGE8zPeYDz2x9FmO6DSuKraQ== + dependencies: + "@types/json-schema" "^7.0.11" + "@babel/code-frame@~7.29.0": version "7.29.0" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" @@ -72,6 +104,121 @@ "@boundaryml/baml-win32-arm64-msvc" "0.219.0" "@boundaryml/baml-win32-x64-msvc" "0.219.0" +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== + "@exodus/schemasafe@^1.0.0-rc.2": version "1.3.0" resolved "https://registry.yarnpkg.com/@exodus/schemasafe/-/schemasafe-1.3.0.tgz#731656abe21e8e769a7f70a4d833e6312fe59b7f" @@ -228,6 +375,26 @@ dependencies: minipass "^7.0.4" +"@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jsep-plugin/assignment@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@jsep-plugin/assignment/-/assignment-1.3.0.tgz#fcfc5417a04933f7ceee786e8ab498aa3ce2b242" + integrity sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ== + +"@jsep-plugin/regex@^1.0.1", "@jsep-plugin/regex@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@jsep-plugin/regex/-/regex-1.0.4.tgz#cb2fc423220fa71c609323b9ba7f7d344a755fcc" + integrity sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg== + +"@jsep-plugin/ternary@^1.0.2": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@jsep-plugin/ternary/-/ternary-1.1.4.tgz#1ac778bee799137f116cc108f3bf58b9615c45c3" + integrity sha512-ck5wiqIbqdMX6WRQztBL7ASDty9YLgJ3sSAK5ZpBzXeySvFGCzIvM6UiAI4hTZ22fEcYQVV/zhUbNscggW+Ukg== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -249,6 +416,138 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@openapi-contrib/openapi-schema-to-json-schema@~3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@openapi-contrib/openapi-schema-to-json-schema/-/openapi-schema-to-json-schema-3.2.0.tgz#c4c92edd4478b5ecb3d99c29ecb355118259dccc" + integrity sha512-Gj6C0JwCr8arj0sYuslWXUBSP/KnUlEGnPW4qxlXvAl543oaNQgMgIgkQUA6vs5BCCvwTEiL8m/wdWzfl4UvSw== + dependencies: + fast-deep-equal "^3.1.3" + +"@rollup/rollup-android-arm-eabi@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz#634b0258cc501bef2353cee09a887b434826e81f" + integrity sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ== + +"@rollup/rollup-android-arm64@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz#d7804ff9c31c2b8e7c51d966fedac65a4c828578" + integrity sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA== + +"@rollup/rollup-darwin-arm64@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz#f26d03228e48c8bd55ff6be847242308dbfdb50d" + integrity sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw== + +"@rollup/rollup-darwin-x64@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz#6e9037ccfc806a749aa044b063256a26ad32339d" + integrity sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w== + +"@rollup/rollup-freebsd-arm64@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz#ff448605b36cc4736a6fea89bd0eb74653f09cbc" + integrity sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ== + +"@rollup/rollup-freebsd-x64@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz#a30fe00a8651b577966022d1db1fb1bd6776105e" + integrity sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz#0ba85b63893eb17e11052bd21fe2809afc475a82" + integrity sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ== + +"@rollup/rollup-linux-arm-musleabihf@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz#982bf23fcfe4e8e13002912d4073f56a2eea2a39" + integrity sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA== + +"@rollup/rollup-linux-arm64-gnu@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz#c94d1e8bd116ea2b569aab37dd04a6ccab74f1ab" + integrity sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g== + +"@rollup/rollup-linux-arm64-musl@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz#a7d79014ba3c5dd2d140309730365d413976db24" + integrity sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw== + +"@rollup/rollup-linux-loong64-gnu@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz#5dd943c58bda55d8b269426bd60a47dd9c27776e" + integrity sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg== + +"@rollup/rollup-linux-loong64-musl@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz#08b1b9d362c64847306fea979b935e93a2590c4e" + integrity sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA== + +"@rollup/rollup-linux-ppc64-gnu@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz#1c5de568966d11091281b22bc764ee7adf92667b" + integrity sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w== + +"@rollup/rollup-linux-ppc64-musl@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz#4dde1c9b941748ea49e07cfc96c64b18236225cd" + integrity sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg== + +"@rollup/rollup-linux-riscv64-gnu@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz#21dd1014033b970dd23189d1d4d3cdab45de7f9a" + integrity sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg== + +"@rollup/rollup-linux-riscv64-musl@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz#4664e0bae205a3a18eb6407c10054c4b8dd7f381" + integrity sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg== + +"@rollup/rollup-linux-s390x-gnu@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz#b05a6b3af6a0d3c9b9f7be9c253eb4f101a67848" + integrity sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA== + +"@rollup/rollup-linux-x64-gnu@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz#85dda72aa08cdc256f80f46d881b2a988bb0cce2" + integrity sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg== + +"@rollup/rollup-linux-x64-musl@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz#d79f5be62a484b58a8ec4d5ae23acf7b0eb1a8ff" + integrity sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw== + +"@rollup/rollup-openbsd-x64@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz#8ebafe0d66cde1c8ab0a867cd9dcea89e22ee7b1" + integrity sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg== + +"@rollup/rollup-openharmony-arm64@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz#105537bcfcb2fd82796518184e995ae4396bb792" + integrity sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg== + +"@rollup/rollup-win32-arm64-msvc@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz#08ebcfc01b5b3b106ae074bae3692e94a63b5125" + integrity sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw== + +"@rollup/rollup-win32-ia32-msvc@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz#493005cb0fcab009e866ccdbad3c97c512c2bf4b" + integrity sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw== + +"@rollup/rollup-win32-x64-gnu@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz#47b40294def035268329d5ffd5364347bf726e5f" + integrity sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA== + +"@rollup/rollup-win32-x64-msvc@4.62.0": + version "4.62.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz#6850434fdb691e9b2408ded9b65ea357bf83636d" + integrity sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA== + "@scalar/helpers@0.2.18": version "0.2.18" resolved "https://registry.yarnpkg.com/@scalar/helpers/-/helpers-0.2.18.tgz#4d575a51c11f312f8a2980d87e6a144795c77ae4" @@ -310,6 +609,228 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz#abb11d99aeb6d27f1b563c38147a72d50058e339" integrity sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ== +"@stoplight/better-ajv-errors@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@stoplight/better-ajv-errors/-/better-ajv-errors-1.0.3.tgz#d74a5c4da5d786c17188d7f4edec505f089885fa" + integrity sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA== + dependencies: + jsonpointer "^5.0.0" + leven "^3.1.0" + +"@stoplight/json-ref-readers@1.2.2", "@stoplight/json-ref-readers@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@stoplight/json-ref-readers/-/json-ref-readers-1.2.2.tgz#e5992bae597f228f988f362a4c0304c03a92008b" + integrity sha512-nty0tHUq2f1IKuFYsLM4CXLZGHdMn+X/IwEUIpeSOXt0QjMUbL0Em57iJUDzz+2MkWG83smIigNZ3fauGjqgdQ== + dependencies: + node-fetch "^2.6.0" + tslib "^1.14.1" + +"@stoplight/json-ref-resolver@^3.1.5", "@stoplight/json-ref-resolver@~3.1.6": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@stoplight/json-ref-resolver/-/json-ref-resolver-3.1.6.tgz#dcf8724472b7d54e8e8952510f39b8ee901dcf56" + integrity sha512-YNcWv3R3n3U6iQYBsFOiWSuRGE5su1tJSiX6pAPRVk7dP0L7lqCteXGzuVRQ0gMZqUl8v1P0+fAKxF6PLo9B5A== + dependencies: + "@stoplight/json" "^3.21.0" + "@stoplight/path" "^1.3.2" + "@stoplight/types" "^12.3.0 || ^13.0.0" + "@types/urijs" "^1.19.19" + dependency-graph "~0.11.0" + fast-memoize "^2.5.2" + immer "^9.0.6" + lodash "^4.17.21" + tslib "^2.6.0" + urijs "^1.19.11" + +"@stoplight/json@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@stoplight/json/-/json-3.21.0.tgz#c0dff9c478f3365d7946cb6e34c17cc2fa84250b" + integrity sha512-5O0apqJ/t4sIevXCO3SBN9AHCEKKR/Zb4gaj7wYe5863jme9g02Q0n/GhM7ZCALkL+vGPTe4ZzTETP8TFtsw3g== + dependencies: + "@stoplight/ordered-object-literal" "^1.0.3" + "@stoplight/path" "^1.3.2" + "@stoplight/types" "^13.6.0" + jsonc-parser "~2.2.1" + lodash "^4.17.21" + safe-stable-stringify "^1.1" + +"@stoplight/json@^3.17.0", "@stoplight/json@^3.17.1", "@stoplight/json@^3.20.1", "@stoplight/json@^3.21.0", "@stoplight/json@~3.21.0": + version "3.21.7" + resolved "https://registry.yarnpkg.com/@stoplight/json/-/json-3.21.7.tgz#102f5fd11921984c96672ce4307850daa1cbfc7b" + integrity sha512-xcJXgKFqv/uCEgtGlPxy3tPA+4I+ZI4vAuMJ885+ThkTHFVkC+0Fm58lA9NlsyjnkpxFh4YiQWpH+KefHdbA0A== + dependencies: + "@stoplight/ordered-object-literal" "^1.0.3" + "@stoplight/path" "^1.3.2" + "@stoplight/types" "^13.6.0" + jsonc-parser "~2.2.1" + lodash "^4.17.21" + safe-stable-stringify "^1.1" + +"@stoplight/ordered-object-literal@^1.0.3", "@stoplight/ordered-object-literal@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.5.tgz#06689095a4f1a53e9d9a5f0055f707c387af966a" + integrity sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg== + +"@stoplight/path@1.3.2", "@stoplight/path@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@stoplight/path/-/path-1.3.2.tgz#96e591496b72fde0f0cdae01a61d64f065bd9ede" + integrity sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ== + +"@stoplight/spectral-core@1.23.0", "@stoplight/spectral-core@^1.18.3": + version "1.23.0" + resolved "https://registry.yarnpkg.com/@stoplight/spectral-core/-/spectral-core-1.23.0.tgz#af02ae6d09e882718e9d21a5d3193edf6642df7d" + integrity sha512-WvdgmiiJrjiMrcw7ByxfcYtUvAXNp2MhAfcEIXP3Mn8ZOVwyAWIsFjLlsE5zRqj0LuN8+7OQM/L+BMcHj6x/BQ== + dependencies: + "@stoplight/better-ajv-errors" "1.0.3" + "@stoplight/json" "~3.21.0" + "@stoplight/path" "1.3.2" + "@stoplight/spectral-parsers" "^1.0.0" + "@stoplight/spectral-ref-resolver" "^1.0.4" + "@stoplight/spectral-runtime" "^1.1.2" + "@stoplight/types" "~13.6.0" + "@types/es-aggregate-error" "^1.0.2" + "@types/json-schema" "^7.0.11" + ajv "^8.18.0" + ajv-errors "~3.0.0" + ajv-formats "~2.1.1" + es-aggregate-error "^1.0.7" + expr-eval-fork "^3.0.1" + jsonpath-plus "^10.3.0" + lodash "^4.18.1" + lodash.topath "^4.5.2" + minimatch "^3.1.4" + nimma "0.2.3" + pony-cause "^1.1.1" + tslib "^2.8.1" + +"@stoplight/spectral-formats@^1.8.1": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@stoplight/spectral-formats/-/spectral-formats-1.8.3.tgz#e0533ad1a97e35bbce646af590027c85034283c1" + integrity sha512-lfYzkHYS2mZQdm3k+TQ0lvXZ66vdBzJuy6awA4kXgQ0jWBbOC/FHzhBk5BaIVo2QRLUAGjMqWSd72WFryi+EvA== + dependencies: + "@stoplight/json" "^3.17.0" + "@stoplight/spectral-core" "1.23.0" + "@types/json-schema" "^7.0.7" + tslib "^2.8.1" + +"@stoplight/spectral-functions@^1.7.2": + version "1.10.3" + resolved "https://registry.yarnpkg.com/@stoplight/spectral-functions/-/spectral-functions-1.10.3.tgz#b8e7b71531a8609cd36daa71e17368e3203941bc" + integrity sha512-AM7Gbh7pv1Mpc6fdVuR7N6C5t5KT3QKDHeBPA27Cw/GAch1VJnHkCV9R/SxDrvOgZ3tL1xrtAGFuNFwRvVdz3g== + dependencies: + "@stoplight/better-ajv-errors" "1.0.3" + "@stoplight/json" "^3.17.1" + "@stoplight/spectral-core" "1.23.0" + "@stoplight/spectral-formats" "^1.8.1" + "@stoplight/spectral-runtime" "^1.1.2" + ajv "^8.18.0" + ajv-draft-04 "~1.0.0" + ajv-errors "~3.0.0" + ajv-formats "~2.1.1" + lodash "^4.18.1" + tslib "^2.8.1" + +"@stoplight/spectral-parsers@^1.0.0", "@stoplight/spectral-parsers@^1.0.2": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@stoplight/spectral-parsers/-/spectral-parsers-1.0.5.tgz#2febd979b2917465759c97fe7375145f86574ff2" + integrity sha512-ANDTp2IHWGvsQDAY85/jQi9ZrF4mRrA5bciNHX+PUxPr4DwS6iv4h+FVWJMVwcEYdpyoIdyL+SRmHdJfQEPmwQ== + dependencies: + "@stoplight/json" "~3.21.0" + "@stoplight/types" "^14.1.1" + "@stoplight/yaml" "~4.3.0" + tslib "^2.8.1" + +"@stoplight/spectral-ref-resolver@^1.0.3", "@stoplight/spectral-ref-resolver@^1.0.4": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@stoplight/spectral-ref-resolver/-/spectral-ref-resolver-1.0.5.tgz#2462ae79bbb90b7fcc76b014118a0beeee5e64d5" + integrity sha512-gj3TieX5a9zMW29z3mBlAtDOCgN3GEc1VgZnCVlr5irmR4Qi5LuECuFItAq4pTn5Zu+sW5bqutsCH7D4PkpyAA== + dependencies: + "@stoplight/json-ref-readers" "1.2.2" + "@stoplight/json-ref-resolver" "~3.1.6" + "@stoplight/spectral-runtime" "^1.1.2" + dependency-graph "0.11.0" + tslib "^2.8.1" + +"@stoplight/spectral-runtime@^1.1.2": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@stoplight/spectral-runtime/-/spectral-runtime-1.1.5.tgz#02b05c439fa35ec28c2a534b2f473887f7b5d034" + integrity sha512-6/HSCQBKnI4M5qonCKos2W7oggXv+U/ml+m/cAd4eJAYfIVEmaLUo03qSWIIl4cBc5ujJPmn2WnCiRrz1++P7Q== + dependencies: + "@stoplight/json" "^3.20.1" + "@stoplight/path" "^1.3.2" + "@stoplight/types" "^13.6.0" + abort-controller "^3.0.0" + lodash "^4.18.1" + node-fetch "^2.7.0" + tslib "^2.8.1" + +"@stoplight/types@^12.3.0 || ^13.0.0", "@stoplight/types@^13.12.0", "@stoplight/types@^13.6.0": + version "13.20.0" + resolved "https://registry.yarnpkg.com/@stoplight/types/-/types-13.20.0.tgz#d42682f1e3a14a3c60bdf0df08bff4023518763d" + integrity sha512-2FNTv05If7ib79VPDA/r9eUet76jewXFH2y2K5vuge6SXbRHtWBhcaRmu+6QpF4/WRNoJj5XYRSwLGXDxysBGA== + dependencies: + "@types/json-schema" "^7.0.4" + utility-types "^3.10.0" + +"@stoplight/types@^14.1.1": + version "14.1.1" + resolved "https://registry.yarnpkg.com/@stoplight/types/-/types-14.1.1.tgz#0dd5761aac25673a951955e984c724c138368b7a" + integrity sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g== + dependencies: + "@types/json-schema" "^7.0.4" + utility-types "^3.10.0" + +"@stoplight/types@~13.6.0": + version "13.6.0" + resolved "https://registry.yarnpkg.com/@stoplight/types/-/types-13.6.0.tgz#96c6aaae05858b36f589821cd52c95aa9b205ce7" + integrity sha512-dzyuzvUjv3m1wmhPfq82lCVYGcXG0xUYgqnWfCq3PCVR4BKFhjdkHrnJ+jIDoMKvXb05AZP/ObQF6+NpDo29IQ== + dependencies: + "@types/json-schema" "^7.0.4" + utility-types "^3.10.0" + +"@stoplight/yaml-ast-parser@0.0.50": + version "0.0.50" + resolved "https://registry.yarnpkg.com/@stoplight/yaml-ast-parser/-/yaml-ast-parser-0.0.50.tgz#ed625a1d9ae63eb61980446e058fa745386ab61e" + integrity sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ== + +"@stoplight/yaml@~4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@stoplight/yaml/-/yaml-4.3.0.tgz#ca403157472509812ccec6f277185e7e65d7bd7d" + integrity sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w== + dependencies: + "@stoplight/ordered-object-literal" "^1.0.5" + "@stoplight/types" "^14.1.1" + "@stoplight/yaml-ast-parser" "0.0.50" + tslib "^2.2.0" + +"@types/es-aggregate-error@^1.0.2": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/es-aggregate-error/-/es-aggregate-error-1.0.6.tgz#1472dfb0fb1cb4c3f2bd3b2a7b7e19f60a1d66c0" + integrity sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg== + dependencies: + "@types/node" "*" + +"@types/estree@1.0.9", "@types/estree@^1.0.0": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24" + integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== + +"@types/json-schema@^7.0.11", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.7": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@*": + version "25.9.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.9.3.tgz#11dfe7a33e68fa5c560f0aa76cc5595621ef26b9" + integrity sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg== + dependencies: + undici-types ">=7.24.0 <7.24.7" + +"@types/urijs@^1.19.19": + version "1.19.26" + resolved "https://registry.yarnpkg.com/@types/urijs/-/urijs-1.19.26.tgz#500fc9912e0ba01d635480970bdc9ba0f45d7bc6" + integrity sha512-wkXrVzX5yoqLnndOwFsieJA7oKM8cNkOKJtf/3vVGSUFkWDKZvFHpIl9Pvqb/T9UsawBBFMTTD8xu7sK5MWuvg== + "@typespec/asset-emitter@^0.79.1": version "0.79.1" resolved "https://registry.yarnpkg.com/@typespec/asset-emitter/-/asset-emitter-0.79.1.tgz#bacb659f18ffa0ec8fb3b5a47f8e304bca8a226a" @@ -372,12 +893,83 @@ resolved "https://registry.yarnpkg.com/@typespec/rest/-/rest-0.81.0.tgz#559ccf59af3d7090bae5605c1ec5cea38d65835a" integrity sha512-qQXZRKEvq5aNlDFEUqBiiXXPIFyr/+PWgBY0kIrnhyZzMjfUqPInkB12QgXpVp2O2Wm3jmETJD45SaLHTCYBbg== -ajv-draft-04@1.0.0, ajv-draft-04@^1.0.0: +"@vitest/expect@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.9.tgz#b566ea20d58ea6578d8dc37040d6c1a47ebe5ff8" + integrity sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw== + dependencies: + "@vitest/spy" "2.1.9" + "@vitest/utils" "2.1.9" + chai "^5.1.2" + tinyrainbow "^1.2.0" + +"@vitest/mocker@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.9.tgz#36243b27351ca8f4d0bbc4ef91594ffd2dc25ef5" + integrity sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg== + dependencies: + "@vitest/spy" "2.1.9" + estree-walker "^3.0.3" + magic-string "^0.30.12" + +"@vitest/pretty-format@2.1.9", "@vitest/pretty-format@^2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.9.tgz#434ff2f7611689f9ce70cd7d567eceb883653fdf" + integrity sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ== + dependencies: + tinyrainbow "^1.2.0" + +"@vitest/runner@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.9.tgz#cc18148d2d797fd1fd5908d1f1851d01459be2f6" + integrity sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g== + dependencies: + "@vitest/utils" "2.1.9" + pathe "^1.1.2" + +"@vitest/snapshot@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.9.tgz#24260b93f798afb102e2dcbd7e61c6dfa118df91" + integrity sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ== + dependencies: + "@vitest/pretty-format" "2.1.9" + magic-string "^0.30.12" + pathe "^1.1.2" + +"@vitest/spy@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.9.tgz#cb28538c5039d09818b8bfa8edb4043c94727c60" + integrity sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ== + dependencies: + tinyspy "^3.0.2" + +"@vitest/utils@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.9.tgz#4f2486de8a54acf7ecbf2c5c24ad7994a680a6c1" + integrity sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ== + dependencies: + "@vitest/pretty-format" "2.1.9" + loupe "^3.1.2" + tinyrainbow "^1.2.0" + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +ajv-draft-04@1.0.0, ajv-draft-04@^1.0.0, ajv-draft-04@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8" integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw== -ajv-formats@2.1.1: +ajv-errors@^3.0.0, ajv-errors@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-3.0.0.tgz#e54f299f3a3d30fe144161e5f0d8d51196c527bc" + integrity sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ== + +ajv-formats@2.1.1, ajv-formats@^2.1.1, ajv-formats@~2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== @@ -401,7 +993,7 @@ ajv@^8.0.0, ajv@^8.17.1: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" -ajv@^8.11.0: +ajv@^8.11.0, ajv@^8.12.0, ajv@^8.18.0: version "8.20.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.20.0.tgz#304b3636add88ba7d936760dd50ece006dea95f9" integrity sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA== @@ -448,11 +1040,72 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" + +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + is-array-buffer "^3.0.4" + +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + +astring@^1.8.1: + version "1.9.0" + resolved "https://registry.yarnpkg.com/astring/-/astring-1.9.0.tgz#cc73e6062a7eb03e7d19c22d8b0b3451fd9bfeef" + integrity sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg== + +async-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== + async@3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +avsc@^5.7.5: + version "5.7.9" + resolved "https://registry.yarnpkg.com/avsc/-/avsc-5.7.9.tgz#8532cd47b2fbff95be4bc470c6780c258d86680a" + integrity sha512-yOA4wFeI7ET3v32Di/sUybQ+ttP20JHSW3mxLuNGeO0uD6PPcvLrIQXSvy/rhJOWU5JrYh7U4OHplWMmtAtjMg== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.15" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.15.tgz#a6d90d54067236e5f42570a3b7378d594d9b7738" + integrity sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + braces@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -460,11 +1113,53 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bind@^1.0.7, call-bind@^1.0.8, call-bind@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.9.tgz#39a644700c80bc7d0ca9102fc6d1d43b2fd7eee7" + integrity sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + get-intrinsic "^1.3.0" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + call-me-maybe@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa" integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== +chai@^5.1.2: + version "5.3.3" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.3.3.tgz#dd3da955e270916a4bd3f625f4b919996ada7e06" + integrity sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + change-case@~5.4.4: version "5.4.4" resolved "https://registry.yarnpkg.com/change-case/-/change-case-5.4.4.tgz#0d52b507d8fb8f204343432381d1a6d7bff97a02" @@ -480,6 +1175,11 @@ charset@^1.0.0: resolved "https://registry.yarnpkg.com/charset/-/charset-1.0.1.tgz#8d59546c355be61049a8fa9164747793319852bd" integrity sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg== +check-error@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.3.tgz#2427361117b70cca8dc89680ead32b157019caf5" + integrity sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA== + chownr@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" @@ -544,6 +1244,82 @@ compute-lcm@^1.1.2: validate.io-function "^1.0.2" validate.io-integer-array "^1.0.0" +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +debug@^4.3.7: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +dependency-graph@0.11.0, dependency-graph@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.11.0.tgz#ac0ce7ed68a54da22165a85e97a01d53f5eb2e27" + integrity sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg== + +dunder-proto@^1.0.0, dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + emoji-regex@^10.3.0: version "10.6.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.6.0.tgz#bf3d6e8f7f8fd22a65d9703475bc0147357a6b0d" @@ -561,16 +1337,194 @@ env-paths@^4.0.0: dependencies: is-safe-filename "^0.1.0" +es-abstract-get@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-abstract-get/-/es-abstract-get-1.0.0.tgz#1eae87101f42bedeb6a740e8c5051271aef89088" + integrity sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg== + dependencies: + es-errors "^1.3.0" + es-object-atoms "^1.1.2" + is-callable "^1.2.7" + object-inspect "^1.13.4" + +es-abstract@^1.23.5, es-abstract@^1.23.9, es-abstract@^1.24.0, es-abstract@^1.24.2: + version "1.24.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.2.tgz#2dbd38c180735ee983f77585140a2706a963ed9a" + integrity sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-negative-zero "^2.0.3" + is-regex "^1.2.1" + is-set "^2.0.3" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.1" + math-intrinsics "^1.1.0" + object-inspect "^1.13.4" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.4" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.19" + +es-aggregate-error@^1.0.7: + version "1.0.14" + resolved "https://registry.yarnpkg.com/es-aggregate-error/-/es-aggregate-error-1.0.14.tgz#f1a24f833d25056c2ebc92a8c04449374f8f9f65" + integrity sha512-3YxX6rVb07B5TV11AV5wsL7nQCHXNwoHPsQC8S4AmBiqYhyNCJ5BRKXkXyDJvs8QzXN20NgRtxe3dEEQD9NLHA== + dependencies: + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.24.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + globalthis "^1.0.4" + has-property-descriptors "^1.0.2" + set-function-name "^2.0.2" + +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-module-lexer@^1.5.4: + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1, es-object-atoms@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz#a2d0b373205724dfa525d23b0c3e1b1ca582c99b" + integrity sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es-to-primitive@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.1.tgz#abd6ef5b12d7c25bcd9eb3a7ef63e568b451ba4a" + integrity sha512-CxN9N56HYfd2m/acc/NOFrZQsN9kU4eh+2kk6A707Kz1krH8tKmfrs5RnftB8WNX80T0NS7vSQsDOlg23diR2g== + dependencies: + es-abstract-get "^1.0.0" + es-errors "^1.3.0" + is-callable "^1.2.7" + is-date-object "^1.1.0" + is-symbol "^1.1.1" + es6-promise@^3.2.1: version "3.3.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" integrity sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg== +esbuild@^0.21.3: + version "0.21.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" + escalade@^3.1.1: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +expect-type@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.3.0.tgz#0d58ed361877a31bbc4dd6cf71bbfef7faf6bd68" + integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA== + +expr-eval-fork@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/expr-eval-fork/-/expr-eval-fork-3.0.3.tgz#82a3291db3835af9dfe05f519e4901f2b72219fb" + integrity sha512-BhC+hbc5lIVjygr840n5DEkW3MQq7H9o+mc1/N7Z5uIiCFVyESLL5DIE7LNq4CYUNxy+XjA+3jRrL/h0Kt2xcg== + fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -587,6 +1541,11 @@ fast-glob@^3.3.3: merge2 "^1.3.0" micromatch "^4.0.8" +fast-memoize@^2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e" + integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw== + fast-safe-stringify@^2.0.7: version "2.1.1" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" @@ -642,11 +1601,53 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" +for-each@^0.3.3, for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + foreach@^2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.6.tgz#87bcc8a1a0e74000ff2bf9802110708cfb02eb6e" integrity sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg== +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: + version "1.2.0" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.2.0.tgz#758f3e84fa542672454bd5e14cb081a5ce07f70c" + integrity sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew== + dependencies: + call-bind "^1.0.9" + call-bound "^1.0.4" + es-define-property "^1.0.1" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + hasown "^2.0.4" + is-callable "^1.2.7" + is-document.all "^1.0.0" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +generator-function@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/generator-function/-/generator-function-2.0.1.tgz#0e75dd410d1243687a0ba2e951b94eedb8f737a2" + integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g== + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -657,6 +1658,39 @@ get-east-asian-width@^1.0.0: resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz#9bc4caa131702b4b61729cb7e42735bc550c9ee6" integrity sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q== +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -664,6 +1698,14 @@ glob-parent@^5.1.2: dependencies: is-glob "^4.0.1" +globalthis@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + globby@~16.1.1: version "16.1.1" resolved "https://registry.yarnpkg.com/globby/-/globby-16.1.1.tgz#a26012f57b819f0491c834dbf09f89b0ce6ba104" @@ -676,6 +1718,11 @@ globby@~16.1.1: slash "^5.1.0" unicorn-magic "^0.4.0" +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + graphlib@2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" @@ -683,6 +1730,44 @@ graphlib@2.1.8: dependencies: lodash "^4.17.15" +has-bigints@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" + integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2, hasown@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" + integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== + dependencies: + function-bind "^1.1.2" + http-reasons@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/http-reasons/-/http-reasons-0.1.0.tgz#a953ca670078669dde142ce899401b9d6e85d3b4" @@ -712,16 +1797,112 @@ ignore@^7.0.5: resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== +immer@^9.0.6: + version "9.0.21" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" + integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== + +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" + +is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + +is-async-function@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== + dependencies: + async-function "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== + dependencies: + has-bigints "^1.0.2" + +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-data-view@^1.0.1, is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== + dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + is-typed-array "^1.1.13" + +is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + +is-document.all@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-document.all/-/is-document.all-1.0.0.tgz#163a4bfb362c6ed7b118ce46cdecc4e37dee3195" + integrity sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g== + dependencies: + call-bound "^1.0.4" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== + dependencies: + call-bound "^1.0.3" + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-generator-function@^1.0.10: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5" + integrity sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA== + dependencies: + call-bound "^1.0.4" + generator-function "^2.0.0" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + is-glob@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -729,6 +1910,24 @@ is-glob@^4.0.1: dependencies: is-extglob "^2.1.1" +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -739,16 +1938,87 @@ is-path-inside@^4.0.0: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-4.0.0.tgz#805aeb62c47c1b12fc3fd13bfb3ed1e7430071db" integrity sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA== +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + is-safe-filename@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-safe-filename/-/is-safe-filename-0.1.1.tgz#fb22eead097c614c47aa674de5d79a1648a53e66" integrity sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g== +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== + dependencies: + call-bound "^1.0.3" + +is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + is-unicode-supported@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz#09f0ab0de6d3744d48d265ebb98f65d11f2a9b3a" integrity sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2, is-weakref@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== + dependencies: + call-bound "^1.0.3" + +is-weakset@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== + dependencies: + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -768,6 +2038,18 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +js-yaml@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.2.0.tgz#2bd9e85682dd91bd469afb809d816043b3d49524" + integrity sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw== + dependencies: + argparse "^2.0.1" + +jsep@^1.2.0, jsep@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jsep/-/jsep-1.4.0.tgz#19feccbfa51d8a79f72480b4b8e40ce2e17152f0" + integrity sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw== + json-pointer@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/json-pointer/-/json-pointer-0.6.2.tgz#f97bd7550be5e9ea901f8c9264c9d436a22a93cd" @@ -796,11 +2078,30 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -jsonpointer@^5.0.1: +jsonc-parser@~2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== + +jsonpath-plus@^10.0.7, jsonpath-plus@^10.3.0, "jsonpath-plus@^6.0.1 || ^10.1.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-10.4.0.tgz#73cf545c231afda21452150b7a2a58e48e109702" + integrity sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA== + dependencies: + "@jsep-plugin/assignment" "^1.3.0" + "@jsep-plugin/regex" "^1.0.4" + jsep "^1.4.0" + +jsonpointer@^5.0.0, jsonpointer@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + leven@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-4.1.0.tgz#1e37150e1711d18bb14e380a5c779995235a710e" @@ -811,6 +2112,11 @@ liquid-json@0.3.1: resolved "https://registry.yarnpkg.com/liquid-json/-/liquid-json-0.3.1.tgz#9155a18136d8a6b2615e5f16f9a2448ab6b50eea" integrity sha512-wUayTU8MS827Dam6MxgD72Ui+KOSF+u/eIqpatOtjnvgJ0+mnDq33uC2M7J0tPK+upe/DpUAuK4JUU89iBoNKQ== +lodash.topath@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009" + integrity sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg== + lodash@4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -821,11 +2127,28 @@ lodash@4.17.23: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== -lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.4: +lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.18.1: version "4.18.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== +loupe@^3.1.0, loupe@^3.1.2: + version "3.2.1" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.2.1.tgz#0095cf56dc5b7a9a7c08ff5b1a8796ec8ad17e76" + integrity sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ== + +magic-string@^0.30.12: + version "0.30.21" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -851,6 +2174,13 @@ mime@3.0.0: resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== +minimatch@^3.1.4: + version "3.1.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== + dependencies: + brace-expansion "^1.1.7" + minipass@^7.0.4, minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" @@ -863,6 +2193,11 @@ minizlib@^3.1.0: dependencies: minipass "^7.1.2" +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + mustache@~4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" @@ -873,11 +2208,29 @@ mute-stream@^3.0.0: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-3.0.0.tgz#cd8014dd2acb72e1e91bb67c74f0019e620ba2d1" integrity sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw== +nanoid@^3.3.12: + version "3.3.12" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.12.tgz#ab3d912e217a6d0a514f00a72a16543a28982c05" + integrity sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ== + neotraverse@0.6.15: version "0.6.15" resolved "https://registry.yarnpkg.com/neotraverse/-/neotraverse-0.6.15.tgz#dc4abb64700c52440f13bc53635b559862420360" integrity sha512-HZpdkco+JeXq0G+WWpMJ4NsX3pqb5O7eR9uGz3FfoFt+LYzU8iRWp49nJtud6hsDoywM8tIrDo3gjgmOqJA8LA== +nimma@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/nimma/-/nimma-0.2.3.tgz#33cd6244ede857d9c8ac45b9d1aad07091559e45" + integrity sha512-1ZOI8J+1PKKGceo/5CT5GfQOG6H8I2BencSK06YarZ2wXwH37BSSUWldqJmMJYA5JfqDqffxDXynt6f11AyKcA== + dependencies: + "@jsep-plugin/regex" "^1.0.1" + "@jsep-plugin/ternary" "^1.0.2" + astring "^1.8.1" + jsep "^1.2.0" + optionalDependencies: + jsonpath-plus "^6.0.1 || ^10.1.0" + lodash.topath "^4.5.2" + node-fetch-h2@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz#c6188325f9bd3d834020bf0f2d6dc17ced2241ac" @@ -885,7 +2238,14 @@ node-fetch-h2@^2.3.0: dependencies: http2-client "^1.2.5" -node-fetch@^2.6.1: +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -962,6 +2322,28 @@ object-hash@3.0.0: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== +object-inspect@^1.13.3, object-inspect@^1.13.4: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" + openapi-to-postmanv2@6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/openapi-to-postmanv2/-/openapi-to-postmanv2-6.0.1.tgz#ce14def40b16cd02f5656445cfe7d039f761a602" @@ -991,16 +2373,35 @@ openapi-types@^12.1.3: resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3" integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== + dependencies: + get-intrinsic "^1.2.6" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" + path-browserify@1.0.1, path-browserify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== +pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + pathe@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== +pathval@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.1.tgz#8855c5a2899af072d6ac05d11e46045ad0dc605d" + integrity sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ== + picocolors@^1.1.1, picocolors@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -1011,6 +2412,25 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pony-cause@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pony-cause/-/pony-cause-1.1.1.tgz#f795524f83bebbf1878bd3587b45f69143cbf3f9" + integrity sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g== + +possible-typed-array-names@^1.0.0, possible-typed-array-names@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== + +postcss@^8.4.43: + version "8.5.15" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.15.tgz#d1eaf677a324e9ec02196da2d3fecf4a0b9a735c" + integrity sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A== + dependencies: + nanoid "^3.3.12" + picocolors "^1.1.1" + source-map-js "^1.2.1" + postman-collection@^5.0.0: version "5.3.0" resolved "https://registry.yarnpkg.com/postman-collection/-/postman-collection-5.3.0.tgz#9b74ff3e2971ee3bf109a06bfe3c4d069176fbe5" @@ -1050,11 +2470,37 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +reflect.getprototypeof@^1.0.10, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" + reftools@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/reftools/-/reftools-1.1.9.tgz#e16e19f662ccd4648605312c06d34e5da3a2b77e" integrity sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w== +regexp.prototype.flags@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" + set-function-name "^2.0.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -1070,6 +2516,40 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== +rollup@^4.20.0: + version "4.62.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.62.0.tgz#f68956c966f3c4a51dafbafc5d5388553244191b" + integrity sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA== + dependencies: + "@types/estree" "1.0.9" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.62.0" + "@rollup/rollup-android-arm64" "4.62.0" + "@rollup/rollup-darwin-arm64" "4.62.0" + "@rollup/rollup-darwin-x64" "4.62.0" + "@rollup/rollup-freebsd-arm64" "4.62.0" + "@rollup/rollup-freebsd-x64" "4.62.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.62.0" + "@rollup/rollup-linux-arm-musleabihf" "4.62.0" + "@rollup/rollup-linux-arm64-gnu" "4.62.0" + "@rollup/rollup-linux-arm64-musl" "4.62.0" + "@rollup/rollup-linux-loong64-gnu" "4.62.0" + "@rollup/rollup-linux-loong64-musl" "4.62.0" + "@rollup/rollup-linux-ppc64-gnu" "4.62.0" + "@rollup/rollup-linux-ppc64-musl" "4.62.0" + "@rollup/rollup-linux-riscv64-gnu" "4.62.0" + "@rollup/rollup-linux-riscv64-musl" "4.62.0" + "@rollup/rollup-linux-s390x-gnu" "4.62.0" + "@rollup/rollup-linux-x64-gnu" "4.62.0" + "@rollup/rollup-linux-x64-musl" "4.62.0" + "@rollup/rollup-openbsd-x64" "4.62.0" + "@rollup/rollup-openharmony-arm64" "4.62.0" + "@rollup/rollup-win32-arm64-msvc" "4.62.0" + "@rollup/rollup-win32-ia32-msvc" "4.62.0" + "@rollup/rollup-win32-x64-gnu" "4.62.0" + "@rollup/rollup-win32-x64-msvc" "4.62.0" + fsevents "~2.3.2" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -1077,6 +2557,39 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +safe-array-concat@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.4.tgz#a54cc9b61a57f33b42abad3cbdda3a2b38cc5719" + integrity sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg== + dependencies: + call-bind "^1.0.9" + call-bound "^1.0.4" + get-intrinsic "^1.3.0" + has-symbols "^1.1.0" + isarray "^2.0.5" + +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== + dependencies: + es-errors "^1.3.0" + isarray "^2.0.5" + +safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" + +safe-stable-stringify@^1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz#c8a220ab525cd94e60ebf47ddc404d610dc5d84a" + integrity sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw== + "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -1092,6 +2605,37 @@ semver@^7.7.4: resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.0.tgz#ed0661039fcbcda2ce71f01fa6adbefaa77040df" integrity sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA== +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + should-equal@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" @@ -1136,6 +2680,51 @@ should@^13.2.1: should-type-adaptors "^1.0.1" should-util "^1.0.0" +side-channel-list@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127" + integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.1.tgz#ea02c62e05dc4bea67d4442f0fb71ee192f8e0ab" + integrity sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + side-channel-list "^1.0.1" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + signal-exit@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" @@ -1146,6 +2735,29 @@ slash@^5.1.0: resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce" integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg== +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^3.8.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.10.0.tgz#d810b27e3a073047b2b5e40034881f5ea6f9c83b" + integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== + +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" + string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -1164,6 +2776,39 @@ string-width@^7.0.0, string-width@^7.2.0: get-east-asian-width "^1.0.0" strip-ansi "^7.1.0" +string.prototype.trim@^1.2.10: + version "1.2.11" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz#e6bd19cda3985d05a42dda31f3ddf4d35d3430e3" + integrity sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w== + dependencies: + call-bind "^1.0.9" + call-bound "^1.0.4" + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.24.2" + es-object-atoms "^1.1.2" + has-property-descriptors "^1.0.2" + safe-regex-test "^1.1.0" + +string.prototype.trimend@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz#be6bcf4f3fe0460bdeccdb2cf4f971b310f8346e" + integrity sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw== + dependencies: + call-bind "^1.0.9" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-object-atoms "^1.1.2" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -1218,6 +2863,31 @@ temporal-spec@0.3.1: resolved "https://registry.yarnpkg.com/temporal-spec/-/temporal-spec-0.3.1.tgz#0882cf2954aac683a2484208b5f5cc7366bfdd14" integrity sha512-B4TUhezh9knfSIMwt7RVggApDRJZo73uZdj8AacL2mZ8RP5KtLianh2MXxL06GN9ESYiIsiuoLQhgVfwe55Yhw== +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +tinypool@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.1.1.tgz#059f2d042bd37567fbc017d3d426bdd2a2612591" + integrity sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg== + +tinyrainbow@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" + integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== + +tinyspy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -1230,16 +2900,96 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +tslib@^1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.2.0, tslib@^2.6.0, tslib@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" + +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== + dependencies: + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" + +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" + +typed-array-length@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.8.tgz#0b70e982c9e9dafe2def6d6458ff4b3f2d2b6d70" + integrity sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g== + dependencies: + call-bind "^1.0.9" + for-each "^0.3.5" + gopd "^1.2.0" + is-typed-array "^1.1.15" + possible-typed-array-names "^1.1.0" + reflect.getprototypeof "^1.0.10" + typescript@^5.5.4: version "5.9.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== + dependencies: + call-bound "^1.0.3" + has-bigints "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" + +"undici-types@>=7.24.0 <7.24.7": + version "7.24.6" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.24.6.tgz#61275b485d7fd4e9d269c7cf04ec2873c9cc0f91" + integrity sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg== + unicorn-magic@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.4.0.tgz#78c6a090fd6d07abd2468b83b385603e00dfdb24" integrity sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw== +urijs@^1.19.11: + version "1.19.11" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.11.tgz#204b0d6b605ae80bea54bea39280cdb7c9f923cc" + integrity sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ== + +utility-types@^3.10.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.11.0.tgz#607c40edb4f258915e901ea7995607fdf319424c" + integrity sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw== + uuid@8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" @@ -1275,6 +3025,54 @@ validate.io-number@^1.0.3: resolved "https://registry.yarnpkg.com/validate.io-number/-/validate.io-number-1.0.3.tgz#f63ffeda248bf28a67a8d48e0e3b461a1665baf8" integrity sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg== +vite-node@2.1.9: + version "2.1.9" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.9.tgz#549710f76a643f1c39ef34bdb5493a944e4f895f" + integrity sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA== + dependencies: + cac "^6.7.14" + debug "^4.3.7" + es-module-lexer "^1.5.4" + pathe "^1.1.2" + vite "^5.0.0" + +vite@^5.0.0: + version "5.4.21" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.21.tgz#84a4f7c5d860b071676d39ba513c0d598fdc7027" + integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^2.0.0: + version "2.1.9" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.9.tgz#7d01ffd07a553a51c87170b5e80fea3da7fb41e7" + integrity sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q== + dependencies: + "@vitest/expect" "2.1.9" + "@vitest/mocker" "2.1.9" + "@vitest/pretty-format" "^2.1.9" + "@vitest/runner" "2.1.9" + "@vitest/snapshot" "2.1.9" + "@vitest/spy" "2.1.9" + "@vitest/utils" "2.1.9" + chai "^5.1.2" + debug "^4.3.7" + expect-type "^1.1.0" + magic-string "^0.30.12" + pathe "^1.1.2" + std-env "^3.8.0" + tinybench "^2.9.0" + tinyexec "^0.3.1" + tinypool "^1.0.1" + tinyrainbow "^1.2.0" + vite "^5.0.0" + vite-node "2.1.9" + why-is-node-running "^2.3.0" + vscode-jsonrpc@8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9" @@ -1318,6 +3116,67 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== + dependencies: + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" + +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.2.1" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" + +which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.22" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.22.tgz#8f3cc78aefb40b437346dd40a1dbfa5d1da43fe9" + integrity sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.9" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -1356,6 +3215,11 @@ yaml@^1.10.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.3.tgz#76e407ed95c42684fb8e14641e5de62fe65bbcb3" integrity sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA== +yaml@^2.3.1: + version "2.9.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.9.0.tgz#78274afd93598a1dfdd6130df6a566defcbf9aa4" + integrity sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA== + yaml@^2.8.0, yaml@~2.8.2: version "2.8.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.2.tgz#5694f25eca0ce9c3e7a9d9e00ce0ddabbd9e35c5" From 9f2d8a9500c39b2a44870077104d5e8f80121032 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 14:15:01 -0400 Subject: [PATCH 02/25] feat(asyncapi): add library definition, options, and test harness --- .../emitters/typespec-asyncapi/src/emitter.ts | 7 +++ specs/emitters/typespec-asyncapi/src/index.ts | 4 ++ specs/emitters/typespec-asyncapi/src/lib.ts | 50 +++++++++++++++++++ .../typespec-asyncapi/src/tsp-index.ts | 6 +++ specs/emitters/typespec-asyncapi/test/host.ts | 21 ++++++++ 5 files changed, 88 insertions(+) create mode 100644 specs/emitters/typespec-asyncapi/src/emitter.ts create mode 100644 specs/emitters/typespec-asyncapi/src/index.ts create mode 100644 specs/emitters/typespec-asyncapi/src/lib.ts create mode 100644 specs/emitters/typespec-asyncapi/src/tsp-index.ts create mode 100644 specs/emitters/typespec-asyncapi/test/host.ts diff --git a/specs/emitters/typespec-asyncapi/src/emitter.ts b/specs/emitters/typespec-asyncapi/src/emitter.ts new file mode 100644 index 0000000000..8ae85c59df --- /dev/null +++ b/specs/emitters/typespec-asyncapi/src/emitter.ts @@ -0,0 +1,7 @@ +import { EmitContext } from "@typespec/compiler"; +import { AsyncAPIEmitterOptions } from "./lib.js"; + +export async function $onEmit(context: EmitContext): Promise { + // Implemented in Task 8-10. + void context; +} diff --git a/specs/emitters/typespec-asyncapi/src/index.ts b/specs/emitters/typespec-asyncapi/src/index.ts new file mode 100644 index 0000000000..ea4250fcdc --- /dev/null +++ b/specs/emitters/typespec-asyncapi/src/index.ts @@ -0,0 +1,4 @@ +export { $lib } from "./lib.js"; +export { $onEmit } from "./emitter.js"; +export { $decorators } from "./tsp-index.js"; +export type { AsyncAPIEmitterOptions } from "./lib.js"; diff --git a/specs/emitters/typespec-asyncapi/src/lib.ts b/specs/emitters/typespec-asyncapi/src/lib.ts new file mode 100644 index 0000000000..316145fd45 --- /dev/null +++ b/specs/emitters/typespec-asyncapi/src/lib.ts @@ -0,0 +1,50 @@ +import { createTypeSpecLibrary, JSONSchemaType } from "@typespec/compiler"; + +export interface AsyncAPIEmitterOptions { + /** Output file name. Default: `asyncapi.yaml`. */ + "output-file"?: string; +} + +const EmitterOptionsSchema: JSONSchemaType = { + type: "object", + additionalProperties: false, + properties: { + "output-file": { type: "string", nullable: true }, + }, + required: [], +}; + +export const $lib = createTypeSpecLibrary({ + name: "@signalwire/typespec-asyncapi", + diagnostics: { + "missing-server": { + severity: "error", + messages: { + default: "AsyncAPI output requires a @server on the service namespace.", + }, + }, + "rpc-method-on-non-op": { + severity: "error", + messages: { + default: "@rpcMethod can only be applied to an operation.", + }, + }, + "missing-channel": { + severity: "error", + messages: { + default: "An @rpcMethod operation must be under a namespace marked with @channel.", + }, + }, + }, + state: { + server: { description: "State for @server" }, + channel: { description: "State for @channel" }, + rpcMethod: { description: "State for @rpcMethod" }, + event: { description: "State for @event" }, + }, + emitter: { + options: EmitterOptionsSchema, + }, +}); + +export const { reportDiagnostic, createDiagnostic, stateKeys } = $lib; diff --git a/specs/emitters/typespec-asyncapi/src/tsp-index.ts b/specs/emitters/typespec-asyncapi/src/tsp-index.ts new file mode 100644 index 0000000000..c0b95a944b --- /dev/null +++ b/specs/emitters/typespec-asyncapi/src/tsp-index.ts @@ -0,0 +1,6 @@ +export { $lib } from "./lib.js"; + +/** @internal */ +export const $decorators = { + "SignalWire.AsyncAPI": {}, +}; diff --git a/specs/emitters/typespec-asyncapi/test/host.ts b/specs/emitters/typespec-asyncapi/test/host.ts new file mode 100644 index 0000000000..c6dcc4195b --- /dev/null +++ b/specs/emitters/typespec-asyncapi/test/host.ts @@ -0,0 +1,21 @@ +import { resolvePath } from "@typespec/compiler"; +import { createTester } from "@typespec/compiler/testing"; +import { parse } from "yaml"; + +export const Tester = createTester(resolvePath(import.meta.dirname, ".."), { + libraries: ["@signalwire/typespec-asyncapi"], +}) + .using("AsyncAPI") + .emit("@signalwire/typespec-asyncapi"); + +/** Compile relay tsp and return the emitted AsyncAPI document (parsed) + raw yaml. */ +export async function asyncApiFor(code: string): Promise<{ doc: any; yaml: string }> { + const outPath = "{emitter-output-dir}/asyncapi.yaml"; + const { outputs } = await Tester.compile(code, { + compilerOptions: { + options: { "@signalwire/typespec-asyncapi": { "output-file": outPath } }, + }, + }); + const yaml = outputs["asyncapi.yaml"]; + return { doc: parse(yaml), yaml }; +} From 36474281f6a36318822549b709af4b1ebde12464 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 14:18:04 -0400 Subject: [PATCH 03/25] feat(asyncapi): add document types, decorators, and server emission Also fixes test harness: import the library and use the full SignalWire.AsyncAPI namespace path so decorators resolve. --- specs/emitters/typespec-asyncapi/lib/main.tsp | 24 +++++++ .../typespec-asyncapi/src/decorators.ts | 51 ++++++++++++++ .../emitters/typespec-asyncapi/src/emitter.ts | 36 +++++++++- .../typespec-asyncapi/src/tsp-index.ts | 9 ++- specs/emitters/typespec-asyncapi/src/types.ts | 67 +++++++++++++++++++ specs/emitters/typespec-asyncapi/test/host.ts | 3 +- .../typespec-asyncapi/test/server.test.ts | 21 ++++++ 7 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 specs/emitters/typespec-asyncapi/src/decorators.ts create mode 100644 specs/emitters/typespec-asyncapi/src/types.ts create mode 100644 specs/emitters/typespec-asyncapi/test/server.test.ts diff --git a/specs/emitters/typespec-asyncapi/lib/main.tsp b/specs/emitters/typespec-asyncapi/lib/main.tsp index 06847d5a6d..762311db55 100644 --- a/specs/emitters/typespec-asyncapi/lib/main.tsp +++ b/specs/emitters/typespec-asyncapi/lib/main.tsp @@ -1 +1,25 @@ import "../dist/src/index.js"; + +using TypeSpec.Reflection; + +namespace SignalWire.AsyncAPI; + +/** AsyncAPI server config for a service namespace. */ +model ServerOptions { + host: string; + protocol: string; + pathname?: string; + description?: string; +} + +/** Declare an AsyncAPI server on the service namespace. */ +extern dec server(target: Namespace, name: valueof string, options: valueof ServerOptions); + +/** Declare the AsyncAPI channel id for a service. */ +extern dec channel(target: Namespace, id: valueof string); + +/** Mark an operation as a JSON-RPC method with the given wire name. */ +extern dec rpcMethod(target: Operation, name: valueof string); + +/** Mark a model as a server-pushed event delivered via signalwire.event. */ +extern dec event(target: Model, eventType: valueof string); diff --git a/specs/emitters/typespec-asyncapi/src/decorators.ts b/specs/emitters/typespec-asyncapi/src/decorators.ts new file mode 100644 index 0000000000..fcc7ab5c8a --- /dev/null +++ b/specs/emitters/typespec-asyncapi/src/decorators.ts @@ -0,0 +1,51 @@ +import { DecoratorContext, Model, Namespace, Operation, Program } from "@typespec/compiler"; +import { reportDiagnostic, stateKeys } from "./lib.js"; + +export interface ServerConfig { + name: string; + host: string; + protocol: string; + pathname?: string; + description?: string; +} + +export function $server( + context: DecoratorContext, + target: Namespace, + name: string, + options: { host: string; protocol: string; pathname?: string; description?: string }, +): void { + context.program.stateMap(stateKeys.server).set(target, { name, ...options } satisfies ServerConfig); +} + +export function getServer(program: Program, target: Namespace): ServerConfig | undefined { + return program.stateMap(stateKeys.server).get(target); +} + +export function $channel(context: DecoratorContext, target: Namespace, id: string): void { + context.program.stateMap(stateKeys.channel).set(target, id); +} + +export function getChannel(program: Program, target: Namespace): string | undefined { + return program.stateMap(stateKeys.channel).get(target); +} + +export function $rpcMethod(context: DecoratorContext, target: Operation, name: string): void { + if (target.kind !== "Operation") { + reportDiagnostic(context.program, { code: "rpc-method-on-non-op", target }); + return; + } + context.program.stateMap(stateKeys.rpcMethod).set(target, name); +} + +export function getRpcMethod(program: Program, target: Operation): string | undefined { + return program.stateMap(stateKeys.rpcMethod).get(target); +} + +export function $event(context: DecoratorContext, target: Model, eventType: string): void { + context.program.stateMap(stateKeys.event).set(target, eventType); +} + +export function getEvent(program: Program, target: Model): string | undefined { + return program.stateMap(stateKeys.event).get(target); +} diff --git a/specs/emitters/typespec-asyncapi/src/emitter.ts b/specs/emitters/typespec-asyncapi/src/emitter.ts index 8ae85c59df..26bd26b6b2 100644 --- a/specs/emitters/typespec-asyncapi/src/emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/emitter.ts @@ -1,7 +1,37 @@ -import { EmitContext } from "@typespec/compiler"; +import { EmitContext, emitFile, getDoc, getService, Namespace, resolvePath } from "@typespec/compiler"; +import { stringify } from "yaml"; +import { getServer } from "./decorators.js"; import { AsyncAPIEmitterOptions } from "./lib.js"; +import { AsyncAPI3Document, AsyncAPIServer } from "./types.js"; export async function $onEmit(context: EmitContext): Promise { - // Implemented in Task 8-10. - void context; + if (context.program.compilerOptions.noEmit) return; + const program = context.program; + + const doc: AsyncAPI3Document = { asyncapi: "3.0.0", info: { title: "", version: "1.0.0" } }; + const servers: Record = {}; + + (function visit(ns: Namespace): void { + const svc = getService(program, ns); + if (svc) { + doc.info.title = svc.title ?? ns.name; + const d = getDoc(program, ns); + if (d) doc.info.description = d; + } + const cfg = getServer(program, ns); + if (cfg) { + servers[cfg.name] = { + host: cfg.host, + protocol: cfg.protocol, + ...(cfg.pathname ? { pathname: cfg.pathname } : {}), + ...(cfg.description ? { description: cfg.description } : {}), + }; + } + ns.namespaces.forEach(visit); + })(program.getGlobalNamespaceType()); + + if (Object.keys(servers).length) doc.servers = servers; + + const outputFile = resolvePath(context.emitterOutputDir, "asyncapi.yaml"); + await emitFile(program, { path: outputFile, content: stringify(doc) }); } diff --git a/specs/emitters/typespec-asyncapi/src/tsp-index.ts b/specs/emitters/typespec-asyncapi/src/tsp-index.ts index c0b95a944b..9625d73066 100644 --- a/specs/emitters/typespec-asyncapi/src/tsp-index.ts +++ b/specs/emitters/typespec-asyncapi/src/tsp-index.ts @@ -1,6 +1,13 @@ +import { $channel, $event, $rpcMethod, $server } from "./decorators.js"; + export { $lib } from "./lib.js"; /** @internal */ export const $decorators = { - "SignalWire.AsyncAPI": {}, + "SignalWire.AsyncAPI": { + server: $server, + channel: $channel, + rpcMethod: $rpcMethod, + event: $event, + }, }; diff --git a/specs/emitters/typespec-asyncapi/src/types.ts b/specs/emitters/typespec-asyncapi/src/types.ts new file mode 100644 index 0000000000..e676eaebde --- /dev/null +++ b/specs/emitters/typespec-asyncapi/src/types.ts @@ -0,0 +1,67 @@ +export interface AsyncAPI3Document { + asyncapi: "3.0.0"; + info: AsyncAPIInfo; + defaultContentType?: string; + servers?: Record; + channels?: Record; + operations?: Record; + components?: AsyncAPIComponents; +} + +export interface AsyncAPIInfo { + title: string; + version: string; + description?: string; +} + +export interface AsyncAPIServer { + host: string; + protocol: string; + pathname?: string; + description?: string; + variables?: Record; + security?: AsyncAPIRef[]; + bindings?: Record; +} + +export interface AsyncAPIChannel { + address: string | null; + title?: string; + description?: string; + servers?: AsyncAPIRef[]; + messages?: Record; + bindings?: Record; +} + +export interface AsyncAPIOperation { + action: "send" | "receive"; + channel: AsyncAPIRef; + title?: string; + summary?: string; + description?: string; + messages?: AsyncAPIRef[]; + reply?: { channel: AsyncAPIRef; messages: AsyncAPIRef[] }; + bindings?: Record; +} + +export interface AsyncAPIMessage { + name?: string; + title?: string; + summary?: string; + contentType?: string; + correlationId?: { description?: string; location: string }; + payload: SchemaObject | AsyncAPIRef; + examples?: Array<{ name?: string; summary?: string; payload: unknown }>; +} + +export interface AsyncAPIComponents { + schemas?: Record; + messages?: Record; + securitySchemes?: Record; +} + +export interface AsyncAPIRef { + $ref: string; +} + +export type SchemaObject = Record; diff --git a/specs/emitters/typespec-asyncapi/test/host.ts b/specs/emitters/typespec-asyncapi/test/host.ts index c6dcc4195b..7a2ca130ea 100644 --- a/specs/emitters/typespec-asyncapi/test/host.ts +++ b/specs/emitters/typespec-asyncapi/test/host.ts @@ -5,7 +5,8 @@ import { parse } from "yaml"; export const Tester = createTester(resolvePath(import.meta.dirname, ".."), { libraries: ["@signalwire/typespec-asyncapi"], }) - .using("AsyncAPI") + .import("@signalwire/typespec-asyncapi") + .using("SignalWire.AsyncAPI") .emit("@signalwire/typespec-asyncapi"); /** Compile relay tsp and return the emitted AsyncAPI document (parsed) + raw yaml. */ diff --git a/specs/emitters/typespec-asyncapi/test/server.test.ts b/specs/emitters/typespec-asyncapi/test/server.test.ts new file mode 100644 index 0000000000..f50e429089 --- /dev/null +++ b/specs/emitters/typespec-asyncapi/test/server.test.ts @@ -0,0 +1,21 @@ +import { strictEqual } from "assert"; +import { describe, it } from "vitest"; +import { asyncApiFor } from "./host.js"; + +describe("@server", () => { + it("emits an AsyncAPI server with host, protocol, and pathname", async () => { + const { doc } = await asyncApiFor(` + @service(#{ title: "Relay Calling" }) + @server("production", #{ host: "relay.signalwire.com", protocol: "wss", pathname: "/api/relay/wss" }) + @channel("calling") + namespace Relay.Calling { + @rpcMethod("calling.ping") op ping(): { code: string; message: string; }; + } + `); + strictEqual(doc.asyncapi, "3.0.0"); + strictEqual(doc.info.title, "Relay Calling"); + strictEqual(doc.servers.production.host, "relay.signalwire.com"); + strictEqual(doc.servers.production.protocol, "wss"); + strictEqual(doc.servers.production.pathname, "/api/relay/wss"); + }); +}); From f6019648b523d5a8db4e1f0937f4de3be060107b Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 15:08:17 -0400 Subject: [PATCH 04/25] feat(asyncapi): resolve service namespace and emit channel --- .../emitters/typespec-asyncapi/src/emitter.ts | 85 +++++++++++++------ .../typespec-asyncapi/test/rpc-method.test.ts | 22 +++++ 2 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 specs/emitters/typespec-asyncapi/test/rpc-method.test.ts diff --git a/specs/emitters/typespec-asyncapi/src/emitter.ts b/specs/emitters/typespec-asyncapi/src/emitter.ts index 26bd26b6b2..ee1d7546a4 100644 --- a/specs/emitters/typespec-asyncapi/src/emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/emitter.ts @@ -1,36 +1,69 @@ -import { EmitContext, emitFile, getDoc, getService, Namespace, resolvePath } from "@typespec/compiler"; +import { + EmitContext, + emitFile, + getDoc, + getService, + Namespace, + Program, + resolvePath, +} from "@typespec/compiler"; import { stringify } from "yaml"; -import { getServer } from "./decorators.js"; -import { AsyncAPIEmitterOptions } from "./lib.js"; -import { AsyncAPI3Document, AsyncAPIServer } from "./types.js"; +import { getChannel, getServer } from "./decorators.js"; +import { AsyncAPIEmitterOptions, reportDiagnostic } from "./lib.js"; +import { AsyncAPI3Document } from "./types.js"; + +function findServiceNamespace(program: Program): Namespace | undefined { + let found: Namespace | undefined; + (function visit(ns: Namespace): void { + if (getService(program, ns)) found ??= ns; + ns.namespaces.forEach(visit); + })(program.getGlobalNamespaceType()); + return found; +} export async function $onEmit(context: EmitContext): Promise { if (context.program.compilerOptions.noEmit) return; const program = context.program; + const ns = findServiceNamespace(program); + if (!ns) return; - const doc: AsyncAPI3Document = { asyncapi: "3.0.0", info: { title: "", version: "1.0.0" } }; - const servers: Record = {}; - - (function visit(ns: Namespace): void { - const svc = getService(program, ns); - if (svc) { - doc.info.title = svc.title ?? ns.name; - const d = getDoc(program, ns); - if (d) doc.info.description = d; - } - const cfg = getServer(program, ns); - if (cfg) { - servers[cfg.name] = { - host: cfg.host, - protocol: cfg.protocol, - ...(cfg.pathname ? { pathname: cfg.pathname } : {}), - ...(cfg.description ? { description: cfg.description } : {}), - }; - } - ns.namespaces.forEach(visit); - })(program.getGlobalNamespaceType()); + const serverCfg = getServer(program, ns); + if (!serverCfg) { + reportDiagnostic(program, { code: "missing-server", target: ns }); + return; + } + const channelId = getChannel(program, ns); + if (!channelId) { + reportDiagnostic(program, { code: "missing-channel", target: ns }); + return; + } - if (Object.keys(servers).length) doc.servers = servers; + const service = getService(program, ns)!; + const doc: AsyncAPI3Document = { + asyncapi: "3.0.0", + info: { title: service.title ?? ns.name, version: "1.0.0" }, + defaultContentType: "application/json", + servers: { + [serverCfg.name]: { + host: serverCfg.host, + protocol: serverCfg.protocol, + ...(serverCfg.pathname ? { pathname: serverCfg.pathname } : {}), + ...(serverCfg.description ? { description: serverCfg.description } : {}), + }, + }, + channels: { + [channelId]: { + address: null, + title: service.title ?? ns.name, + servers: [{ $ref: `#/servers/${serverCfg.name}` }], + messages: {}, + }, + }, + operations: {}, + components: { schemas: {}, messages: {} }, + }; + const desc = getDoc(program, ns); + if (desc) doc.info.description = desc; const outputFile = resolvePath(context.emitterOutputDir, "asyncapi.yaml"); await emitFile(program, { path: outputFile, content: stringify(doc) }); diff --git a/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts b/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts new file mode 100644 index 0000000000..ccb780605e --- /dev/null +++ b/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts @@ -0,0 +1,22 @@ +import { deepStrictEqual, strictEqual } from "assert"; +import { describe, it } from "vitest"; +import { asyncApiFor } from "./host.js"; + +export const SVC = ` + @service(#{ title: "Relay Calling" }) + @server("production", #{ host: "relay.signalwire.com", protocol: "wss" }) + @channel("calling") + namespace Relay.Calling { + model DialParams { tag: string; } + model DialResult { code: string; message: string; } + @rpcMethod("calling.dial") op dial(...DialParams): DialResult; + } +`; + +describe("@channel", () => { + it("emits a single channel with address null", async () => { + const { doc } = await asyncApiFor(SVC); + strictEqual(doc.channels.calling.address, null); + deepStrictEqual(doc.channels.calling.servers, [{ $ref: "#/servers/production" }]); + }); +}); From fc7217ca1744ebee8c905fd62f6be18f06f921ab Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 15:09:04 -0400 Subject: [PATCH 05/25] test(asyncapi): cover missing server/channel diagnostics --- .../typespec-asyncapi/test/server.test.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/specs/emitters/typespec-asyncapi/test/server.test.ts b/specs/emitters/typespec-asyncapi/test/server.test.ts index f50e429089..a3b661d203 100644 --- a/specs/emitters/typespec-asyncapi/test/server.test.ts +++ b/specs/emitters/typespec-asyncapi/test/server.test.ts @@ -1,6 +1,6 @@ import { strictEqual } from "assert"; import { describe, it } from "vitest"; -import { asyncApiFor } from "./host.js"; +import { asyncApiFor, Tester } from "./host.js"; describe("@server", () => { it("emits an AsyncAPI server with host, protocol, and pathname", async () => { @@ -19,3 +19,23 @@ describe("@server", () => { strictEqual(doc.servers.production.pathname, "/api/relay/wss"); }); }); + +describe("diagnostics", () => { + it("errors when @server is missing", async () => { + const diagnostics = await Tester.diagnose(` + @service(#{ title: "X" }) + @channel("calling") + namespace Relay.Calling { @rpcMethod("x") op x(): {}; } + `); + strictEqual(diagnostics.some((d) => d.code.endsWith("missing-server")), true); + }); + + it("errors when @channel is missing", async () => { + const diagnostics = await Tester.diagnose(` + @service(#{ title: "X" }) + @server("p", #{ host: "h", protocol: "wss" }) + namespace Relay.Calling { @rpcMethod("x") op x(): {}; } + `); + strictEqual(diagnostics.some((d) => d.code.endsWith("missing-channel")), true); + }); +}); From a60505a2704227d708a8e5588924058a7c167392 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 15:15:05 -0400 Subject: [PATCH 06/25] =?UTF-8?q?feat(asyncapi):=20Type=E2=86=92Schema=20c?= =?UTF-8?q?onverter=20with=20allOf-inheritance=20polymorphism?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../typespec-asyncapi/src/schema-emitter.ts | 180 ++++++++++++++++++ .../test/schema-emitter.test.ts | 54 ++++++ 2 files changed, 234 insertions(+) create mode 100644 specs/emitters/typespec-asyncapi/src/schema-emitter.ts create mode 100644 specs/emitters/typespec-asyncapi/test/schema-emitter.test.ts diff --git a/specs/emitters/typespec-asyncapi/src/schema-emitter.ts b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts new file mode 100644 index 0000000000..df2c047cea --- /dev/null +++ b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts @@ -0,0 +1,180 @@ +import { + getDiscriminator, + getDoc, + Model, + ModelProperty, + Program, + Scalar, + Type, +} from "@typespec/compiler"; +import { SchemaObject } from "./types.js"; + +/** Resolve a (possibly named) type to a schema — a `$ref` for named models/unions, inline otherwise. */ +export type RefFn = (type: Type) => SchemaObject; + +const SCALAR_MAP: Record = { + string: { type: "string" }, + boolean: { type: "boolean" }, + bytes: { type: "string", format: "byte" }, + int8: { type: "integer", format: "int8" }, + int16: { type: "integer", format: "int16" }, + int32: { type: "integer", format: "int32" }, + int64: { type: "integer", format: "int64" }, + integer: { type: "integer" }, + safeint: { type: "integer", format: "int64" }, + float32: { type: "number", format: "float" }, + float64: { type: "number", format: "double" }, + float: { type: "number" }, + numeric: { type: "number" }, + decimal: { type: "number" }, + url: { type: "string", format: "uri" }, + uuid: { type: "string", format: "uuid" }, + plainDate: { type: "string", format: "date" }, + plainTime: { type: "string", format: "time" }, + utcDateTime: { type: "string", format: "date-time" }, + offsetDateTime: { type: "string", format: "date-time" }, + duration: { type: "string", format: "duration" }, +}; + +function scalarSchema(scalar: Scalar): SchemaObject { + let s: Scalar | undefined = scalar; + while (s) { + if (SCALAR_MAP[s.name]) return { ...SCALAR_MAP[s.name] }; + s = s.baseScalar; + } + return { type: "string" }; +} + +/** True for types that should be emitted as a named component and `$ref`'d. */ +function isRefworthy(t: Type): boolean { + if (t.kind === "Model" && !!t.name && t.name !== "Array" && t.name !== "Record" && !(t as Model).indexer) { + return true; + } + if (t.kind === "Union" && !!t.name) return true; + return false; +} + +/** Schema for a property/element type: `$ref` if named, inline otherwise. */ +function schemaForType(program: Program, t: Type, ref: RefFn): SchemaObject { + return isRefworthy(t) ? ref(t) : typeToSchema(program, t, ref); +} + +/** Build an inline object schema from a model's OWN properties. */ +function ownObjectSchema(program: Program, model: Model, ref: RefFn): SchemaObject { + const properties: Record = {}; + const required: string[] = []; + for (const [name, prop] of model.properties as Map) { + let s = schemaForType(program, prop.type, ref); + const doc = getDoc(program, prop); + if (doc) s = { ...s, description: doc }; + properties[name] = s; + if (!prop.optional) required.push(name); + } + const schema: SchemaObject = { type: "object", properties }; + if (required.length) schema.required = required; + const doc = getDoc(program, model); + if (doc) schema.description = doc; + return schema; +} + +function unionInline(program: Program, union: { variants: Map }, ref: RefFn): SchemaObject { + const variants = [...union.variants.values()].map((v) => v.type); + if (variants.length && variants.every((v) => v.kind === "String")) { + return { type: "string", enum: variants.map((v: any) => v.value) }; + } + return { oneOf: variants.map((v) => schemaForType(program, v, ref)) }; +} + +/** Build an inline schema for a type. Named property/element types are delegated to `ref`. */ +export function typeToSchema(program: Program, type: Type, ref: RefFn): SchemaObject { + switch (type.kind) { + case "Scalar": + return scalarSchema(type); + case "String": + return { type: "string", enum: [type.value] }; + case "Number": + return { type: "number", enum: [type.value] }; + case "Boolean": + return { type: "boolean", enum: [type.value] }; + case "Model": { + const m = type as Model; + if (m.name === "Array" && m.indexer) { + return { type: "array", items: schemaForType(program, m.indexer.value, ref) }; + } + if (m.name === "Record" && m.indexer) { + return { type: "object", additionalProperties: schemaForType(program, m.indexer.value, ref) }; + } + return ownObjectSchema(program, m, ref); + } + case "Union": + return unionInline(program, type as any, ref); + case "Enum": + return { type: "string", enum: [...type.members.values()].map((mem) => mem.value ?? mem.name) }; + case "EnumMember": + return { type: "string", enum: [type.value ?? type.name] }; + default: + return {}; + } +} + +export interface SchemaRegistry { + schemas: Record; + /** Register a type if named (returns `$ref`), or return its inline schema. */ + refFor: RefFn; +} + +/** + * Creates a registry that accumulates named component schemas. Models with + * `@discriminator` are emitted as an AsyncAPI polymorphism base (string + * discriminator + required); models that `extends` a base are emitted as + * `allOf`-inheritance — the AsyncAPI-documented (and Fern-safe) form. + */ +export function createSchemaRegistry(program: Program): SchemaRegistry { + const schemas: Record = {}; + + function refFor(t: Type): SchemaObject { + if (t.kind === "Model" && !!t.name && t.name !== "Array" && t.name !== "Record" && !(t as Model).indexer) { + registerModel(t as Model); + return { $ref: `#/components/schemas/${t.name}` }; + } + if (t.kind === "Union" && !!t.name) { + if (!schemas[t.name]) schemas[t.name] = unionInline(program, t as any, refFor); + return { $ref: `#/components/schemas/${t.name}` }; + } + return typeToSchema(program, t, refFor); + } + + function registerModel(model: Model): void { + const name = model.name; + if (schemas[name]) return; + schemas[name] = {}; // cycle guard + + const disc = getDiscriminator(program, model); + if (disc) { + const base = ownObjectSchema(program, model, refFor); + base.properties = (base.properties as Record) ?? {}; + (base.properties as Record)[disc.propertyName] ??= { type: "string" }; + base.discriminator = disc.propertyName; + base.required = Array.from(new Set([...((base.required as string[]) ?? []), disc.propertyName])); + schemas[name] = base; + for (const derived of model.derivedModels) refFor(derived); + return; + } + + if (model.baseModel) { + const baseRef = refFor(model.baseModel); + const own = ownObjectSchema(program, model, refFor); + const baseDisc = getDiscriminator(program, model.baseModel)?.propertyName; + const props = own.properties as Record | undefined; + if (baseDisc && props?.[baseDisc]?.enum && (props[baseDisc].enum as unknown[]).length === 1) { + props[baseDisc] = { type: "string", const: (props[baseDisc].enum as unknown[])[0] }; + } + schemas[name] = { allOf: [baseRef, own] }; + return; + } + + schemas[name] = ownObjectSchema(program, model, refFor); + } + + return { schemas, refFor }; +} diff --git a/specs/emitters/typespec-asyncapi/test/schema-emitter.test.ts b/specs/emitters/typespec-asyncapi/test/schema-emitter.test.ts new file mode 100644 index 0000000000..ef513dd6ec --- /dev/null +++ b/specs/emitters/typespec-asyncapi/test/schema-emitter.test.ts @@ -0,0 +1,54 @@ +import { deepStrictEqual, strictEqual } from "assert"; +import { describe, it } from "vitest"; +import { createSchemaRegistry, typeToSchema } from "../src/schema-emitter.js"; +import { Tester } from "./host.js"; + +async function compileModels(def: string) { + const { program } = await Tester.compile(def); + return program; +} + +describe("typeToSchema", () => { + it("converts a simple model to a JSON-schema object", async () => { + const program = await compileModels(`model Foo { a: string; b?: int32; }`); + const Foo = program.getGlobalNamespaceType().models.get("Foo")!; + const s = typeToSchema(program, Foo, () => ({})); + deepStrictEqual(s, { + type: "object", + required: ["a"], + properties: { a: { type: "string" }, b: { type: "integer", format: "int32" } }, + }); + }); + + it("converts arrays and string-literal unions to enums", async () => { + const program = await compileModels(`model Foo { items: string[]; state: "a" | "b"; }`); + const Foo = program.getGlobalNamespaceType().models.get("Foo")!; + const s: any = typeToSchema(program, Foo, () => ({})); + deepStrictEqual(s.properties.items, { type: "array", items: { type: "string" } }); + deepStrictEqual(s.properties.state, { type: "string", enum: ["a", "b"] }); + }); +}); + +describe("createSchemaRegistry — discriminated inheritance", () => { + it("emits @discriminator base + extends variants as allOf-inheritance", async () => { + const program = await compileModels(` + @discriminator("type") model Device { type: string; } + model PhoneDevice extends Device { type: "phone"; from_number: string; } + model SipDevice extends Device { type: "sip"; from: string; } + `); + const Device = program.getGlobalNamespaceType().models.get("Device")!; + const reg = createSchemaRegistry(program); + reg.refFor(Device); + const s: any = reg.schemas; + + // base: required string discriminator + strictEqual(s.Device.discriminator, "type"); + deepStrictEqual(s.Device.required, ["type"]); + strictEqual(s.Device.properties.type.type, "string"); + + // variants: allOf-inherit the base, override the discriminator to a const + deepStrictEqual(s.PhoneDevice.allOf[0], { $ref: "#/components/schemas/Device" }); + deepStrictEqual(s.PhoneDevice.allOf[1].properties.type, { type: "string", const: "phone" }); + deepStrictEqual(s.SipDevice.allOf[1].properties.type, { type: "string", const: "sip" }); + }); +}); From ddf6887d0c7f0038aafb1725520fac99054a4a19 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 15:16:57 -0400 Subject: [PATCH 07/25] feat(asyncapi): synthesize JSON-RPC request/reply envelopes for @rpcMethod --- .../emitters/typespec-asyncapi/src/emitter.ts | 112 +++++++++++++++++- .../typespec-asyncapi/test/rpc-method.test.ts | 25 ++++ 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/specs/emitters/typespec-asyncapi/src/emitter.ts b/specs/emitters/typespec-asyncapi/src/emitter.ts index ee1d7546a4..58eb9eb296 100644 --- a/specs/emitters/typespec-asyncapi/src/emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/emitter.ts @@ -3,14 +3,17 @@ import { emitFile, getDoc, getService, + getSummary, Namespace, + Operation, Program, resolvePath, } from "@typespec/compiler"; import { stringify } from "yaml"; -import { getChannel, getServer } from "./decorators.js"; +import { getChannel, getRpcMethod, getServer } from "./decorators.js"; import { AsyncAPIEmitterOptions, reportDiagnostic } from "./lib.js"; -import { AsyncAPI3Document } from "./types.js"; +import { createSchemaRegistry, RefFn } from "./schema-emitter.js"; +import { AsyncAPI3Document, SchemaObject } from "./types.js"; function findServiceNamespace(program: Program): Namespace | undefined { let found: Namespace | undefined; @@ -21,6 +24,106 @@ function findServiceNamespace(program: Program): Namespace | undefined { return found; } +function pascal(s: string): string { + return s.replace(/(^|[._-])([a-z])/g, (_m, _sep, c) => c.toUpperCase()); +} +function lcfirst(s: string): string { + return s.charAt(0).toLowerCase() + s.slice(1); +} + +/** Schema for an operation's parameters: a `$ref` when it's a single spread model, else an inline object. */ +function paramsSchema(op: Operation, ref: RefFn): SchemaObject { + const params = op.parameters; + const spreads = params.sourceModels.filter((s) => s.usage === "spread"); + if (spreads.length === 1 && spreads[0].model.name) { + return ref(spreads[0].model); + } + const properties: Record = {}; + const required: string[] = []; + for (const [name, prop] of params.properties) { + properties[name] = ref(prop.type); + if (!prop.optional) required.push(name); + } + const schema: SchemaObject = { type: "object", properties }; + if (required.length) schema.required = required; + return schema; +} + +function emitRpcMethods( + program: Program, + ns: Namespace, + channelId: string, + doc: AsyncAPI3Document, + ref: RefFn, +): void { + const schemas = doc.components!.schemas!; + const messages = doc.components!.messages!; + const channelMessages = doc.channels![channelId].messages!; + + (function visit(n: Namespace): void { + for (const op of n.operations.values()) { + const method = getRpcMethod(program, op); + if (!method) continue; + + const baseId = pascal(method); // e.g. CallingDial + const reqMsgId = `${lcfirst(baseId)}Request`; + const resMsgId = `${lcfirst(baseId)}Response`; + + schemas[`${baseId}Request`] = { + type: "object", + required: ["jsonrpc", "id", "method", "params"], + properties: { + jsonrpc: { type: "string", const: "2.0" }, + id: { type: "string", format: "uuid" }, + method: { type: "string", const: method }, + params: paramsSchema(op, ref), + }, + }; + schemas[`${baseId}Response`] = { + type: "object", + required: ["jsonrpc", "id"], + properties: { + jsonrpc: { type: "string", const: "2.0" }, + id: { type: "string", format: "uuid" }, + result: ref(op.returnType), + }, + }; + + messages[reqMsgId] = { + name: `${method}.request`, + title: `${method} request`, + contentType: "application/json", + correlationId: { location: "$message.payload#/id" }, + payload: { $ref: `#/components/schemas/${baseId}Request` }, + }; + messages[resMsgId] = { + name: `${method}.response`, + title: `${method} response`, + contentType: "application/json", + correlationId: { location: "$message.payload#/id" }, + payload: { $ref: `#/components/schemas/${baseId}Response` }, + }; + + channelMessages[reqMsgId] = { $ref: `#/components/messages/${reqMsgId}` }; + channelMessages[resMsgId] = { $ref: `#/components/messages/${resMsgId}` }; + + const summary = getSummary(program, op); + doc.operations![lcfirst(baseId)] = { + action: "send", + channel: { $ref: `#/channels/${channelId}` }, + title: method, + ...(summary ? { summary } : {}), + messages: [{ $ref: `#/channels/${channelId}/messages/${reqMsgId}` }], + reply: { + channel: { $ref: `#/channels/${channelId}` }, + messages: [{ $ref: `#/channels/${channelId}/messages/${resMsgId}` }], + }, + }; + } + n.namespaces.forEach(visit); + })(ns); +} + export async function $onEmit(context: EmitContext): Promise { if (context.program.compilerOptions.noEmit) return; const program = context.program; @@ -38,6 +141,7 @@ export async function $onEmit(context: EmitContext): Pro return; } + const registry = createSchemaRegistry(program); const service = getService(program, ns)!; const doc: AsyncAPI3Document = { asyncapi: "3.0.0", @@ -60,11 +164,13 @@ export async function $onEmit(context: EmitContext): Pro }, }, operations: {}, - components: { schemas: {}, messages: {} }, + components: { schemas: registry.schemas, messages: {} }, }; const desc = getDoc(program, ns); if (desc) doc.info.description = desc; + emitRpcMethods(program, ns, channelId, doc, registry.refFor); + const outputFile = resolvePath(context.emitterOutputDir, "asyncapi.yaml"); await emitFile(program, { path: outputFile, content: stringify(doc) }); } diff --git a/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts b/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts index ccb780605e..74ec84f42b 100644 --- a/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts +++ b/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts @@ -20,3 +20,28 @@ describe("@channel", () => { deepStrictEqual(doc.channels.calling.servers, [{ $ref: "#/servers/production" }]); }); }); + +describe("@rpcMethod", () => { + it("synthesizes request envelope, response envelope, send op, and reply", async () => { + const { doc } = await asyncApiFor(SVC); + + const op = doc.operations.callingDial; + strictEqual(op.action, "send"); + deepStrictEqual(op.channel, { $ref: "#/channels/calling" }); + deepStrictEqual(op.messages, [{ $ref: "#/channels/calling/messages/callingDialRequest" }]); + deepStrictEqual(op.reply.messages, [ + { $ref: "#/channels/calling/messages/callingDialResponse" }, + ]); + + const reqMsg = doc.components.messages.callingDialRequest; + deepStrictEqual(reqMsg.correlationId, { location: "$message.payload#/id" }); + + const reqSchema = doc.components.schemas.CallingDialRequest; + strictEqual(reqSchema.properties.jsonrpc.const, "2.0"); + strictEqual(reqSchema.properties.method.const, "calling.dial"); + strictEqual(reqSchema.properties.params.$ref, "#/components/schemas/DialParams"); + + const resSchema = doc.components.schemas.CallingDialResponse; + strictEqual(resSchema.properties.result.$ref, "#/components/schemas/DialResult"); + }); +}); From e8a97b988bf668bf96649e8f44e2dd5973b361f8 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 15:18:01 -0400 Subject: [PATCH 08/25] feat(asyncapi): synthesize signalwire.event carrier and receive operation --- .../emitters/typespec-asyncapi/src/emitter.ts | 67 ++++++++++++++++++- .../typespec-asyncapi/test/events.test.ts | 28 ++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 specs/emitters/typespec-asyncapi/test/events.test.ts diff --git a/specs/emitters/typespec-asyncapi/src/emitter.ts b/specs/emitters/typespec-asyncapi/src/emitter.ts index 58eb9eb296..3af58bfb2d 100644 --- a/specs/emitters/typespec-asyncapi/src/emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/emitter.ts @@ -10,7 +10,7 @@ import { resolvePath, } from "@typespec/compiler"; import { stringify } from "yaml"; -import { getChannel, getRpcMethod, getServer } from "./decorators.js"; +import { getChannel, getEvent, getRpcMethod, getServer } from "./decorators.js"; import { AsyncAPIEmitterOptions, reportDiagnostic } from "./lib.js"; import { createSchemaRegistry, RefFn } from "./schema-emitter.js"; import { AsyncAPI3Document, SchemaObject } from "./types.js"; @@ -124,6 +124,70 @@ function emitRpcMethods( })(ns); } +function emitEvents( + program: Program, + ns: Namespace, + channelId: string, + doc: AsyncAPI3Document, + ref: RefFn, +): void { + const schemas = doc.components!.schemas!; + const messages = doc.components!.messages!; + const channelMessages = doc.channels![channelId].messages!; + const eventRefs: { $ref: string }[] = []; + + (function visit(n: Namespace): void { + for (const model of n.models.values()) { + const eventType = getEvent(program, model); + if (!eventType) continue; + + const frameId = `${model.name}Frame`; + schemas[frameId] = { + type: "object", + required: ["jsonrpc", "method", "id", "params"], + properties: { + jsonrpc: { type: "string", const: "2.0" }, + method: { type: "string", const: "signalwire.event" }, + id: { type: "string", format: "uuid" }, + params: { + type: "object", + required: ["event_type", "params"], + properties: { + event_type: { type: "string", const: eventType }, + event_channel: { type: "string" }, + timestamp: { type: "number" }, + space_id: { type: "string" }, + project_id: { type: "string" }, + params: ref(model), + }, + }, + }, + }; + + const msgId = lcfirst(model.name); + messages[msgId] = { + name: eventType, + title: `${eventType} event`, + contentType: "application/json", + payload: { $ref: `#/components/schemas/${frameId}` }, + }; + channelMessages[msgId] = { $ref: `#/components/messages/${msgId}` }; + eventRefs.push({ $ref: `#/channels/${channelId}/messages/${msgId}` }); + } + n.namespaces.forEach(visit); + })(ns); + + if (eventRefs.length) { + doc.operations![`on${pascal(channelId)}Event`] = { + action: "receive", + channel: { $ref: `#/channels/${channelId}` }, + title: "signalwire.event", + summary: "Asynchronous events pushed by the server over the signalwire.event carrier.", + messages: eventRefs, + }; + } +} + export async function $onEmit(context: EmitContext): Promise { if (context.program.compilerOptions.noEmit) return; const program = context.program; @@ -170,6 +234,7 @@ export async function $onEmit(context: EmitContext): Pro if (desc) doc.info.description = desc; emitRpcMethods(program, ns, channelId, doc, registry.refFor); + emitEvents(program, ns, channelId, doc, registry.refFor); const outputFile = resolvePath(context.emitterOutputDir, "asyncapi.yaml"); await emitFile(program, { path: outputFile, content: stringify(doc) }); diff --git a/specs/emitters/typespec-asyncapi/test/events.test.ts b/specs/emitters/typespec-asyncapi/test/events.test.ts new file mode 100644 index 0000000000..12fada33ec --- /dev/null +++ b/specs/emitters/typespec-asyncapi/test/events.test.ts @@ -0,0 +1,28 @@ +import { strictEqual } from "assert"; +import { describe, it } from "vitest"; +import { asyncApiFor } from "./host.js"; + +describe("@event", () => { + it("wraps event payload in the signalwire.event carrier + a receive op", async () => { + const { doc } = await asyncApiFor(` + @service(#{ title: "Relay Calling" }) + @server("production", #{ host: "relay.signalwire.com", protocol: "wss" }) + @channel("calling") + namespace Relay.Calling { + model DialResult { code: string; } + @rpcMethod("calling.dial") op dial(): DialResult; + + model CallStateParams { node_id: string; call_state: "created" | "ended"; } + @event("calling.call.state") model CallStateEvent { ...CallStateParams; } + } + `); + + const op = doc.operations.onCallingEvent; + strictEqual(op.action, "receive"); + strictEqual(op.messages[0].$ref, "#/channels/calling/messages/callStateEvent"); + + const frame = doc.components.schemas.CallStateEventFrame; + strictEqual(frame.properties.method.const, "signalwire.event"); + strictEqual(frame.properties.params.properties.event_type.const, "calling.call.state"); + }); +}); From 028016b785d2286c5521736e210ff3c203fcde73 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 15:23:32 -0400 Subject: [PATCH 09/25] feat(asyncapi): deterministic serialization + self-contained @bearerAuth security Avoids depending on @typespec/http (whose lib tsp won't load in the hoisted standalone tester); custom @bearerAuth keeps the emitter self-contained. --- specs/emitters/typespec-asyncapi/lib/main.tsp | 3 +++ .../typespec-asyncapi/src/decorators.ts | 12 +++++++++ .../emitters/typespec-asyncapi/src/emitter.ts | 19 +++++++++++--- specs/emitters/typespec-asyncapi/src/lib.ts | 1 + .../typespec-asyncapi/src/serialize.ts | 25 +++++++++++++++++++ .../typespec-asyncapi/src/tsp-index.ts | 3 ++- .../typespec-asyncapi/test/output.test.ts | 23 +++++++++++++++++ 7 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 specs/emitters/typespec-asyncapi/src/serialize.ts create mode 100644 specs/emitters/typespec-asyncapi/test/output.test.ts diff --git a/specs/emitters/typespec-asyncapi/lib/main.tsp b/specs/emitters/typespec-asyncapi/lib/main.tsp index 762311db55..0c45a14e3c 100644 --- a/specs/emitters/typespec-asyncapi/lib/main.tsp +++ b/specs/emitters/typespec-asyncapi/lib/main.tsp @@ -23,3 +23,6 @@ extern dec rpcMethod(target: Operation, name: valueof string); /** Mark a model as a server-pushed event delivered via signalwire.event. */ extern dec event(target: Model, eventType: valueof string); + +/** Declare HTTP bearer (token) authentication for the service. */ +extern dec bearerAuth(target: Namespace, bearerFormat?: valueof string); diff --git a/specs/emitters/typespec-asyncapi/src/decorators.ts b/specs/emitters/typespec-asyncapi/src/decorators.ts index fcc7ab5c8a..154a01b664 100644 --- a/specs/emitters/typespec-asyncapi/src/decorators.ts +++ b/specs/emitters/typespec-asyncapi/src/decorators.ts @@ -49,3 +49,15 @@ export function $event(context: DecoratorContext, target: Model, eventType: stri export function getEvent(program: Program, target: Model): string | undefined { return program.stateMap(stateKeys.event).get(target); } + +export interface BearerAuthConfig { + bearerFormat?: string; +} + +export function $bearerAuth(context: DecoratorContext, target: Namespace, bearerFormat?: string): void { + context.program.stateMap(stateKeys.bearerAuth).set(target, { bearerFormat } satisfies BearerAuthConfig); +} + +export function getBearerAuth(program: Program, target: Namespace): BearerAuthConfig | undefined { + return program.stateMap(stateKeys.bearerAuth).get(target); +} diff --git a/specs/emitters/typespec-asyncapi/src/emitter.ts b/specs/emitters/typespec-asyncapi/src/emitter.ts index 3af58bfb2d..f5c908ce43 100644 --- a/specs/emitters/typespec-asyncapi/src/emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/emitter.ts @@ -9,10 +9,10 @@ import { Program, resolvePath, } from "@typespec/compiler"; -import { stringify } from "yaml"; -import { getChannel, getEvent, getRpcMethod, getServer } from "./decorators.js"; +import { getBearerAuth, getChannel, getEvent, getRpcMethod, getServer } from "./decorators.js"; import { AsyncAPIEmitterOptions, reportDiagnostic } from "./lib.js"; import { createSchemaRegistry, RefFn } from "./schema-emitter.js"; +import { serialize } from "./serialize.js"; import { AsyncAPI3Document, SchemaObject } from "./types.js"; function findServiceNamespace(program: Program): Namespace | undefined { @@ -188,6 +188,18 @@ function emitEvents( } } +function emitSecurity(program: Program, ns: Namespace, serverName: string, doc: AsyncAPI3Document): void { + const auth = getBearerAuth(program, ns); + if (!auth) return; + doc.components!.securitySchemes ??= {}; + doc.components!.securitySchemes["httpBearer"] = { + type: "http", + scheme: "bearer", + ...(auth.bearerFormat ? { bearerFormat: auth.bearerFormat } : {}), + }; + doc.servers![serverName].security = [{ $ref: "#/components/securitySchemes/httpBearer" }]; +} + export async function $onEmit(context: EmitContext): Promise { if (context.program.compilerOptions.noEmit) return; const program = context.program; @@ -235,7 +247,8 @@ export async function $onEmit(context: EmitContext): Pro emitRpcMethods(program, ns, channelId, doc, registry.refFor); emitEvents(program, ns, channelId, doc, registry.refFor); + emitSecurity(program, ns, serverCfg.name, doc); const outputFile = resolvePath(context.emitterOutputDir, "asyncapi.yaml"); - await emitFile(program, { path: outputFile, content: stringify(doc) }); + await emitFile(program, { path: outputFile, content: serialize(doc) }); } diff --git a/specs/emitters/typespec-asyncapi/src/lib.ts b/specs/emitters/typespec-asyncapi/src/lib.ts index 316145fd45..5be6738922 100644 --- a/specs/emitters/typespec-asyncapi/src/lib.ts +++ b/specs/emitters/typespec-asyncapi/src/lib.ts @@ -41,6 +41,7 @@ export const $lib = createTypeSpecLibrary({ channel: { description: "State for @channel" }, rpcMethod: { description: "State for @rpcMethod" }, event: { description: "State for @event" }, + bearerAuth: { description: "State for @bearerAuth" }, }, emitter: { options: EmitterOptionsSchema, diff --git a/specs/emitters/typespec-asyncapi/src/serialize.ts b/specs/emitters/typespec-asyncapi/src/serialize.ts new file mode 100644 index 0000000000..019a537bcb --- /dev/null +++ b/specs/emitters/typespec-asyncapi/src/serialize.ts @@ -0,0 +1,25 @@ +import { stringify } from "yaml"; +import { AsyncAPI3Document } from "./types.js"; + +const TOP_ORDER = [ + "asyncapi", + "info", + "defaultContentType", + "servers", + "channels", + "operations", + "components", +]; + +function orderKeys>(obj: T, order: string[]): T { + const out: Record = {}; + for (const k of order) if (k in obj) out[k] = obj[k]; + for (const k of Object.keys(obj)) if (!(k in out)) out[k] = obj[k]; + return out as T; +} + +export function serialize(doc: AsyncAPI3Document): string { + return stringify(orderKeys(doc as unknown as Record, TOP_ORDER), { + lineWidth: 0, + }); +} diff --git a/specs/emitters/typespec-asyncapi/src/tsp-index.ts b/specs/emitters/typespec-asyncapi/src/tsp-index.ts index 9625d73066..65d1e7a85f 100644 --- a/specs/emitters/typespec-asyncapi/src/tsp-index.ts +++ b/specs/emitters/typespec-asyncapi/src/tsp-index.ts @@ -1,4 +1,4 @@ -import { $channel, $event, $rpcMethod, $server } from "./decorators.js"; +import { $bearerAuth, $channel, $event, $rpcMethod, $server } from "./decorators.js"; export { $lib } from "./lib.js"; @@ -9,5 +9,6 @@ export const $decorators = { channel: $channel, rpcMethod: $rpcMethod, event: $event, + bearerAuth: $bearerAuth, }, }; diff --git a/specs/emitters/typespec-asyncapi/test/output.test.ts b/specs/emitters/typespec-asyncapi/test/output.test.ts new file mode 100644 index 0000000000..474c789b7b --- /dev/null +++ b/specs/emitters/typespec-asyncapi/test/output.test.ts @@ -0,0 +1,23 @@ +import { strictEqual } from "assert"; +import { describe, it } from "vitest"; +import { asyncApiFor } from "./host.js"; + +describe("output", () => { + it("emits parseable yaml with a bearer security scheme via @useAuth", async () => { + const { doc, yaml } = await asyncApiFor(` + @service(#{ title: "Relay Calling" }) + @server("production", #{ host: "relay.signalwire.com", protocol: "wss" }) + @channel("calling") + @bearerAuth("JWT") + namespace Relay.Calling { + model DialResult { code: string; } + @rpcMethod("calling.dial") op dial(): DialResult; + } + `); + strictEqual(typeof yaml, "string"); + strictEqual(doc.components.securitySchemes.httpBearer.type, "http"); + strictEqual(doc.components.securitySchemes.httpBearer.scheme, "bearer"); + strictEqual(doc.components.securitySchemes.httpBearer.bearerFormat, "JWT"); + strictEqual(doc.servers.production.security[0].$ref, "#/components/securitySchemes/httpBearer"); + }); +}); From 9e01d920b09b5384e0729faf0720843964d86c92 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 15:25:19 -0400 Subject: [PATCH 10/25] test(asyncapi): conformance gate via official AsyncAPI CLI (kitchen-sink fixture) --- .../test/cli-conformance.test.ts | 36 +++++++++++++ .../test/fixtures/kitchen-sink.tsp | 53 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 specs/emitters/typespec-asyncapi/test/cli-conformance.test.ts create mode 100644 specs/emitters/typespec-asyncapi/test/fixtures/kitchen-sink.tsp diff --git a/specs/emitters/typespec-asyncapi/test/cli-conformance.test.ts b/specs/emitters/typespec-asyncapi/test/cli-conformance.test.ts new file mode 100644 index 0000000000..d28bc6b4cd --- /dev/null +++ b/specs/emitters/typespec-asyncapi/test/cli-conformance.test.ts @@ -0,0 +1,36 @@ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import { asyncApiFor } from "./host.js"; + +const CLI_VERSION = "6.0.2"; + +describe("official AsyncAPI CLI conformance", () => { + it("emitted kitchen-sink output is valid per the official AsyncAPI CLI", async () => { + const code = readFileSync( + fileURLToPath(new URL("./fixtures/kitchen-sink.tsp", import.meta.url)), + "utf8", + ); + const { yaml } = await asyncApiFor(code); + const file = join(mkdtempSync(join(tmpdir(), "asyncapi-")), "asyncapi.yaml"); + writeFileSync(file, yaml); + + let output = ""; + let status = 0; + try { + output = execFileSync("npx", ["-y", `@asyncapi/cli@${CLI_VERSION}`, "validate", file], { + encoding: "utf8", + env: { ...process.env, SUPPRESS_NO_CONFIG_WARNING: "1" }, + }); + } catch (e: any) { + status = e.status ?? 1; + output = `${e.stdout ?? ""}${e.stderr ?? ""}`; + } + if (status !== 0 || !/0 errors/.test(output)) console.error(output); + expect(status).toBe(0); + expect(output).toMatch(/0 errors/); + }, 180_000); +}); diff --git a/specs/emitters/typespec-asyncapi/test/fixtures/kitchen-sink.tsp b/specs/emitters/typespec-asyncapi/test/fixtures/kitchen-sink.tsp new file mode 100644 index 0000000000..c9accbea9a --- /dev/null +++ b/specs/emitters/typespec-asyncapi/test/fixtures/kitchen-sink.tsp @@ -0,0 +1,53 @@ +@service(#{ title: "Relay Kitchen Sink" }) +@server("production", #{ host: "relay.signalwire.com", protocol: "wss", pathname: "/api/relay/wss" }) +@channel("calling") +@bearerAuth("JWT") +namespace Relay.Calling; + +@discriminator("type") +model Device { + type: string; +} +model PhoneDevice extends Device { + type: "phone"; + params: { + from_number: string; + to_number: string; + timeout?: int32; + }; +} +model SipDevice extends Device { + type: "sip"; + params: { + from: string; + to: string; + }; +} + +model DialParams { + /** Identifier added to all call and dial events. */ + tag: string; + region?: string; + devices: Device[][]; +} +model DialResult { + code: string; + message: string; + call_id?: string; + node_id?: string; +} + +@rpcMethod("calling.dial") +@summary("Dial outbound call(s); first to answer wins") +op dial(...DialParams): DialResult; + +model CallStateParams { + node_id: string; + call_id: string; + call_state: "created" | "ringing" | "answered" | "ending" | "ended"; +} + +@event("calling.call.state") +model CallStateEvent { + ...CallStateParams; +} From 77455a0a899c4ca6e5c739a915fef003c027a181 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 15:26:16 -0400 Subject: [PATCH 11/25] feat(asyncapi): add WebSocket server/channel bindings --- .../typespec-asyncapi/src/bindings/ws.ts | 14 ++++++++++++++ specs/emitters/typespec-asyncapi/src/emitter.ts | 2 ++ .../typespec-asyncapi/test/output.test.ts | 16 +++++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 specs/emitters/typespec-asyncapi/src/bindings/ws.ts diff --git a/specs/emitters/typespec-asyncapi/src/bindings/ws.ts b/specs/emitters/typespec-asyncapi/src/bindings/ws.ts new file mode 100644 index 0000000000..817ad91bd2 --- /dev/null +++ b/specs/emitters/typespec-asyncapi/src/bindings/ws.ts @@ -0,0 +1,14 @@ +import { AsyncAPI3Document } from "../types.js"; + +/** Attach minimal WebSocket bindings when the server protocol is ws/wss. */ +export function applyWebSocketBindings( + doc: AsyncAPI3Document, + serverName: string, + channelId: string, +): void { + const server = doc.servers?.[serverName]; + if (!server || (server.protocol !== "ws" && server.protocol !== "wss")) return; + server.bindings = { ...(server.bindings ?? {}), ws: {} }; + const channel = doc.channels?.[channelId]; + if (channel) channel.bindings = { ...(channel.bindings ?? {}), ws: {} }; +} diff --git a/specs/emitters/typespec-asyncapi/src/emitter.ts b/specs/emitters/typespec-asyncapi/src/emitter.ts index f5c908ce43..3b9a3eb396 100644 --- a/specs/emitters/typespec-asyncapi/src/emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/emitter.ts @@ -9,6 +9,7 @@ import { Program, resolvePath, } from "@typespec/compiler"; +import { applyWebSocketBindings } from "./bindings/ws.js"; import { getBearerAuth, getChannel, getEvent, getRpcMethod, getServer } from "./decorators.js"; import { AsyncAPIEmitterOptions, reportDiagnostic } from "./lib.js"; import { createSchemaRegistry, RefFn } from "./schema-emitter.js"; @@ -248,6 +249,7 @@ export async function $onEmit(context: EmitContext): Pro emitRpcMethods(program, ns, channelId, doc, registry.refFor); emitEvents(program, ns, channelId, doc, registry.refFor); emitSecurity(program, ns, serverCfg.name, doc); + applyWebSocketBindings(doc, serverCfg.name, channelId); const outputFile = resolvePath(context.emitterOutputDir, "asyncapi.yaml"); await emitFile(program, { path: outputFile, content: serialize(doc) }); diff --git a/specs/emitters/typespec-asyncapi/test/output.test.ts b/specs/emitters/typespec-asyncapi/test/output.test.ts index 474c789b7b..65ecd049f7 100644 --- a/specs/emitters/typespec-asyncapi/test/output.test.ts +++ b/specs/emitters/typespec-asyncapi/test/output.test.ts @@ -1,4 +1,4 @@ -import { strictEqual } from "assert"; +import { deepStrictEqual, strictEqual } from "assert"; import { describe, it } from "vitest"; import { asyncApiFor } from "./host.js"; @@ -20,4 +20,18 @@ describe("output", () => { strictEqual(doc.components.securitySchemes.httpBearer.bearerFormat, "JWT"); strictEqual(doc.servers.production.security[0].$ref, "#/components/securitySchemes/httpBearer"); }); + + it("adds a ws binding to wss servers and the channel", async () => { + const { doc } = await asyncApiFor(` + @service(#{ title: "Relay Calling" }) + @server("production", #{ host: "relay.signalwire.com", protocol: "wss" }) + @channel("calling") + namespace Relay.Calling { + model DialResult { code: string; } + @rpcMethod("calling.dial") op dial(): DialResult; + } + `); + deepStrictEqual(doc.servers.production.bindings, { ws: {} }); + deepStrictEqual(doc.channels.calling.bindings, { ws: {} }); + }); }); From 704d6014f66ed8e125438b307d26287fbbc3bab7 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 15:26:59 -0400 Subject: [PATCH 12/25] test(asyncapi): golden snapshot for calling vertical slice --- .../test/__snapshots__/calling.yaml | 253 ++++++++++++++++++ .../typespec-asyncapi/test/golden.test.ts | 15 ++ 2 files changed, 268 insertions(+) create mode 100644 specs/emitters/typespec-asyncapi/test/__snapshots__/calling.yaml create mode 100644 specs/emitters/typespec-asyncapi/test/golden.test.ts diff --git a/specs/emitters/typespec-asyncapi/test/__snapshots__/calling.yaml b/specs/emitters/typespec-asyncapi/test/__snapshots__/calling.yaml new file mode 100644 index 0000000000..bbb5d1b027 --- /dev/null +++ b/specs/emitters/typespec-asyncapi/test/__snapshots__/calling.yaml @@ -0,0 +1,253 @@ +asyncapi: 3.0.0 +info: + title: Relay Kitchen Sink + version: 1.0.0 +defaultContentType: application/json +servers: + production: + host: relay.signalwire.com + protocol: wss + pathname: /api/relay/wss + security: + - $ref: "#/components/securitySchemes/httpBearer" + bindings: + ws: {} +channels: + calling: + address: null + title: Relay Kitchen Sink + servers: + - $ref: "#/servers/production" + messages: + callingDialRequest: + $ref: "#/components/messages/callingDialRequest" + callingDialResponse: + $ref: "#/components/messages/callingDialResponse" + callStateEvent: + $ref: "#/components/messages/callStateEvent" + bindings: + ws: {} +operations: + callingDial: + action: send + channel: + $ref: "#/channels/calling" + title: calling.dial + summary: Dial outbound call(s); first to answer wins + messages: + - $ref: "#/channels/calling/messages/callingDialRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingDialResponse" + onCallingEvent: + action: receive + channel: + $ref: "#/channels/calling" + title: signalwire.event + summary: Asynchronous events pushed by the server over the signalwire.event carrier. + messages: + - $ref: "#/channels/calling/messages/callStateEvent" +components: + schemas: + DialParams: + type: object + properties: + tag: + type: string + description: Identifier added to all call and dial events. + region: + type: string + devices: + type: array + items: + type: array + items: + $ref: "#/components/schemas/Device" + required: + - tag + - devices + Device: + type: object + properties: + type: + type: string + required: + - type + discriminator: type + PhoneDevice: + allOf: + - $ref: "#/components/schemas/Device" + - type: object + properties: + type: + type: string + const: phone + params: + type: object + properties: + from_number: + type: string + to_number: + type: string + timeout: + type: integer + format: int32 + required: + - from_number + - to_number + required: + - type + - params + SipDevice: + allOf: + - $ref: "#/components/schemas/Device" + - type: object + properties: + type: + type: string + const: sip + params: + type: object + properties: + from: + type: string + to: + type: string + required: + - from + - to + required: + - type + - params + CallingDialRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.dial + params: + $ref: "#/components/schemas/DialParams" + DialResult: + type: object + properties: + code: + type: string + message: + type: string + call_id: + type: string + node_id: + type: string + required: + - code + - message + CallingDialResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/DialResult" + CallStateEvent: + type: object + properties: + node_id: + type: string + call_id: + type: string + call_state: + type: string + enum: + - created + - ringing + - answered + - ending + - ended + required: + - node_id + - call_id + - call_state + CallStateEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.state + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallStateEvent" + messages: + callingDialRequest: + name: calling.dial.request + title: calling.dial request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDialRequest" + callingDialResponse: + name: calling.dial.response + title: calling.dial response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDialResponse" + callStateEvent: + name: calling.call.state + title: calling.call.state event + contentType: application/json + payload: + $ref: "#/components/schemas/CallStateEventFrame" + securitySchemes: + httpBearer: + type: http + scheme: bearer + bearerFormat: JWT diff --git a/specs/emitters/typespec-asyncapi/test/golden.test.ts b/specs/emitters/typespec-asyncapi/test/golden.test.ts new file mode 100644 index 0000000000..7d38e588e8 --- /dev/null +++ b/specs/emitters/typespec-asyncapi/test/golden.test.ts @@ -0,0 +1,15 @@ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import { asyncApiFor } from "./host.js"; + +describe("golden", () => { + it("matches the calling vertical-slice snapshot", async () => { + const code = readFileSync( + fileURLToPath(new URL("./fixtures/kitchen-sink.tsp", import.meta.url)), + "utf8", + ); + const { yaml } = await asyncApiFor(code); + await expect(yaml).toMatchFileSnapshot("./__snapshots__/calling.yaml"); + }); +}); From 79ca5c817069ba7eb1b5cbb99b5221b36fc78c39 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 15:29:21 -0400 Subject: [PATCH 13/25] feat(asyncapi): wire relay calling spec through emitter into fern/apis/relay --- fern/apis/relay/asyncapi.yaml | 253 +++++++++++++++++++++++++++++ fern/apis/relay/generators.yml | 4 + specs/package.json | 4 +- specs/relay/calling/main.tsp | 57 +++++++ specs/relay/calling/tspconfig.yaml | 6 + 5 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 fern/apis/relay/asyncapi.yaml create mode 100644 fern/apis/relay/generators.yml create mode 100644 specs/relay/calling/main.tsp create mode 100644 specs/relay/calling/tspconfig.yaml diff --git a/fern/apis/relay/asyncapi.yaml b/fern/apis/relay/asyncapi.yaml new file mode 100644 index 0000000000..98fca1efc4 --- /dev/null +++ b/fern/apis/relay/asyncapi.yaml @@ -0,0 +1,253 @@ +asyncapi: 3.0.0 +info: + title: SignalWire Relay — Calling + version: 1.0.0 +defaultContentType: application/json +servers: + production: + host: relay.signalwire.com + protocol: wss + pathname: /api/relay/wss + security: + - $ref: "#/components/securitySchemes/httpBearer" + bindings: + ws: {} +channels: + calling: + address: null + title: SignalWire Relay — Calling + servers: + - $ref: "#/servers/production" + messages: + callingDialRequest: + $ref: "#/components/messages/callingDialRequest" + callingDialResponse: + $ref: "#/components/messages/callingDialResponse" + callStateEvent: + $ref: "#/components/messages/callStateEvent" + bindings: + ws: {} +operations: + callingDial: + action: send + channel: + $ref: "#/channels/calling" + title: calling.dial + summary: Dial outbound call(s); first to answer wins + messages: + - $ref: "#/channels/calling/messages/callingDialRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingDialResponse" + onCallingEvent: + action: receive + channel: + $ref: "#/channels/calling" + title: signalwire.event + summary: Asynchronous events pushed by the server over the signalwire.event carrier. + messages: + - $ref: "#/channels/calling/messages/callStateEvent" +components: + schemas: + DialParams: + type: object + properties: + tag: + type: string + description: Identifier added to all call and dial events. + region: + type: string + devices: + type: array + items: + type: array + items: + $ref: "#/components/schemas/Device" + required: + - tag + - devices + Device: + type: object + properties: + type: + type: string + required: + - type + discriminator: type + PhoneDevice: + allOf: + - $ref: "#/components/schemas/Device" + - type: object + properties: + type: + type: string + const: phone + params: + type: object + properties: + from_number: + type: string + to_number: + type: string + timeout: + type: integer + format: int32 + required: + - from_number + - to_number + required: + - type + - params + SipDevice: + allOf: + - $ref: "#/components/schemas/Device" + - type: object + properties: + type: + type: string + const: sip + params: + type: object + properties: + from: + type: string + to: + type: string + required: + - from + - to + required: + - type + - params + CallingDialRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.dial + params: + $ref: "#/components/schemas/DialParams" + DialResult: + type: object + properties: + code: + type: string + message: + type: string + call_id: + type: string + node_id: + type: string + required: + - code + - message + CallingDialResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/DialResult" + CallStateEvent: + type: object + properties: + node_id: + type: string + call_id: + type: string + call_state: + type: string + enum: + - created + - ringing + - answered + - ending + - ended + required: + - node_id + - call_id + - call_state + CallStateEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.state + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallStateEvent" + messages: + callingDialRequest: + name: calling.dial.request + title: calling.dial request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDialRequest" + callingDialResponse: + name: calling.dial.response + title: calling.dial response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDialResponse" + callStateEvent: + name: calling.call.state + title: calling.call.state event + contentType: application/json + payload: + $ref: "#/components/schemas/CallStateEventFrame" + securitySchemes: + httpBearer: + type: http + scheme: bearer + bearerFormat: JWT diff --git a/fern/apis/relay/generators.yml b/fern/apis/relay/generators.yml new file mode 100644 index 0000000000..056389a239 --- /dev/null +++ b/fern/apis/relay/generators.yml @@ -0,0 +1,4 @@ +# yaml-language-server: $schema=https://schema.buildwithfern.dev/generators-yml.json +api: + specs: + - asyncapi: asyncapi.yaml diff --git a/specs/package.json b/specs/package.json index 52d383fec0..831d7120ae 100644 --- a/specs/package.json +++ b/specs/package.json @@ -4,9 +4,10 @@ "private": true, "scripts": { "build": "yarn build:api", - "build:all": "yarn build:api && yarn build:schema", + "build:all": "yarn build:api && yarn build:schema && yarn build:relay", "build:api": "yarn build:signalwire-rest && yarn build:compatibility-api", "build:schema": "yarn build:swml-calling && yarn build:swml-messaging", + "build:relay": "cd ./relay/calling && tsp compile . && cd ../..", "build:swml-calling": "cd ./swml/calling && tsp compile . && cd ../", "build:swml-messaging": "cd ./swml/messaging && tsp compile . && cd ../", "build:signalwire-rest": "cd ./signalwire-rest && tsp compile . && cd ../", @@ -17,6 +18,7 @@ "format:check": "tsp format --check **/*.tsp" }, "dependencies": { + "@signalwire/typespec-asyncapi": "0.0.0", "@typespec/compiler": "1.11.0", "@typespec/http": "1.11.0", "@typespec/json-schema": "1.11.0", diff --git a/specs/relay/calling/main.tsp b/specs/relay/calling/main.tsp new file mode 100644 index 0000000000..3e298915ba --- /dev/null +++ b/specs/relay/calling/main.tsp @@ -0,0 +1,57 @@ +import "@signalwire/typespec-asyncapi"; + +using SignalWire.AsyncAPI; + +@service(#{ title: "SignalWire Relay — Calling" }) +@server("production", #{ host: "relay.signalwire.com", protocol: "wss", pathname: "/api/relay/wss" }) +@channel("calling") +@bearerAuth("JWT") +namespace Relay.Calling; + +@discriminator("type") +model Device { + type: string; +} +model PhoneDevice extends Device { + type: "phone"; + params: { + from_number: string; + to_number: string; + timeout?: int32; + }; +} +model SipDevice extends Device { + type: "sip"; + params: { + from: string; + to: string; + }; +} + +model DialParams { + /** Identifier added to all call and dial events. */ + tag: string; + region?: string; + devices: Device[][]; +} +model DialResult { + code: string; + message: string; + call_id?: string; + node_id?: string; +} + +@rpcMethod("calling.dial") +@summary("Dial outbound call(s); first to answer wins") +op dial(...DialParams): DialResult; + +model CallStateParams { + node_id: string; + call_id: string; + call_state: "created" | "ringing" | "answered" | "ending" | "ended"; +} + +@event("calling.call.state") +model CallStateEvent { + ...CallStateParams; +} diff --git a/specs/relay/calling/tspconfig.yaml b/specs/relay/calling/tspconfig.yaml new file mode 100644 index 0000000000..8dd827b272 --- /dev/null +++ b/specs/relay/calling/tspconfig.yaml @@ -0,0 +1,6 @@ +emit: + - "@signalwire/typespec-asyncapi" + +options: + "@signalwire/typespec-asyncapi": + emitter-output-dir: "{cwd}/../../../fern/apis/relay" From 314c4df01de78323a747936bece417926ebc39b0 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 15:41:48 -0400 Subject: [PATCH 14/25] fix(asyncapi): infer types for enums/discriminator consts, DRY isNamedModel, inline-param docs Addresses adversarial review: numeric enum/discriminator type inference (latent invalid-schema bug), Array.isArray guard, isNamedModel helper, inline-parameter description extraction, and a multi-method/multi-event collision-coverage test. String-discriminator output is unchanged (golden snapshot + Relay artifact in sync). --- .../emitters/typespec-asyncapi/src/emitter.ts | 9 ++- .../typespec-asyncapi/src/schema-emitter.ts | 56 ++++++++++++++----- .../typespec-asyncapi/test/rpc-method.test.ts | 31 ++++++++++ 3 files changed, 79 insertions(+), 17 deletions(-) diff --git a/specs/emitters/typespec-asyncapi/src/emitter.ts b/specs/emitters/typespec-asyncapi/src/emitter.ts index 3b9a3eb396..e16b6586b6 100644 --- a/specs/emitters/typespec-asyncapi/src/emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/emitter.ts @@ -33,7 +33,7 @@ function lcfirst(s: string): string { } /** Schema for an operation's parameters: a `$ref` when it's a single spread model, else an inline object. */ -function paramsSchema(op: Operation, ref: RefFn): SchemaObject { +function paramsSchema(program: Program, op: Operation, ref: RefFn): SchemaObject { const params = op.parameters; const spreads = params.sourceModels.filter((s) => s.usage === "spread"); if (spreads.length === 1 && spreads[0].model.name) { @@ -42,7 +42,10 @@ function paramsSchema(op: Operation, ref: RefFn): SchemaObject { const properties: Record = {}; const required: string[] = []; for (const [name, prop] of params.properties) { - properties[name] = ref(prop.type); + let s = ref(prop.type); + const doc = getDoc(program, prop); + if (doc) s = { ...s, description: doc }; + properties[name] = s; if (!prop.optional) required.push(name); } const schema: SchemaObject = { type: "object", properties }; @@ -77,7 +80,7 @@ function emitRpcMethods( jsonrpc: { type: "string", const: "2.0" }, id: { type: "string", format: "uuid" }, method: { type: "string", const: method }, - params: paramsSchema(op, ref), + params: paramsSchema(program, op, ref), }, }; schemas[`${baseId}Response`] = { diff --git a/specs/emitters/typespec-asyncapi/src/schema-emitter.ts b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts index df2c047cea..d39d20e89b 100644 --- a/specs/emitters/typespec-asyncapi/src/schema-emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts @@ -45,13 +45,33 @@ function scalarSchema(scalar: Scalar): SchemaObject { return { type: "string" }; } +/** A model emitted as a named component (excludes Array/Record/anonymous models). */ +function isNamedModel(t: Type): t is Model { + return ( + t.kind === "Model" && !!t.name && t.name !== "Array" && t.name !== "Record" && !(t as Model).indexer + ); +} + /** True for types that should be emitted as a named component and `$ref`'d. */ function isRefworthy(t: Type): boolean { - if (t.kind === "Model" && !!t.name && t.name !== "Array" && t.name !== "Record" && !(t as Model).indexer) { - return true; + return isNamedModel(t) || (t.kind === "Union" && !!t.name); +} + +/** JSON-Schema for a set of literal values, inferring `type` from the values. */ +function enumSchema(values: unknown[]): SchemaObject { + if (values.every((v) => typeof v === "string")) return { type: "string", enum: values }; + if (values.every((v) => typeof v === "number")) { + return { type: values.every((v) => Number.isInteger(v)) ? "integer" : "number", enum: values }; } - if (t.kind === "Union" && !!t.name) return true; - return false; + if (values.every((v) => typeof v === "boolean")) return { type: "boolean", enum: values }; + return { enum: values }; +} + +/** JSON-Schema for a single constant, inferring `type` from the value. */ +function constSchema(value: unknown): SchemaObject { + if (typeof value === "number") return { type: Number.isInteger(value) ? "integer" : "number", const: value }; + if (typeof value === "boolean") return { type: "boolean", const: value }; + return { type: "string", const: value }; } /** Schema for a property/element type: `$ref` if named, inline otherwise. */ @@ -59,7 +79,14 @@ function schemaForType(program: Program, t: Type, ref: RefFn): SchemaObject { return isRefworthy(t) ? ref(t) : typeToSchema(program, t, ref); } -/** Build an inline object schema from a model's OWN properties. */ +/** + * Build an inline object schema from a model's OWN properties. + * + * NOTE: JSON-Schema constraints (`@minValue`/`@maxValue`/`@minLength`/`@maxLength`/ + * `@pattern`/`@format`) and property `default`s are intentionally NOT emitted yet — + * out of scope for the current Relay slice. Adding them (via the compiler's + * get* helpers + `ModelProperty.defaultValue`) is a documented follow-up. + */ function ownObjectSchema(program: Program, model: Model, ref: RefFn): SchemaObject { const properties: Record = {}; const required: string[] = []; @@ -91,11 +118,9 @@ export function typeToSchema(program: Program, type: Type, ref: RefFn): SchemaOb case "Scalar": return scalarSchema(type); case "String": - return { type: "string", enum: [type.value] }; case "Number": - return { type: "number", enum: [type.value] }; case "Boolean": - return { type: "boolean", enum: [type.value] }; + return enumSchema([type.value]); case "Model": { const m = type as Model; if (m.name === "Array" && m.indexer) { @@ -109,9 +134,9 @@ export function typeToSchema(program: Program, type: Type, ref: RefFn): SchemaOb case "Union": return unionInline(program, type as any, ref); case "Enum": - return { type: "string", enum: [...type.members.values()].map((mem) => mem.value ?? mem.name) }; + return enumSchema([...type.members.values()].map((mem) => mem.value ?? mem.name)); case "EnumMember": - return { type: "string", enum: [type.value ?? type.name] }; + return enumSchema([type.value ?? type.name]); default: return {}; } @@ -133,8 +158,8 @@ export function createSchemaRegistry(program: Program): SchemaRegistry { const schemas: Record = {}; function refFor(t: Type): SchemaObject { - if (t.kind === "Model" && !!t.name && t.name !== "Array" && t.name !== "Record" && !(t as Model).indexer) { - registerModel(t as Model); + if (isNamedModel(t)) { + registerModel(t); return { $ref: `#/components/schemas/${t.name}` }; } if (t.kind === "Union" && !!t.name) { @@ -166,8 +191,11 @@ export function createSchemaRegistry(program: Program): SchemaRegistry { const own = ownObjectSchema(program, model, refFor); const baseDisc = getDiscriminator(program, model.baseModel)?.propertyName; const props = own.properties as Record | undefined; - if (baseDisc && props?.[baseDisc]?.enum && (props[baseDisc].enum as unknown[]).length === 1) { - props[baseDisc] = { type: "string", const: (props[baseDisc].enum as unknown[])[0] }; + const discEnum = baseDisc ? props?.[baseDisc]?.enum : undefined; + if (baseDisc && props && Array.isArray(discEnum) && discEnum.length === 1) { + // Override the inherited discriminator with the variant's literal value, + // inferring the JSON-Schema type from the value (string/number/boolean). + props[baseDisc] = constSchema(discEnum[0]); } schemas[name] = { allOf: [baseRef, own] }; return; diff --git a/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts b/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts index 74ec84f42b..290040bec2 100644 --- a/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts +++ b/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts @@ -44,4 +44,35 @@ describe("@rpcMethod", () => { const resSchema = doc.components.schemas.CallingDialResponse; strictEqual(resSchema.properties.result.$ref, "#/components/schemas/DialResult"); }); + + it("emits distinct operations/schemas/messages for multiple methods and one receive op for all events", async () => { + const { doc } = await asyncApiFor(` + @service(#{ title: "Relay Calling" }) + @server("production", #{ host: "relay.signalwire.com", protocol: "wss" }) + @channel("calling") + namespace Relay.Calling { + model DialResult { code: string; } + model AnswerResult { code: string; } + @rpcMethod("calling.dial") op dial(): DialResult; + @rpcMethod("calling.answer") op answer(): AnswerResult; + + model StateParams { call_state: string; } + model ReferParams { sip_refer_to: string; } + @event("calling.call.state") model CallStateEvent { ...StateParams; } + @event("calling.call.refer") model CallReferEvent { ...ReferParams; } + } + `); + + // distinct send operations per method + strictEqual(doc.operations.callingDial.action, "send"); + strictEqual(doc.operations.callingAnswer.action, "send"); + // distinct request schemas per method (no collision) + strictEqual(typeof doc.components.schemas.CallingDialRequest, "object"); + strictEqual(typeof doc.components.schemas.CallingAnswerRequest, "object"); + // 2 messages per method (req+resp) + 1 per event = 6 channel messages, none overwritten + strictEqual(Object.keys(doc.channels.calling.messages).length, 6); + // a single receive op carrying both events + strictEqual(doc.operations.onCallingEvent.action, "receive"); + strictEqual(doc.operations.onCallingEvent.messages.length, 2); + }); }); From 1afb0b651b8acc57c6639371c423620e4d73b318 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 17:08:54 -0400 Subject: [PATCH 15/25] feat(asyncapi): emit JSON-Schema constraints, defaults, and examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @minValue/@maxValue/exclusive, @minLength/@maxLength, @minItems/@maxItems, @pattern, @format, @secret→format:password, property defaults, and @example now map to the corresponding JSON-Schema keywords (valid per AsyncAPI 3.0's Draft-07 superset). Relay timeout now models its non-negative/default-30 semantics. --- fern/apis/relay/asyncapi.yaml | 2 + .../emitters/typespec-asyncapi/src/emitter.ts | 7 +- .../typespec-asyncapi/src/schema-emitter.ts | 73 ++++++++++++++++--- .../test/__snapshots__/calling.yaml | 2 + .../test/fixtures/kitchen-sink.tsp | 2 +- .../test/schema-emitter.test.ts | 39 ++++++++++ specs/relay/calling/main.tsp | 2 +- 7 files changed, 109 insertions(+), 18 deletions(-) diff --git a/fern/apis/relay/asyncapi.yaml b/fern/apis/relay/asyncapi.yaml index 98fca1efc4..805e2822d4 100644 --- a/fern/apis/relay/asyncapi.yaml +++ b/fern/apis/relay/asyncapi.yaml @@ -94,6 +94,8 @@ components: timeout: type: integer format: int32 + minimum: 0 + default: 30 required: - from_number - to_number diff --git a/specs/emitters/typespec-asyncapi/src/emitter.ts b/specs/emitters/typespec-asyncapi/src/emitter.ts index e16b6586b6..7247c3109d 100644 --- a/specs/emitters/typespec-asyncapi/src/emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/emitter.ts @@ -12,7 +12,7 @@ import { import { applyWebSocketBindings } from "./bindings/ws.js"; import { getBearerAuth, getChannel, getEvent, getRpcMethod, getServer } from "./decorators.js"; import { AsyncAPIEmitterOptions, reportDiagnostic } from "./lib.js"; -import { createSchemaRegistry, RefFn } from "./schema-emitter.js"; +import { createSchemaRegistry, propertySchema, RefFn } from "./schema-emitter.js"; import { serialize } from "./serialize.js"; import { AsyncAPI3Document, SchemaObject } from "./types.js"; @@ -42,10 +42,7 @@ function paramsSchema(program: Program, op: Operation, ref: RefFn): SchemaObject const properties: Record = {}; const required: string[] = []; for (const [name, prop] of params.properties) { - let s = ref(prop.type); - const doc = getDoc(program, prop); - if (doc) s = { ...s, description: doc }; - properties[name] = s; + properties[name] = propertySchema(program, prop, ref); if (!prop.optional) required.push(name); } const schema: SchemaObject = { type: "object", properties }; diff --git a/specs/emitters/typespec-asyncapi/src/schema-emitter.ts b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts index d39d20e89b..0decccf2da 100644 --- a/specs/emitters/typespec-asyncapi/src/schema-emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts @@ -1,10 +1,23 @@ import { getDiscriminator, getDoc, + getExamples, + getFormat, + getMaxItems, + getMaxLength, + getMaxValue, + getMaxValueExclusive, + getMinItems, + getMinLength, + getMinValue, + getMinValueExclusive, + getPattern, + isSecret, Model, ModelProperty, Program, Scalar, + serializeValueAsJson, Type, } from "@typespec/compiler"; import { SchemaObject } from "./types.js"; @@ -80,21 +93,59 @@ function schemaForType(program: Program, t: Type, ref: RefFn): SchemaObject { } /** - * Build an inline object schema from a model's OWN properties. - * - * NOTE: JSON-Schema constraints (`@minValue`/`@maxValue`/`@minLength`/`@maxLength`/ - * `@pattern`/`@format`) and property `default`s are intentionally NOT emitted yet — - * out of scope for the current Relay slice. Adding them (via the compiler's - * get* helpers + `ModelProperty.defaultValue`) is a documented follow-up. + * Merge JSON-Schema validation keywords from a target's TypeSpec constraint + * decorators (`@minValue`/`@maxValue`/`@minValueExclusive`/`@maxValueExclusive`/ + * `@minLength`/`@maxLength`/`@minItems`/`@maxItems`/`@pattern`/`@format`/`@secret`). + * AsyncAPI 3.0's Schema Object is a JSON-Schema Draft-07 superset, so these are valid. */ +function applyConstraints(program: Program, target: Type, schema: SchemaObject): SchemaObject { + const out: SchemaObject = { ...schema }; + const set = (k: string, v: unknown) => { + if (v !== undefined) out[k] = v; + }; + set("minimum", getMinValue(program, target)); + set("maximum", getMaxValue(program, target)); + set("exclusiveMinimum", getMinValueExclusive(program, target)); + set("exclusiveMaximum", getMaxValueExclusive(program, target)); + set("minLength", getMinLength(program, target)); + set("maxLength", getMaxLength(program, target)); + set("minItems", getMinItems(program, target)); + set("maxItems", getMaxItems(program, target)); + set("pattern", getPattern(program, target)); + set("format", getFormat(program, target)); + if (out.format === undefined && isSecret(program, target)) out.format = "password"; + return out; +} + +/** + * Schema for a model property: resolves the type, then layers on description, + * constraints, `default`, and `examples`. Constraints/default/examples are only + * merged onto inline schemas — a `$ref` ignores sibling keywords in Draft-07. + */ +export function propertySchema(program: Program, prop: ModelProperty, ref: RefFn): SchemaObject { + let s = schemaForType(program, prop.type, ref); + const doc = getDoc(program, prop); + if (doc) s = { ...s, description: doc }; + if (!("$ref" in s)) { + s = applyConstraints(program, prop, s); + if (prop.defaultValue) { + const def = serializeValueAsJson(program, prop.defaultValue, prop.type); + if (def !== undefined) s = { ...s, default: def }; + } + const examples = getExamples(program, prop); + if (examples.length) { + s = { ...s, examples: examples.map((e) => serializeValueAsJson(program, e.value, prop.type)) }; + } + } + return s; +} + +/** Build an inline object schema from a model's OWN properties. */ function ownObjectSchema(program: Program, model: Model, ref: RefFn): SchemaObject { const properties: Record = {}; const required: string[] = []; for (const [name, prop] of model.properties as Map) { - let s = schemaForType(program, prop.type, ref); - const doc = getDoc(program, prop); - if (doc) s = { ...s, description: doc }; - properties[name] = s; + properties[name] = propertySchema(program, prop, ref); if (!prop.optional) required.push(name); } const schema: SchemaObject = { type: "object", properties }; @@ -116,7 +167,7 @@ function unionInline(program: Program, union: { variants: Map { }); }); +describe("constraints, defaults, and examples", () => { + it("emits JSON-Schema constraint keywords, default, and examples from decorators", async () => { + const program = await compileModels(` + model Foo { + @minValue(0) @maxValue(100) count?: int32 = 30; + @minLength(1) @maxLength(64) @pattern("^[a-z]+$") name: string; + @minItems(1) @maxItems(5) tags: string[]; + @secret token: string; + @format("uri") link: string; + } + `); + const Foo = program.getGlobalNamespaceType().models.get("Foo")!; + const s: any = typeToSchema(program, Foo, () => ({})); + deepStrictEqual(s.properties.count, { + type: "integer", + format: "int32", + minimum: 0, + maximum: 100, + default: 30, + }); + deepStrictEqual(s.properties.name, { + type: "string", + minLength: 1, + maxLength: 64, + pattern: "^[a-z]+$", + }); + deepStrictEqual(s.properties.tags, { + type: "array", + items: { type: "string" }, + minItems: 1, + maxItems: 5, + }); + strictEqual(s.properties.token.format, "password"); + strictEqual(s.properties.link.format, "uri"); + // count is optional (has default) → not required; name/tags/token/link required + deepStrictEqual(s.required, ["name", "tags", "token", "link"]); + }); +}); + describe("createSchemaRegistry — discriminated inheritance", () => { it("emits @discriminator base + extends variants as allOf-inheritance", async () => { const program = await compileModels(` diff --git a/specs/relay/calling/main.tsp b/specs/relay/calling/main.tsp index 3e298915ba..a1a5b481d3 100644 --- a/specs/relay/calling/main.tsp +++ b/specs/relay/calling/main.tsp @@ -17,7 +17,7 @@ model PhoneDevice extends Device { params: { from_number: string; to_number: string; - timeout?: int32; + @minValue(0) timeout?: int32 = 30; }; } model SipDevice extends Device { From 023e1aeadd813ce7128a7a994876ca7786b0a9d4 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 17:12:37 -0400 Subject: [PATCH 16/25] feat(asyncapi): handle @encode (decay to wire type + format) and @encodedName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the standard property-decorator surface: @encode decays to its wire type with a JSON-Schema format (e.g. utcDateTime+rfc3339 → string/date-time), @encodedName remaps the emitted JSON property key. No standard property decorator is silently dropped now. --- .../emitters/typespec-asyncapi/src/emitter.ts | 5 ++- .../typespec-asyncapi/src/schema-emitter.ts | 45 +++++++++++++++++-- .../test/schema-emitter.test.ts | 22 +++++++++ 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/specs/emitters/typespec-asyncapi/src/emitter.ts b/specs/emitters/typespec-asyncapi/src/emitter.ts index 7247c3109d..40de231f68 100644 --- a/specs/emitters/typespec-asyncapi/src/emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/emitter.ts @@ -12,7 +12,7 @@ import { import { applyWebSocketBindings } from "./bindings/ws.js"; import { getBearerAuth, getChannel, getEvent, getRpcMethod, getServer } from "./decorators.js"; import { AsyncAPIEmitterOptions, reportDiagnostic } from "./lib.js"; -import { createSchemaRegistry, propertySchema, RefFn } from "./schema-emitter.js"; +import { createSchemaRegistry, encodedPropName, propertySchema, RefFn } from "./schema-emitter.js"; import { serialize } from "./serialize.js"; import { AsyncAPI3Document, SchemaObject } from "./types.js"; @@ -41,7 +41,8 @@ function paramsSchema(program: Program, op: Operation, ref: RefFn): SchemaObject } const properties: Record = {}; const required: string[] = []; - for (const [name, prop] of params.properties) { + for (const prop of params.properties.values()) { + const name = encodedPropName(program, prop); properties[name] = propertySchema(program, prop, ref); if (!prop.optional) required.push(name); } diff --git a/specs/emitters/typespec-asyncapi/src/schema-emitter.ts b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts index 0decccf2da..a60009ac19 100644 --- a/specs/emitters/typespec-asyncapi/src/schema-emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts @@ -1,6 +1,7 @@ import { getDiscriminator, getDoc, + getEncode, getExamples, getFormat, getMaxItems, @@ -16,6 +17,7 @@ import { Model, ModelProperty, Program, + resolveEncodedName, Scalar, serializeValueAsJson, Type, @@ -80,6 +82,42 @@ function enumSchema(values: unknown[]): SchemaObject { return { enum: values }; } +/** Map a TypeSpec `@encode` encoding to a JSON-Schema `format` (open vocabulary). */ +function encodeFormat(encoding?: string): string | undefined { + switch (encoding) { + case "rfc3339": + return "date-time"; + case "rfc7231": + return "http-date"; + case "ISO8601": + return "duration"; + case "base64": + return "byte"; + case "base64url": + return "base64url"; + default: + // unixTimestamp / seconds / milliseconds / base10 string → wire type carries it, no format + return undefined; + } +} + +/** + * If `@encode` is present, "decay" to the wire type's schema (per the compiler's + * encoding guidance) and carry the encoding as a JSON-Schema `format` where known. + */ +function encodeSchema(program: Program, target: ModelProperty | Scalar): SchemaObject | undefined { + const enc = getEncode(program, target); + if (!enc) return undefined; + const base = scalarSchema(enc.type); + const fmt = encodeFormat(enc.encoding); + return fmt ? { ...base, format: fmt } : base; +} + +/** The JSON wire name of a property, honoring `@encodedName("application/json", ...)`. */ +export function encodedPropName(program: Program, prop: ModelProperty): string { + return resolveEncodedName(program, prop, "application/json"); +} + /** JSON-Schema for a single constant, inferring `type` from the value. */ function constSchema(value: unknown): SchemaObject { if (typeof value === "number") return { type: Number.isInteger(value) ? "integer" : "number", const: value }; @@ -123,7 +161,7 @@ function applyConstraints(program: Program, target: Type, schema: SchemaObject): * merged onto inline schemas — a `$ref` ignores sibling keywords in Draft-07. */ export function propertySchema(program: Program, prop: ModelProperty, ref: RefFn): SchemaObject { - let s = schemaForType(program, prop.type, ref); + let s = encodeSchema(program, prop) ?? schemaForType(program, prop.type, ref); const doc = getDoc(program, prop); if (doc) s = { ...s, description: doc }; if (!("$ref" in s)) { @@ -144,7 +182,8 @@ export function propertySchema(program: Program, prop: ModelProperty, ref: RefFn function ownObjectSchema(program: Program, model: Model, ref: RefFn): SchemaObject { const properties: Record = {}; const required: string[] = []; - for (const [name, prop] of model.properties as Map) { + for (const prop of (model.properties as Map).values()) { + const name = encodedPropName(program, prop); properties[name] = propertySchema(program, prop, ref); if (!prop.optional) required.push(name); } @@ -167,7 +206,7 @@ function unionInline(program: Program, union: { variants: Map { }); }); +describe("@encode and @encodedName", () => { + it("decays @encode to the wire type + format and renames via @encodedName", async () => { + const program = await compileModels(` + model Foo { + @encode("rfc3339") createdAt: utcDateTime; + @encode("seconds", int32) ttl: duration; + @encodedName("application/json", "from_number") fromNumber: string; + } + `); + const Foo = program.getGlobalNamespaceType().models.get("Foo")!; + const s: any = typeToSchema(program, Foo, () => ({})); + // utcDateTime + rfc3339 → string/date-time + deepStrictEqual(s.properties.createdAt, { type: "string", format: "date-time" }); + // duration encoded as int32 seconds → integer (no format) + deepStrictEqual(s.properties.ttl, { type: "integer", format: "int32" }); + // @encodedName remaps the JSON property key + strictEqual("from_number" in s.properties, true); + strictEqual("fromNumber" in s.properties, false); + deepStrictEqual(s.required, ["createdAt", "ttl", "from_number"]); + }); +}); + describe("createSchemaRegistry — discriminated inheritance", () => { it("emits @discriminator base + extends variants as allOf-inheritance", async () => { const program = await compileModels(` From a03d6e6b2c27e950f6a330fa98bc451d6cfe7ac1 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 17:13:51 -0400 Subject: [PATCH 17/25] feat(asyncapi): emit deprecated:true for @deprecated properties and models --- .../typespec-asyncapi/src/schema-emitter.ts | 3 +++ .../test/schema-emitter.test.ts | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/specs/emitters/typespec-asyncapi/src/schema-emitter.ts b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts index a60009ac19..7c0d80491e 100644 --- a/specs/emitters/typespec-asyncapi/src/schema-emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts @@ -13,6 +13,7 @@ import { getMinValue, getMinValueExclusive, getPattern, + isDeprecated, isSecret, Model, ModelProperty, @@ -174,6 +175,7 @@ export function propertySchema(program: Program, prop: ModelProperty, ref: RefFn if (examples.length) { s = { ...s, examples: examples.map((e) => serializeValueAsJson(program, e.value, prop.type)) }; } + if (isDeprecated(program, prop)) s = { ...s, deprecated: true }; } return s; } @@ -191,6 +193,7 @@ function ownObjectSchema(program: Program, model: Model, ref: RefFn): SchemaObje if (required.length) schema.required = required; const doc = getDoc(program, model); if (doc) schema.description = doc; + if (isDeprecated(program, model)) schema.deprecated = true; return schema; } diff --git a/specs/emitters/typespec-asyncapi/test/schema-emitter.test.ts b/specs/emitters/typespec-asyncapi/test/schema-emitter.test.ts index 9b42536e59..d82c3128bf 100644 --- a/specs/emitters/typespec-asyncapi/test/schema-emitter.test.ts +++ b/specs/emitters/typespec-asyncapi/test/schema-emitter.test.ts @@ -68,6 +68,24 @@ describe("constraints, defaults, and examples", () => { }); }); +describe("@deprecated", () => { + it("emits deprecated: true for deprecated properties and models", async () => { + const program = await compileModels(` + model Foo { + #deprecated "use newField" + oldField?: string; + } + #deprecated "use NewModel" + model OldModel { x: string; } + `); + const ns = program.getGlobalNamespaceType(); + const foo: any = typeToSchema(program, ns.models.get("Foo")!, () => ({})); + const old: any = typeToSchema(program, ns.models.get("OldModel")!, () => ({})); + strictEqual(foo.properties.oldField.deprecated, true); + strictEqual(old.deprecated, true); + }); +}); + describe("@encode and @encodedName", () => { it("decays @encode to the wire type + format and renames via @encodedName", async () => { const program = await compileModels(` From 9a4516a448c64c4feb30d132b2d326f5f6fef7f6 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 18:10:01 -0400 Subject: [PATCH 18/25] refactor(asyncapi): structured AsyncAPISchema type, typed RefFn, fewer assertions, dup-method diagnostic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the remaining adversarial-review quality findings: - Replace SchemaObject=Record with a structured AsyncAPISchema interface (correct AsyncAPI/Draft-07 dialect: numeric exclusiveMin/Max, string discriminator, no nullable) — removes the as-Record casts. Not reusing @typespec/openapi3's OpenAPI3Schema, which models a different dialect. - RefFn returns SchemaOrRef (= AsyncAPISchema | AsyncAPIRef); unionInline typed to Union (removes the as-any casts). - emitRpcMethods/emitEvents take a typed EmitTarget context instead of drilling doc!.components! — removes non-null assertions. - Add duplicate-@rpcMethod diagnostic + test. --- .../emitters/typespec-asyncapi/src/emitter.ts | 122 +++++++++----- specs/emitters/typespec-asyncapi/src/lib.ts | 8 +- .../typespec-asyncapi/src/schema-emitter.ts | 157 +++++++++--------- specs/emitters/typespec-asyncapi/src/types.ts | 48 +++++- .../typespec-asyncapi/test/server.test.ts | 14 ++ 5 files changed, 225 insertions(+), 124 deletions(-) diff --git a/specs/emitters/typespec-asyncapi/src/emitter.ts b/specs/emitters/typespec-asyncapi/src/emitter.ts index 40de231f68..8aee39abd5 100644 --- a/specs/emitters/typespec-asyncapi/src/emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/emitter.ts @@ -14,7 +14,24 @@ import { getBearerAuth, getChannel, getEvent, getRpcMethod, getServer } from "./ import { AsyncAPIEmitterOptions, reportDiagnostic } from "./lib.js"; import { createSchemaRegistry, encodedPropName, propertySchema, RefFn } from "./schema-emitter.js"; import { serialize } from "./serialize.js"; -import { AsyncAPI3Document, SchemaObject } from "./types.js"; +import { + AsyncAPI3Document, + AsyncAPIComponents, + AsyncAPIMessage, + AsyncAPIOperation, + AsyncAPIRef, + AsyncAPISchema, + AsyncAPIServer, + SchemaOrRef, +} from "./types.js"; + +/** The mutable component/operation maps the channel emitters write into. */ +interface EmitTarget { + schemas: Record; + messages: Record; + channelMessages: Record; + operations: Record; +} function findServiceNamespace(program: Program): Namespace | undefined { let found: Namespace | undefined; @@ -33,20 +50,20 @@ function lcfirst(s: string): string { } /** Schema for an operation's parameters: a `$ref` when it's a single spread model, else an inline object. */ -function paramsSchema(program: Program, op: Operation, ref: RefFn): SchemaObject { +function paramsSchema(program: Program, op: Operation, ref: RefFn): SchemaOrRef { const params = op.parameters; const spreads = params.sourceModels.filter((s) => s.usage === "spread"); if (spreads.length === 1 && spreads[0].model.name) { return ref(spreads[0].model); } - const properties: Record = {}; + const properties: Record = {}; const required: string[] = []; for (const prop of params.properties.values()) { const name = encodedPropName(program, prop); properties[name] = propertySchema(program, prop, ref); if (!prop.optional) required.push(name); } - const schema: SchemaObject = { type: "object", properties }; + const schema: AsyncAPISchema = { type: "object", properties }; if (required.length) schema.required = required; return schema; } @@ -55,23 +72,25 @@ function emitRpcMethods( program: Program, ns: Namespace, channelId: string, - doc: AsyncAPI3Document, ref: RefFn, + target: EmitTarget, ): void { - const schemas = doc.components!.schemas!; - const messages = doc.components!.messages!; - const channelMessages = doc.channels![channelId].messages!; - + const seen = new Set(); (function visit(n: Namespace): void { for (const op of n.operations.values()) { const method = getRpcMethod(program, op); if (!method) continue; + if (seen.has(method)) { + reportDiagnostic(program, { code: "duplicate-rpc-method", target: op, format: { method } }); + continue; + } + seen.add(method); const baseId = pascal(method); // e.g. CallingDial const reqMsgId = `${lcfirst(baseId)}Request`; const resMsgId = `${lcfirst(baseId)}Response`; - schemas[`${baseId}Request`] = { + target.schemas[`${baseId}Request`] = { type: "object", required: ["jsonrpc", "id", "method", "params"], properties: { @@ -81,7 +100,7 @@ function emitRpcMethods( params: paramsSchema(program, op, ref), }, }; - schemas[`${baseId}Response`] = { + target.schemas[`${baseId}Response`] = { type: "object", required: ["jsonrpc", "id"], properties: { @@ -91,14 +110,14 @@ function emitRpcMethods( }, }; - messages[reqMsgId] = { + target.messages[reqMsgId] = { name: `${method}.request`, title: `${method} request`, contentType: "application/json", correlationId: { location: "$message.payload#/id" }, payload: { $ref: `#/components/schemas/${baseId}Request` }, }; - messages[resMsgId] = { + target.messages[resMsgId] = { name: `${method}.response`, title: `${method} response`, contentType: "application/json", @@ -106,11 +125,11 @@ function emitRpcMethods( payload: { $ref: `#/components/schemas/${baseId}Response` }, }; - channelMessages[reqMsgId] = { $ref: `#/components/messages/${reqMsgId}` }; - channelMessages[resMsgId] = { $ref: `#/components/messages/${resMsgId}` }; + target.channelMessages[reqMsgId] = { $ref: `#/components/messages/${reqMsgId}` }; + target.channelMessages[resMsgId] = { $ref: `#/components/messages/${resMsgId}` }; const summary = getSummary(program, op); - doc.operations![lcfirst(baseId)] = { + target.operations[lcfirst(baseId)] = { action: "send", channel: { $ref: `#/channels/${channelId}` }, title: method, @@ -130,13 +149,10 @@ function emitEvents( program: Program, ns: Namespace, channelId: string, - doc: AsyncAPI3Document, ref: RefFn, + target: EmitTarget, ): void { - const schemas = doc.components!.schemas!; - const messages = doc.components!.messages!; - const channelMessages = doc.channels![channelId].messages!; - const eventRefs: { $ref: string }[] = []; + const eventRefs: AsyncAPIRef[] = []; (function visit(n: Namespace): void { for (const model of n.models.values()) { @@ -144,7 +160,7 @@ function emitEvents( if (!eventType) continue; const frameId = `${model.name}Frame`; - schemas[frameId] = { + target.schemas[frameId] = { type: "object", required: ["jsonrpc", "method", "id", "params"], properties: { @@ -167,20 +183,20 @@ function emitEvents( }; const msgId = lcfirst(model.name); - messages[msgId] = { + target.messages[msgId] = { name: eventType, title: `${eventType} event`, contentType: "application/json", payload: { $ref: `#/components/schemas/${frameId}` }, }; - channelMessages[msgId] = { $ref: `#/components/messages/${msgId}` }; + target.channelMessages[msgId] = { $ref: `#/components/messages/${msgId}` }; eventRefs.push({ $ref: `#/channels/${channelId}/messages/${msgId}` }); } n.namespaces.forEach(visit); })(ns); if (eventRefs.length) { - doc.operations![`on${pascal(channelId)}Event`] = { + target.operations[`on${pascal(channelId)}Event`] = { action: "receive", channel: { $ref: `#/channels/${channelId}` }, title: "signalwire.event", @@ -190,16 +206,21 @@ function emitEvents( } } -function emitSecurity(program: Program, ns: Namespace, serverName: string, doc: AsyncAPI3Document): void { +function emitSecurity( + program: Program, + ns: Namespace, + server: AsyncAPIServer, + components: AsyncAPIComponents, +): void { const auth = getBearerAuth(program, ns); if (!auth) return; - doc.components!.securitySchemes ??= {}; - doc.components!.securitySchemes["httpBearer"] = { + components.securitySchemes ??= {}; + components.securitySchemes["httpBearer"] = { type: "http", scheme: "bearer", ...(auth.bearerFormat ? { bearerFormat: auth.bearerFormat } : {}), }; - doc.servers![serverName].security = [{ $ref: "#/components/securitySchemes/httpBearer" }]; + server.security = [{ $ref: "#/components/securitySchemes/httpBearer" }]; } export async function $onEmit(context: EmitContext): Promise { @@ -220,36 +241,45 @@ export async function $onEmit(context: EmitContext): Pro } const registry = createSchemaRegistry(program); - const service = getService(program, ns)!; + const title = getService(program, ns)?.title ?? ns.name; + + // Concrete component maps the emitters write into — referenced by `doc` so writes show through. + const target: EmitTarget = { + schemas: registry.schemas, + messages: {}, + channelMessages: {}, + operations: {}, + }; + const server: AsyncAPIServer = { + host: serverCfg.host, + protocol: serverCfg.protocol, + ...(serverCfg.pathname ? { pathname: serverCfg.pathname } : {}), + ...(serverCfg.description ? { description: serverCfg.description } : {}), + }; + const components: AsyncAPIComponents = { schemas: target.schemas, messages: target.messages }; + const doc: AsyncAPI3Document = { asyncapi: "3.0.0", - info: { title: service.title ?? ns.name, version: "1.0.0" }, + info: { title, version: "1.0.0" }, defaultContentType: "application/json", - servers: { - [serverCfg.name]: { - host: serverCfg.host, - protocol: serverCfg.protocol, - ...(serverCfg.pathname ? { pathname: serverCfg.pathname } : {}), - ...(serverCfg.description ? { description: serverCfg.description } : {}), - }, - }, + servers: { [serverCfg.name]: server }, channels: { [channelId]: { address: null, - title: service.title ?? ns.name, + title, servers: [{ $ref: `#/servers/${serverCfg.name}` }], - messages: {}, + messages: target.channelMessages, }, }, - operations: {}, - components: { schemas: registry.schemas, messages: {} }, + operations: target.operations, + components, }; const desc = getDoc(program, ns); if (desc) doc.info.description = desc; - emitRpcMethods(program, ns, channelId, doc, registry.refFor); - emitEvents(program, ns, channelId, doc, registry.refFor); - emitSecurity(program, ns, serverCfg.name, doc); + emitRpcMethods(program, ns, channelId, registry.refFor, target); + emitEvents(program, ns, channelId, registry.refFor, target); + emitSecurity(program, ns, server, components); applyWebSocketBindings(doc, serverCfg.name, channelId); const outputFile = resolvePath(context.emitterOutputDir, "asyncapi.yaml"); diff --git a/specs/emitters/typespec-asyncapi/src/lib.ts b/specs/emitters/typespec-asyncapi/src/lib.ts index 5be6738922..b38e34513f 100644 --- a/specs/emitters/typespec-asyncapi/src/lib.ts +++ b/specs/emitters/typespec-asyncapi/src/lib.ts @@ -1,4 +1,4 @@ -import { createTypeSpecLibrary, JSONSchemaType } from "@typespec/compiler"; +import { createTypeSpecLibrary, JSONSchemaType, paramMessage } from "@typespec/compiler"; export interface AsyncAPIEmitterOptions { /** Output file name. Default: `asyncapi.yaml`. */ @@ -35,6 +35,12 @@ export const $lib = createTypeSpecLibrary({ default: "An @rpcMethod operation must be under a namespace marked with @channel.", }, }, + "duplicate-rpc-method": { + severity: "error", + messages: { + default: paramMessage`Duplicate @rpcMethod "${"method"}". JSON-RPC method names must be unique within a service.`, + }, + }, }, state: { server: { description: "State for @server" }, diff --git a/specs/emitters/typespec-asyncapi/src/schema-emitter.ts b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts index 7c0d80491e..da604423c1 100644 --- a/specs/emitters/typespec-asyncapi/src/schema-emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts @@ -21,14 +21,16 @@ import { resolveEncodedName, Scalar, serializeValueAsJson, + StringLiteral, Type, + Union, } from "@typespec/compiler"; -import { SchemaObject } from "./types.js"; +import { AsyncAPISchema, SchemaOrRef } from "./types.js"; /** Resolve a (possibly named) type to a schema — a `$ref` for named models/unions, inline otherwise. */ -export type RefFn = (type: Type) => SchemaObject; +export type RefFn = (type: Type) => SchemaOrRef; -const SCALAR_MAP: Record = { +const SCALAR_MAP: Record = { string: { type: "string" }, boolean: { type: "boolean" }, bytes: { type: "string", format: "byte" }, @@ -52,7 +54,7 @@ const SCALAR_MAP: Record = { duration: { type: "string", format: "duration" }, }; -function scalarSchema(scalar: Scalar): SchemaObject { +function scalarSchema(scalar: Scalar): AsyncAPISchema { let s: Scalar | undefined = scalar; while (s) { if (SCALAR_MAP[s.name]) return { ...SCALAR_MAP[s.name] }; @@ -63,9 +65,7 @@ function scalarSchema(scalar: Scalar): SchemaObject { /** A model emitted as a named component (excludes Array/Record/anonymous models). */ function isNamedModel(t: Type): t is Model { - return ( - t.kind === "Model" && !!t.name && t.name !== "Array" && t.name !== "Record" && !(t as Model).indexer - ); + return t.kind === "Model" && !!t.name && t.name !== "Array" && t.name !== "Record" && !t.indexer; } /** True for types that should be emitted as a named component and `$ref`'d. */ @@ -74,7 +74,7 @@ function isRefworthy(t: Type): boolean { } /** JSON-Schema for a set of literal values, inferring `type` from the values. */ -function enumSchema(values: unknown[]): SchemaObject { +function enumSchema(values: unknown[]): AsyncAPISchema { if (values.every((v) => typeof v === "string")) return { type: "string", enum: values }; if (values.every((v) => typeof v === "number")) { return { type: values.every((v) => Number.isInteger(v)) ? "integer" : "number", enum: values }; @@ -83,6 +83,15 @@ function enumSchema(values: unknown[]): SchemaObject { return { enum: values }; } +/** JSON-Schema for a single constant, inferring `type` from the value. */ +function constSchema(value: unknown): AsyncAPISchema { + if (typeof value === "number") { + return { type: Number.isInteger(value) ? "integer" : "number", const: value }; + } + if (typeof value === "boolean") return { type: "boolean", const: value }; + return { type: "string", const: value }; +} + /** Map a TypeSpec `@encode` encoding to a JSON-Schema `format` (open vocabulary). */ function encodeFormat(encoding?: string): string | undefined { switch (encoding) { @@ -106,7 +115,7 @@ function encodeFormat(encoding?: string): string | undefined { * If `@encode` is present, "decay" to the wire type's schema (per the compiler's * encoding guidance) and carry the encoding as a JSON-Schema `format` where known. */ -function encodeSchema(program: Program, target: ModelProperty | Scalar): SchemaObject | undefined { +function encodeSchema(program: Program, target: ModelProperty | Scalar): AsyncAPISchema | undefined { const enc = getEncode(program, target); if (!enc) return undefined; const base = scalarSchema(enc.type); @@ -119,15 +128,8 @@ export function encodedPropName(program: Program, prop: ModelProperty): string { return resolveEncodedName(program, prop, "application/json"); } -/** JSON-Schema for a single constant, inferring `type` from the value. */ -function constSchema(value: unknown): SchemaObject { - if (typeof value === "number") return { type: Number.isInteger(value) ? "integer" : "number", const: value }; - if (typeof value === "boolean") return { type: "boolean", const: value }; - return { type: "string", const: value }; -} - /** Schema for a property/element type: `$ref` if named, inline otherwise. */ -function schemaForType(program: Program, t: Type, ref: RefFn): SchemaObject { +function schemaForType(program: Program, t: Type, ref: RefFn): SchemaOrRef { return isRefworthy(t) ? ref(t) : typeToSchema(program, t, ref); } @@ -137,59 +139,67 @@ function schemaForType(program: Program, t: Type, ref: RefFn): SchemaObject { * `@minLength`/`@maxLength`/`@minItems`/`@maxItems`/`@pattern`/`@format`/`@secret`). * AsyncAPI 3.0's Schema Object is a JSON-Schema Draft-07 superset, so these are valid. */ -function applyConstraints(program: Program, target: Type, schema: SchemaObject): SchemaObject { - const out: SchemaObject = { ...schema }; - const set = (k: string, v: unknown) => { - if (v !== undefined) out[k] = v; - }; - set("minimum", getMinValue(program, target)); - set("maximum", getMaxValue(program, target)); - set("exclusiveMinimum", getMinValueExclusive(program, target)); - set("exclusiveMaximum", getMaxValueExclusive(program, target)); - set("minLength", getMinLength(program, target)); - set("maxLength", getMaxLength(program, target)); - set("minItems", getMinItems(program, target)); - set("maxItems", getMaxItems(program, target)); - set("pattern", getPattern(program, target)); - set("format", getFormat(program, target)); +function applyConstraints(program: Program, target: Type, schema: AsyncAPISchema): AsyncAPISchema { + const out: AsyncAPISchema = { ...schema }; + const minimum = getMinValue(program, target); + if (minimum !== undefined) out.minimum = minimum; + const maximum = getMaxValue(program, target); + if (maximum !== undefined) out.maximum = maximum; + const exclusiveMinimum = getMinValueExclusive(program, target); + if (exclusiveMinimum !== undefined) out.exclusiveMinimum = exclusiveMinimum; + const exclusiveMaximum = getMaxValueExclusive(program, target); + if (exclusiveMaximum !== undefined) out.exclusiveMaximum = exclusiveMaximum; + const minLength = getMinLength(program, target); + if (minLength !== undefined) out.minLength = minLength; + const maxLength = getMaxLength(program, target); + if (maxLength !== undefined) out.maxLength = maxLength; + const minItems = getMinItems(program, target); + if (minItems !== undefined) out.minItems = minItems; + const maxItems = getMaxItems(program, target); + if (maxItems !== undefined) out.maxItems = maxItems; + const pattern = getPattern(program, target); + if (pattern !== undefined) out.pattern = pattern; + const format = getFormat(program, target); + if (format !== undefined) out.format = format; if (out.format === undefined && isSecret(program, target)) out.format = "password"; return out; } /** - * Schema for a model property: resolves the type, then layers on description, - * constraints, `default`, and `examples`. Constraints/default/examples are only + * Schema for a model property: resolves the type (honoring `@encode`), then layers on + * description, constraints, `default`, `examples`, and `deprecated`. These are only * merged onto inline schemas — a `$ref` ignores sibling keywords in Draft-07. */ -export function propertySchema(program: Program, prop: ModelProperty, ref: RefFn): SchemaObject { - let s = encodeSchema(program, prop) ?? schemaForType(program, prop.type, ref); +export function propertySchema(program: Program, prop: ModelProperty, ref: RefFn): SchemaOrRef { + const resolved = encodeSchema(program, prop) ?? schemaForType(program, prop.type, ref); + if ("$ref" in resolved) return resolved; + + let out: AsyncAPISchema = resolved; const doc = getDoc(program, prop); - if (doc) s = { ...s, description: doc }; - if (!("$ref" in s)) { - s = applyConstraints(program, prop, s); - if (prop.defaultValue) { - const def = serializeValueAsJson(program, prop.defaultValue, prop.type); - if (def !== undefined) s = { ...s, default: def }; - } - const examples = getExamples(program, prop); - if (examples.length) { - s = { ...s, examples: examples.map((e) => serializeValueAsJson(program, e.value, prop.type)) }; - } - if (isDeprecated(program, prop)) s = { ...s, deprecated: true }; + if (doc) out = { ...out, description: doc }; + out = applyConstraints(program, prop, out); + if (prop.defaultValue) { + const def = serializeValueAsJson(program, prop.defaultValue, prop.type); + if (def !== undefined) out = { ...out, default: def }; } - return s; + const examples = getExamples(program, prop); + if (examples.length) { + out = { ...out, examples: examples.map((e) => serializeValueAsJson(program, e.value, prop.type)) }; + } + if (isDeprecated(program, prop)) out = { ...out, deprecated: true }; + return out; } /** Build an inline object schema from a model's OWN properties. */ -function ownObjectSchema(program: Program, model: Model, ref: RefFn): SchemaObject { - const properties: Record = {}; +function ownObjectSchema(program: Program, model: Model, ref: RefFn): AsyncAPISchema { + const properties: Record = {}; const required: string[] = []; - for (const prop of (model.properties as Map).values()) { + for (const prop of model.properties.values()) { const name = encodedPropName(program, prop); properties[name] = propertySchema(program, prop, ref); if (!prop.optional) required.push(name); } - const schema: SchemaObject = { type: "object", properties }; + const schema: AsyncAPISchema = { type: "object", properties }; if (required.length) schema.required = required; const doc = getDoc(program, model); if (doc) schema.description = doc; @@ -197,16 +207,16 @@ function ownObjectSchema(program: Program, model: Model, ref: RefFn): SchemaObje return schema; } -function unionInline(program: Program, union: { variants: Map }, ref: RefFn): SchemaObject { +function unionInline(program: Program, union: Union, ref: RefFn): AsyncAPISchema { const variants = [...union.variants.values()].map((v) => v.type); if (variants.length && variants.every((v) => v.kind === "String")) { - return { type: "string", enum: variants.map((v: any) => v.value) }; + return { type: "string", enum: variants.map((v) => (v as StringLiteral).value) }; } return { oneOf: variants.map((v) => schemaForType(program, v, ref)) }; } /** Build an inline schema for a type. Named property/element types are delegated to `ref`. */ -export function typeToSchema(program: Program, type: Type, ref: RefFn): SchemaObject { +export function typeToSchema(program: Program, type: Type, ref: RefFn): AsyncAPISchema { switch (type.kind) { case "Scalar": return applyConstraints(program, type, encodeSchema(program, type) ?? scalarSchema(type)); @@ -215,17 +225,16 @@ export function typeToSchema(program: Program, type: Type, ref: RefFn): SchemaOb case "Boolean": return enumSchema([type.value]); case "Model": { - const m = type as Model; - if (m.name === "Array" && m.indexer) { - return { type: "array", items: schemaForType(program, m.indexer.value, ref) }; + if (type.name === "Array" && type.indexer) { + return { type: "array", items: schemaForType(program, type.indexer.value, ref) }; } - if (m.name === "Record" && m.indexer) { - return { type: "object", additionalProperties: schemaForType(program, m.indexer.value, ref) }; + if (type.name === "Record" && type.indexer) { + return { type: "object", additionalProperties: schemaForType(program, type.indexer.value, ref) }; } - return ownObjectSchema(program, m, ref); + return ownObjectSchema(program, type, ref); } case "Union": - return unionInline(program, type as any, ref); + return unionInline(program, type, ref); case "Enum": return enumSchema([...type.members.values()].map((mem) => mem.value ?? mem.name)); case "EnumMember": @@ -236,7 +245,7 @@ export function typeToSchema(program: Program, type: Type, ref: RefFn): SchemaOb } export interface SchemaRegistry { - schemas: Record; + schemas: Record; /** Register a type if named (returns `$ref`), or return its inline schema. */ refFor: RefFn; } @@ -248,15 +257,15 @@ export interface SchemaRegistry { * `allOf`-inheritance — the AsyncAPI-documented (and Fern-safe) form. */ export function createSchemaRegistry(program: Program): SchemaRegistry { - const schemas: Record = {}; + const schemas: Record = {}; - function refFor(t: Type): SchemaObject { + function refFor(t: Type): SchemaOrRef { if (isNamedModel(t)) { registerModel(t); return { $ref: `#/components/schemas/${t.name}` }; } if (t.kind === "Union" && !!t.name) { - if (!schemas[t.name]) schemas[t.name] = unionInline(program, t as any, refFor); + if (!schemas[t.name]) schemas[t.name] = unionInline(program, t, refFor); return { $ref: `#/components/schemas/${t.name}` }; } return typeToSchema(program, t, refFor); @@ -270,10 +279,10 @@ export function createSchemaRegistry(program: Program): SchemaRegistry { const disc = getDiscriminator(program, model); if (disc) { const base = ownObjectSchema(program, model, refFor); - base.properties = (base.properties as Record) ?? {}; - (base.properties as Record)[disc.propertyName] ??= { type: "string" }; + base.properties ??= {}; + base.properties[disc.propertyName] ??= { type: "string" }; base.discriminator = disc.propertyName; - base.required = Array.from(new Set([...((base.required as string[]) ?? []), disc.propertyName])); + base.required = Array.from(new Set([...(base.required ?? []), disc.propertyName])); schemas[name] = base; for (const derived of model.derivedModels) refFor(derived); return; @@ -283,12 +292,12 @@ export function createSchemaRegistry(program: Program): SchemaRegistry { const baseRef = refFor(model.baseModel); const own = ownObjectSchema(program, model, refFor); const baseDisc = getDiscriminator(program, model.baseModel)?.propertyName; - const props = own.properties as Record | undefined; - const discEnum = baseDisc ? props?.[baseDisc]?.enum : undefined; - if (baseDisc && props && Array.isArray(discEnum) && discEnum.length === 1) { + const discProp = baseDisc ? own.properties?.[baseDisc] : undefined; + const discEnum = discProp && !("$ref" in discProp) ? discProp.enum : undefined; + if (baseDisc && own.properties && Array.isArray(discEnum) && discEnum.length === 1) { // Override the inherited discriminator with the variant's literal value, // inferring the JSON-Schema type from the value (string/number/boolean). - props[baseDisc] = constSchema(discEnum[0]); + own.properties[baseDisc] = constSchema(discEnum[0]); } schemas[name] = { allOf: [baseRef, own] }; return; diff --git a/specs/emitters/typespec-asyncapi/src/types.ts b/specs/emitters/typespec-asyncapi/src/types.ts index e676eaebde..cf7b519d81 100644 --- a/specs/emitters/typespec-asyncapi/src/types.ts +++ b/specs/emitters/typespec-asyncapi/src/types.ts @@ -50,12 +50,12 @@ export interface AsyncAPIMessage { summary?: string; contentType?: string; correlationId?: { description?: string; location: string }; - payload: SchemaObject | AsyncAPIRef; + payload: SchemaOrRef; examples?: Array<{ name?: string; summary?: string; payload: unknown }>; } export interface AsyncAPIComponents { - schemas?: Record; + schemas?: Record; messages?: Record; securitySchemes?: Record; } @@ -64,4 +64,46 @@ export interface AsyncAPIRef { $ref: string; } -export type SchemaObject = Record; +/** + * An AsyncAPI 3.0 Schema Object — a JSON-Schema Draft-07 superset. Note the + * dialect: `exclusiveMinimum`/`exclusiveMaximum` are NUMBERS (not booleans as in + * OpenAPI 3.0), `discriminator` is a STRING (the property name, not an object), + * and there is no `nullable`. This is why we don't reuse `@typespec/openapi3`'s + * `OpenAPI3Schema` type — it models a different dialect. + */ +export interface AsyncAPISchema { + type?: string | string[]; + format?: string; + description?: string; + default?: unknown; + examples?: unknown[]; + enum?: unknown[]; + const?: unknown; + deprecated?: boolean; + // numeric + minimum?: number; + maximum?: number; + exclusiveMinimum?: number; + exclusiveMaximum?: number; + // string + minLength?: number; + maxLength?: number; + pattern?: string; + // array + minItems?: number; + maxItems?: number; + items?: SchemaOrRef; + // object + properties?: Record; + required?: string[]; + additionalProperties?: boolean | SchemaOrRef; + // composition / polymorphism (AsyncAPI discriminator is the property NAME string) + allOf?: SchemaOrRef[]; + oneOf?: SchemaOrRef[]; + anyOf?: SchemaOrRef[]; + not?: SchemaOrRef; + discriminator?: string; +} + +/** Either an inline schema or a `$ref` to a component schema. */ +export type SchemaOrRef = AsyncAPISchema | AsyncAPIRef; diff --git a/specs/emitters/typespec-asyncapi/test/server.test.ts b/specs/emitters/typespec-asyncapi/test/server.test.ts index a3b661d203..b7e3f1f0dc 100644 --- a/specs/emitters/typespec-asyncapi/test/server.test.ts +++ b/specs/emitters/typespec-asyncapi/test/server.test.ts @@ -38,4 +38,18 @@ describe("diagnostics", () => { `); strictEqual(diagnostics.some((d) => d.code.endsWith("missing-channel")), true); }); + + it("errors on duplicate @rpcMethod names", async () => { + const diagnostics = await Tester.diagnose(` + @service(#{ title: "X" }) + @server("p", #{ host: "h", protocol: "wss" }) + @channel("calling") + namespace Relay.Calling { + model R { code: string; } + @rpcMethod("calling.dial") op dial(): R; + @rpcMethod("calling.dial") op dialAgain(): R; + } + `); + strictEqual(diagnostics.some((d) => d.code.endsWith("duplicate-rpc-method")), true); + }); }); From 232da6678b913a0292f91ce8dae19d7677454390 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 19:24:08 -0400 Subject: [PATCH 19/25] feat(asyncapi): per-service output-file + $ref metadata; author signalwire + calling core --- fern/apis/relay/asyncapi.yaml | 255 --- fern/apis/relay/calling.yaml | 1645 +++++++++++++++++ fern/apis/relay/generators.yml | 3 +- fern/apis/relay/signalwire.yaml | 607 ++++++ .../emitters/typespec-asyncapi/src/emitter.ts | 2 +- .../typespec-asyncapi/src/schema-emitter.ts | 28 +- specs/emitters/typespec-asyncapi/test/host.ts | 16 +- .../typespec-asyncapi/test/output.test.ts | 23 +- .../test/schema-emitter.test.ts | 25 + specs/package.json | 8 +- specs/relay/calling/common.tsp | 435 +++++ specs/relay/calling/events/core.tsp | 166 ++ specs/relay/calling/main.tsp | 65 +- specs/relay/calling/methods/core-control.tsp | 171 ++ specs/relay/calling/tspconfig.yaml | 1 + specs/relay/signalwire/main.tsp | 218 +++ specs/relay/signalwire/tspconfig.yaml | 7 + 17 files changed, 3360 insertions(+), 315 deletions(-) delete mode 100644 fern/apis/relay/asyncapi.yaml create mode 100644 fern/apis/relay/calling.yaml create mode 100644 fern/apis/relay/signalwire.yaml create mode 100644 specs/relay/calling/common.tsp create mode 100644 specs/relay/calling/events/core.tsp create mode 100644 specs/relay/calling/methods/core-control.tsp create mode 100644 specs/relay/signalwire/main.tsp create mode 100644 specs/relay/signalwire/tspconfig.yaml diff --git a/fern/apis/relay/asyncapi.yaml b/fern/apis/relay/asyncapi.yaml deleted file mode 100644 index 805e2822d4..0000000000 --- a/fern/apis/relay/asyncapi.yaml +++ /dev/null @@ -1,255 +0,0 @@ -asyncapi: 3.0.0 -info: - title: SignalWire Relay — Calling - version: 1.0.0 -defaultContentType: application/json -servers: - production: - host: relay.signalwire.com - protocol: wss - pathname: /api/relay/wss - security: - - $ref: "#/components/securitySchemes/httpBearer" - bindings: - ws: {} -channels: - calling: - address: null - title: SignalWire Relay — Calling - servers: - - $ref: "#/servers/production" - messages: - callingDialRequest: - $ref: "#/components/messages/callingDialRequest" - callingDialResponse: - $ref: "#/components/messages/callingDialResponse" - callStateEvent: - $ref: "#/components/messages/callStateEvent" - bindings: - ws: {} -operations: - callingDial: - action: send - channel: - $ref: "#/channels/calling" - title: calling.dial - summary: Dial outbound call(s); first to answer wins - messages: - - $ref: "#/channels/calling/messages/callingDialRequest" - reply: - channel: - $ref: "#/channels/calling" - messages: - - $ref: "#/channels/calling/messages/callingDialResponse" - onCallingEvent: - action: receive - channel: - $ref: "#/channels/calling" - title: signalwire.event - summary: Asynchronous events pushed by the server over the signalwire.event carrier. - messages: - - $ref: "#/channels/calling/messages/callStateEvent" -components: - schemas: - DialParams: - type: object - properties: - tag: - type: string - description: Identifier added to all call and dial events. - region: - type: string - devices: - type: array - items: - type: array - items: - $ref: "#/components/schemas/Device" - required: - - tag - - devices - Device: - type: object - properties: - type: - type: string - required: - - type - discriminator: type - PhoneDevice: - allOf: - - $ref: "#/components/schemas/Device" - - type: object - properties: - type: - type: string - const: phone - params: - type: object - properties: - from_number: - type: string - to_number: - type: string - timeout: - type: integer - format: int32 - minimum: 0 - default: 30 - required: - - from_number - - to_number - required: - - type - - params - SipDevice: - allOf: - - $ref: "#/components/schemas/Device" - - type: object - properties: - type: - type: string - const: sip - params: - type: object - properties: - from: - type: string - to: - type: string - required: - - from - - to - required: - - type - - params - CallingDialRequest: - type: object - required: - - jsonrpc - - id - - method - - params - properties: - jsonrpc: - type: string - const: "2.0" - id: - type: string - format: uuid - method: - type: string - const: calling.dial - params: - $ref: "#/components/schemas/DialParams" - DialResult: - type: object - properties: - code: - type: string - message: - type: string - call_id: - type: string - node_id: - type: string - required: - - code - - message - CallingDialResponse: - type: object - required: - - jsonrpc - - id - properties: - jsonrpc: - type: string - const: "2.0" - id: - type: string - format: uuid - result: - $ref: "#/components/schemas/DialResult" - CallStateEvent: - type: object - properties: - node_id: - type: string - call_id: - type: string - call_state: - type: string - enum: - - created - - ringing - - answered - - ending - - ended - required: - - node_id - - call_id - - call_state - CallStateEventFrame: - type: object - required: - - jsonrpc - - method - - id - - params - properties: - jsonrpc: - type: string - const: "2.0" - method: - type: string - const: signalwire.event - id: - type: string - format: uuid - params: - type: object - required: - - event_type - - params - properties: - event_type: - type: string - const: calling.call.state - event_channel: - type: string - timestamp: - type: number - space_id: - type: string - project_id: - type: string - params: - $ref: "#/components/schemas/CallStateEvent" - messages: - callingDialRequest: - name: calling.dial.request - title: calling.dial request - contentType: application/json - correlationId: - location: $message.payload#/id - payload: - $ref: "#/components/schemas/CallingDialRequest" - callingDialResponse: - name: calling.dial.response - title: calling.dial response - contentType: application/json - correlationId: - location: $message.payload#/id - payload: - $ref: "#/components/schemas/CallingDialResponse" - callStateEvent: - name: calling.call.state - title: calling.call.state event - contentType: application/json - payload: - $ref: "#/components/schemas/CallStateEventFrame" - securitySchemes: - httpBearer: - type: http - scheme: bearer - bearerFormat: JWT diff --git a/fern/apis/relay/calling.yaml b/fern/apis/relay/calling.yaml new file mode 100644 index 0000000000..bc7c3da962 --- /dev/null +++ b/fern/apis/relay/calling.yaml @@ -0,0 +1,1645 @@ +asyncapi: 3.0.0 +info: + title: SignalWire Relay — Calling + version: 1.0.0 + description: |- + The `calling` service controls voice calls over Relay: dialing, answering, + bridging, media playback/collection, recording, detection, AI, and the + asynchronous `calling.call.*` events that report call/leg state. +defaultContentType: application/json +servers: + production: + host: relay.signalwire.com + protocol: wss + pathname: /api/relay/wss + description: SignalWire Relay WebSocket endpoint. + security: + - $ref: "#/components/securitySchemes/httpBearer" + bindings: + ws: {} +channels: + calling: + address: null + title: SignalWire Relay — Calling + servers: + - $ref: "#/servers/production" + messages: + callingBeginRequest: + $ref: "#/components/messages/callingBeginRequest" + callingBeginResponse: + $ref: "#/components/messages/callingBeginResponse" + callingDialRequest: + $ref: "#/components/messages/callingDialRequest" + callingDialResponse: + $ref: "#/components/messages/callingDialResponse" + callingAnswerRequest: + $ref: "#/components/messages/callingAnswerRequest" + callingAnswerResponse: + $ref: "#/components/messages/callingAnswerResponse" + callingEndRequest: + $ref: "#/components/messages/callingEndRequest" + callingEndResponse: + $ref: "#/components/messages/callingEndResponse" + callingConnectRequest: + $ref: "#/components/messages/callingConnectRequest" + callingConnectResponse: + $ref: "#/components/messages/callingConnectResponse" + callingDisconnectRequest: + $ref: "#/components/messages/callingDisconnectRequest" + callingDisconnectResponse: + $ref: "#/components/messages/callingDisconnectResponse" + callStateEvent: + $ref: "#/components/messages/callStateEvent" + callReceiveEvent: + $ref: "#/components/messages/callReceiveEvent" + callConnectEvent: + $ref: "#/components/messages/callConnectEvent" + callDialEvent: + $ref: "#/components/messages/callDialEvent" + bindings: + ws: {} +operations: + callingBegin: + action: send + channel: + $ref: "#/channels/calling" + title: calling.begin + summary: (Deprecated) Make an outbound call to a single device + messages: + - $ref: "#/channels/calling/messages/callingBeginRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingBeginResponse" + callingDial: + action: send + channel: + $ref: "#/channels/calling" + title: calling.dial + summary: Dial outbound call(s); first to answer wins + messages: + - $ref: "#/channels/calling/messages/callingDialRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingDialResponse" + callingAnswer: + action: send + channel: + $ref: "#/channels/calling" + title: calling.answer + summary: Answer an incoming call + messages: + - $ref: "#/channels/calling/messages/callingAnswerRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingAnswerResponse" + callingEnd: + action: send + channel: + $ref: "#/channels/calling" + title: calling.end + summary: End a call + messages: + - $ref: "#/channels/calling/messages/callingEndRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingEndResponse" + callingConnect: + action: send + channel: + $ref: "#/channels/calling" + title: calling.connect + summary: Connect a device to an active call + messages: + - $ref: "#/channels/calling/messages/callingConnectRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingConnectResponse" + callingDisconnect: + action: send + channel: + $ref: "#/channels/calling" + title: calling.disconnect + summary: Disconnect connected legs without hanging up + messages: + - $ref: "#/channels/calling/messages/callingDisconnectRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingDisconnectResponse" + onCallingEvent: + action: receive + channel: + $ref: "#/channels/calling" + title: signalwire.event + summary: Asynchronous events pushed by the server over the signalwire.event carrier. + messages: + - $ref: "#/channels/calling/messages/callStateEvent" + - $ref: "#/channels/calling/messages/callReceiveEvent" + - $ref: "#/channels/calling/messages/callConnectEvent" + - $ref: "#/channels/calling/messages/callDialEvent" +components: + schemas: + BeginParams: + type: object + properties: + tag: + type: string + description: Identifier added to all call events. + region: + type: string + description: Region to originate from (account/device default if unset). + device: + description: The single device to call (only `phone` is documented for this method). + allOf: + - $ref: "#/components/schemas/DialDevice" + required: + - device + DialDevice: + type: object + properties: + type: + type: string + required: + - type + description: A device to dial (`calling.dial` / `calling.begin`). Discriminated on `type`. + discriminator: type + DialPhoneDevice: + allOf: + - $ref: "#/components/schemas/DialDevice" + - type: object + properties: + type: + type: string + const: phone + params: + $ref: "#/components/schemas/PhoneDeviceParams" + required: + - type + - params + PhoneDeviceParams: + type: object + properties: + from_number: + type: string + description: Origination number, E.164. + to_number: + type: string + description: Destination number, E.164. + timeout: + type: integer + format: int32 + description: Seconds to ring before giving up. + minimum: 0 + default: 30 + max_duration: + type: integer + format: int32 + description: Maximum call duration in seconds. + minimum: 0 + call_state_url: + type: string + format: uri + description: Webhook to receive call-state events for this leg. + call_state_events: + type: array + items: + $ref: "#/components/schemas/CallStateEventName" + description: Which call states to deliver to `call_state_url`. Default `["ended"]`. + confirm: + description: |- + A confirmation prompt to require before bridging: a SWML URL string or an + inline compact SWML document. (Modeled loosely — SWML is documented + separately.) + required: + - from_number + - to_number + description: "`phone` device params." + CallStateEventName: + type: string + enum: + - created + - ringing + - answered + - ended + DialSipDevice: + allOf: + - $ref: "#/components/schemas/DialDevice" + - type: object + properties: + type: + type: string + const: sip + params: + $ref: "#/components/schemas/SipDeviceParams" + required: + - type + - params + SipDeviceParams: + type: object + properties: + from: + type: string + description: Origination SIP URI / address. + from_name: + type: string + description: Caller name to present. + to: + type: string + description: Destination SIP URI / address. + timeout: + type: integer + format: int32 + description: Seconds to ring before giving up. + minimum: 0 + default: 30 + max_duration: + type: integer + format: int32 + description: Maximum call duration in seconds. + minimum: 0 + headers: + type: array + items: + $ref: "#/components/schemas/SipHeader" + description: Custom `X-` SIP headers. + codecs: + type: array + items: + $ref: "#/components/schemas/SipCodec" + description: Negotiable codecs (SignalWire-picked if unset). + webrtc_media: + type: boolean + description: Use WebRTC media for this leg. + call_state_url: + type: string + format: uri + description: Webhook to receive call-state events for this leg. + call_state_events: + type: array + items: + $ref: "#/components/schemas/CallStateEventName" + description: Which call states to deliver to `call_state_url`. Default `["ended"]`. + confirm: + description: A confirmation prompt (SWML URL or inline SWML). + required: + - from + - to + description: "`sip` device params." + SipHeader: + type: object + properties: + name: + type: string + description: Header name (must start with `X-`). + value: + type: string + description: Header value. + required: + - name + - value + description: A SIP header. Only `X-`-prefixed custom headers are permitted. + SipCodec: + type: string + enum: + - PCMU + - PCMA + - OPUS + - G729 + - G722 + - VP8 + - H264 + DialWebrtcDevice: + allOf: + - $ref: "#/components/schemas/DialDevice" + - type: object + properties: + type: + type: string + const: webrtc + params: + $ref: "#/components/schemas/WebrtcDeviceParams" + required: + - type + - params + WebrtcDeviceParams: + type: object + properties: + from: + type: string + description: Origination — E.164 or a registered endpoint URI. + to: + type: string + description: Destination — a WebRTC endpoint URI / resource name. + timeout: + type: integer + format: int32 + description: Seconds to ring before giving up. + minimum: 0 + default: 30 + max_duration: + type: integer + format: int32 + description: Maximum call duration in seconds. + minimum: 0 + codecs: + type: array + items: + $ref: "#/components/schemas/WebrtcCodec" + description: Negotiable codecs (SignalWire-picked if unset). + call_state_url: + type: string + format: uri + description: Webhook to receive call-state events for this leg. + call_state_events: + type: array + items: + $ref: "#/components/schemas/CallStateEventName" + description: Which call states to deliver to `call_state_url`. Default `["ended"]`. + confirm: + description: A confirmation prompt (SWML URL or inline SWML). + required: + - from + - to + description: "`webrtc` device params." + WebrtcCodec: + type: string + enum: + - PCMU + - PCMA + - OPUS + - VP8 + - H264 + CallingBeginRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.begin + params: + $ref: "#/components/schemas/BeginParams" + BeginResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + call_id: + type: string + description: The created call id. + node_id: + type: string + description: Node the call is on. + required: + - code + - message + CallingBeginResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/BeginResult" + DialParams: + type: object + properties: + tag: + type: string + description: Identifier added to all call and dial events. + region: + type: string + description: Region to originate from. + devices: + type: array + items: + type: array + items: + $ref: "#/components/schemas/DialDevice" + description: |- + Devices to dial. The outer array is sequential ringing groups; the inner + array is simultaneous (parallel) dials within a group. The first device to + answer wins. + max_price_per_minute: + type: number + format: double + description: Maximum price per minute willing to be paid. + required: + - tag + - devices + CallingDialRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.dial + params: + $ref: "#/components/schemas/DialParams" + DialResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingDialResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/DialResult" + AnswerParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + codecs: + type: array + items: + $ref: "#/components/schemas/AnswerCodec" + description: |- + Codecs to negotiate (SignalWire-picked if unset). If a listed codec is + unsupported by the call type the request fails with `"400"`. + required: + - node_id + - call_id + AnswerCodec: + type: string + enum: + - PCMU + - PCMA + - OPUS + - G729 + - G722 + - AMR-WB + - VP8 + - H264 + CallingAnswerRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.answer + params: + $ref: "#/components/schemas/AnswerParams" + AnswerResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingAnswerResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/AnswerResult" + EndParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + reason: + description: Why the call is ending. + default: hangup + allOf: + - $ref: "#/components/schemas/CallEndReason" + required: + - node_id + - call_id + CallEndReason: + type: string + enum: + - hangup + - cancel + - busy + - noAnswer + - decline + - error + CallingEndRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.end + params: + $ref: "#/components/schemas/EndParams" + EndResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingEndResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/EndResult" + ConnectParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + ringback: + type: array + items: + $ref: "#/components/schemas/Ringback" + description: Audio to play to the caller while connecting. + tag: + type: string + description: Identifier added to created calls' events. + devices: + type: array + items: + type: array + items: + $ref: "#/components/schemas/ConnectDevice" + description: Devices to connect. Same sequential/parallel topology as `calling.dial`. + max_duration: + type: integer + format: int32 + description: Maximum duration once connected, in MINUTES. + max_price_per_minute: + type: number + format: double + description: Maximum price per minute willing to be paid. + status_url: + type: string + format: uri + description: URL to POST connect events to. + required: + - node_id + - call_id + - devices + Ringback: + type: object + properties: + type: + type: string + required: + - type + description: Audio played to the caller while a connect is in progress. Discriminated on `type`. + discriminator: type + RingbackAudio: + allOf: + - $ref: "#/components/schemas/Ringback" + - type: object + properties: + type: + type: string + const: audio + params: + type: object + properties: + url: + type: string + format: uri + description: Audio file URL. + required: + - url + required: + - type + - params + RingbackTts: + allOf: + - $ref: "#/components/schemas/Ringback" + - type: object + properties: + type: + type: string + const: tts + params: + type: object + properties: + text: + type: string + description: Text to speak (plain or SSML). + language: + type: string + description: TTS language. + default: en-US + gender: + description: TTS voice gender. + default: female + allOf: + - $ref: "#/components/schemas/TtsGender" + required: + - text + required: + - type + - params + TtsGender: + type: string + enum: + - male + - female + RingbackSilence: + allOf: + - $ref: "#/components/schemas/Ringback" + - type: object + properties: + type: + type: string + const: silence + params: + type: object + properties: + duration: + type: number + format: double + description: Seconds of silence. + required: + - duration + required: + - type + - params + RingbackRingtone: + allOf: + - $ref: "#/components/schemas/Ringback" + - type: object + properties: + type: + type: string + const: ringtone + params: + type: object + properties: + name: + description: Tone name (country code). + allOf: + - $ref: "#/components/schemas/ToneName" + duration: + type: number + format: double + description: Seconds to play. + exclusiveMinimum: 0 + required: + - name + required: + - type + - params + ToneName: + type: string + enum: + - at + - au + - bg + - br + - be + - ch + - cl + - cn + - cz + - de + - dk + - ee + - es + - fi + - fr + - gr + - hu + - il + - in + - it + - lt + - jp + - mx + - my + - nl + - no + - nz + - ph + - pl + - pt + - ru + - se + - sg + - th + - uk + - us + - tw + - ve + - za + ConnectDevice: + type: object + properties: + type: + type: string + required: + - type + description: A device to connect to an active call (`calling.connect`). Discriminated on `type`. + discriminator: type + ConnectCallDevice: + allOf: + - $ref: "#/components/schemas/ConnectDevice" + - type: object + properties: + type: + type: string + const: call + params: + $ref: "#/components/schemas/CallRefDeviceParams" + required: + - type + - params + CallRefDeviceParams: + type: object + properties: + node_id: + type: string + description: Node of the existing call. + call_id: + type: string + description: Existing call id. + required: + - node_id + - call_id + description: "`call` device params (connect only) — bridge to an existing call." + ConnectQueueDevice: + allOf: + - $ref: "#/components/schemas/ConnectDevice" + - type: object + properties: + type: + type: string + const: queue + params: + $ref: "#/components/schemas/QueueDeviceParams" + required: + - type + - params + QueueDeviceParams: + type: object + properties: + node_id: + type: string + description: Node of the queue. + queue_name: + type: string + description: Queue name. + queue_id: + type: string + description: Queue id. + required: + - node_id + - queue_name + description: "`queue` device params (connect only) — pull a call from a queue." + ConnectPhoneDevice: + allOf: + - $ref: "#/components/schemas/ConnectDevice" + - type: object + properties: + type: + type: string + const: phone + params: + $ref: "#/components/schemas/PhoneDeviceParams" + required: + - type + - params + ConnectSipDevice: + allOf: + - $ref: "#/components/schemas/ConnectDevice" + - type: object + properties: + type: + type: string + const: sip + params: + $ref: "#/components/schemas/SipDeviceParams" + required: + - type + - params + ConnectWebrtcDevice: + allOf: + - $ref: "#/components/schemas/ConnectDevice" + - type: object + properties: + type: + type: string + const: webrtc + params: + $ref: "#/components/schemas/WebrtcDeviceParams" + required: + - type + - params + ConnectStreamDevice: + allOf: + - $ref: "#/components/schemas/ConnectDevice" + - type: object + properties: + type: + type: string + const: stream + params: + $ref: "#/components/schemas/StreamDeviceParams" + required: + - type + - params + StreamDeviceParams: + type: object + properties: + url: + type: string + format: uri + description: Stream target — `wss://` required. + name: + type: string + description: Optional stream name. + codec: + type: string + description: |- + Codec, optionally with rate/ptime modifiers (e.g. `PCMU@40i`, + `L16@24000h@40i`). One of `PCMU|PCMA|G722|L16`. Default `PCMU`. + default: PCMU + status_url: + type: string + format: uri + description: Webhook for stream status. + status_url_method: + type: string + enum: + - GET + - POST + description: HTTP method for `status_url`. + default: POST + realtime: + type: boolean + description: Stream realtime audio. + default: false + authorization_bearer_token: + type: string + description: Bearer token sent to the stream endpoint. + custom_parameters: + type: object + additionalProperties: {} + description: Arbitrary custom parameters forwarded to the stream endpoint. + required: + - url + description: "`stream` device params (connect only) — bidirectional audio to a WS endpoint." + CallingConnectRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.connect + params: + $ref: "#/components/schemas/ConnectParams" + ConnectResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingConnectResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/ConnectResult" + DisconnectParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + required: + - node_id + - call_id + CallingDisconnectRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.disconnect + params: + $ref: "#/components/schemas/DisconnectParams" + DisconnectResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingDisconnectResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/DisconnectResult" + CallStateEvent: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + tag: + type: string + description: Identifier set on the originating dial/connect. + device: + description: The negotiated device for this call. + allOf: + - $ref: "#/components/schemas/CallDevice" + parent: + description: The parent call, when this call was created by another. + allOf: + - $ref: "#/components/schemas/CallParentRef" + peer: + description: The peer call, when bridged. + allOf: + - $ref: "#/components/schemas/CallPeerRef" + call_state: + description: The new call state. + allOf: + - $ref: "#/components/schemas/CallState" + start_time: + type: integer + format: int64 + description: Epoch milliseconds the call started. + answer_time: + type: integer + format: int64 + description: Epoch milliseconds the call was answered. + end_time: + type: integer + format: int64 + description: Epoch milliseconds the call ended. + created_by: + type: string + enum: + - dial + - connect + - receive + description: What created this call. + required: + - node_id + - call_id + - call_state + description: A change in state of an active Relay-controlled call. + CallDevice: + type: object + properties: + type: + type: string + required: + - type + discriminator: type + CallPhoneDevice: + allOf: + - $ref: "#/components/schemas/CallDevice" + - type: object + properties: + type: + type: string + const: phone + params: + type: object + properties: + from_number: + type: string + description: Origination number, E.164. + to_number: + type: string + description: Destination number, E.164. + required: + - from_number + - to_number + required: + - type + - params + CallSipDevice: + allOf: + - $ref: "#/components/schemas/CallDevice" + - type: object + properties: + type: + type: string + const: sip + params: + type: object + properties: + from: + type: string + description: Origination SIP address. + to: + type: string + description: Destination SIP address. + headers: + type: array + items: + $ref: "#/components/schemas/SipHeader" + description: Custom `X-` SIP headers. + required: + - from + - to + required: + - type + - params + CallWebrtcDevice: + allOf: + - $ref: "#/components/schemas/CallDevice" + - type: object + properties: + type: + type: string + const: webrtc + params: + type: object + additionalProperties: {} + description: WebRTC device params (shapes not documented in the protocol reference). + required: + - type + - params + CallParentRef: + type: object + properties: + node_id: + type: string + call_id: + type: string + device_type: + type: string + description: The parent device type (flattened, e.g. `sip`). + description: A parent call referenced by a state event. + CallPeerRef: + type: object + properties: + node_id: + type: string + call_id: + type: string + description: A peer call referenced by an event. + CallState: + type: string + enum: + - created + - ringing + - answered + - ending + - ended + CallStateEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.state + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallStateEvent" + CallReceiveEvent: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + call_state: + description: State of the inbound call. + allOf: + - $ref: "#/components/schemas/ReceiveCallState" + context: + type: string + description: Routing context the call arrived on (e.g. `pbx`). + device: + description: The inbound device. + allOf: + - $ref: "#/components/schemas/CallDevice" + required: + - node_id + - call_id + - call_state + - device + description: An incoming call available for a Relay client to control. + ReceiveCallState: + type: string + enum: + - created + - connecting + - connected + - disconnecting + - disconnected + CallReceiveEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.receive + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallReceiveEvent" + CallConnectEvent: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + tag: + type: string + description: Identifier of the connect operation. + peer: + description: The peer call being connected. + allOf: + - $ref: "#/components/schemas/ConnectPeer" + connect_state: + type: string + enum: + - disconnected + - connecting + - connected + - failed + description: The connect (bridge) state. + required: + - node_id + - call_id + - peer + - connect_state + description: A call's connect (bridge/unbridge) state. + ConnectPeer: + type: object + properties: + node_id: + type: string + call_id: + type: string + tag: + type: string + queue_id: + type: string + queue_name: + type: string + device: + description: The peer's negotiated device. + allOf: + - $ref: "#/components/schemas/CallDevice" + description: The peer leg in a connect event. + CallConnectEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.connect + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallConnectEvent" + CallDialEvent: + type: object + properties: + node_id: + type: string + description: Node the dial is on. + tag: + type: string + description: Identifier from `calling.dial`. + dial_state: + type: string + enum: + - dialing + - answered + - failed + description: The dial operation state. + call: + description: The answered call (present when `dial_state` is `answered`). + allOf: + - $ref: "#/components/schemas/DialWinnerCall" + required: + - node_id + - tag + - dial_state + description: The state of a `calling.dial` operation. + DialWinnerCall: + type: object + properties: + node_id: + type: string + call_id: + type: string + tag: + type: string + device: + description: The negotiated device. + allOf: + - $ref: "#/components/schemas/CallDevice" + dial_winner: + type: boolean + description: Whether this call is the selected (first-answered) winner. + description: The answered call carried by a `calling.call.dial` event. + CallDialEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.dial + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallDialEvent" + messages: + callingBeginRequest: + name: calling.begin.request + title: calling.begin request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingBeginRequest" + callingBeginResponse: + name: calling.begin.response + title: calling.begin response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingBeginResponse" + callingDialRequest: + name: calling.dial.request + title: calling.dial request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDialRequest" + callingDialResponse: + name: calling.dial.response + title: calling.dial response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDialResponse" + callingAnswerRequest: + name: calling.answer.request + title: calling.answer request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAnswerRequest" + callingAnswerResponse: + name: calling.answer.response + title: calling.answer response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAnswerResponse" + callingEndRequest: + name: calling.end.request + title: calling.end request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingEndRequest" + callingEndResponse: + name: calling.end.response + title: calling.end response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingEndResponse" + callingConnectRequest: + name: calling.connect.request + title: calling.connect request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingConnectRequest" + callingConnectResponse: + name: calling.connect.response + title: calling.connect response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingConnectResponse" + callingDisconnectRequest: + name: calling.disconnect.request + title: calling.disconnect request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDisconnectRequest" + callingDisconnectResponse: + name: calling.disconnect.response + title: calling.disconnect response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDisconnectResponse" + callStateEvent: + name: calling.call.state + title: calling.call.state event + contentType: application/json + payload: + $ref: "#/components/schemas/CallStateEventFrame" + callReceiveEvent: + name: calling.call.receive + title: calling.call.receive event + contentType: application/json + payload: + $ref: "#/components/schemas/CallReceiveEventFrame" + callConnectEvent: + name: calling.call.connect + title: calling.call.connect event + contentType: application/json + payload: + $ref: "#/components/schemas/CallConnectEventFrame" + callDialEvent: + name: calling.call.dial + title: calling.call.dial event + contentType: application/json + payload: + $ref: "#/components/schemas/CallDialEventFrame" + securitySchemes: + httpBearer: + type: http + scheme: bearer + bearerFormat: JWT diff --git a/fern/apis/relay/generators.yml b/fern/apis/relay/generators.yml index 056389a239..c3680635d2 100644 --- a/fern/apis/relay/generators.yml +++ b/fern/apis/relay/generators.yml @@ -1,4 +1,5 @@ # yaml-language-server: $schema=https://schema.buildwithfern.dev/generators-yml.json api: specs: - - asyncapi: asyncapi.yaml + - asyncapi: signalwire.yaml + - asyncapi: calling.yaml diff --git a/fern/apis/relay/signalwire.yaml b/fern/apis/relay/signalwire.yaml new file mode 100644 index 0000000000..7ef34db51f --- /dev/null +++ b/fern/apis/relay/signalwire.yaml @@ -0,0 +1,607 @@ +asyncapi: 3.0.0 +info: + title: SignalWire Relay — Signalwire (handshake & control) + version: 1.0.0 + description: |- + The `signalwire` protocol bootstraps a Relay connection: it authenticates the + client to the network, returns the authorization block and ICE servers, and + controls context (event) subscriptions. Every other Relay service + (`calling`, `messaging`, `tasking`, …) rides on top of a connection + established here. +defaultContentType: application/json +servers: + production: + host: relay.signalwire.com + protocol: wss + pathname: /api/relay/wss + description: SignalWire Relay WebSocket endpoint. + security: + - $ref: "#/components/securitySchemes/httpBearer" + bindings: + ws: {} +channels: + signalwire: + address: null + title: SignalWire Relay — Signalwire (handshake & control) + servers: + - $ref: "#/servers/production" + messages: + signalwireConnectRequest: + $ref: "#/components/messages/signalwireConnectRequest" + signalwireConnectResponse: + $ref: "#/components/messages/signalwireConnectResponse" + signalwireDisconnectRequest: + $ref: "#/components/messages/signalwireDisconnectRequest" + signalwireDisconnectResponse: + $ref: "#/components/messages/signalwireDisconnectResponse" + signalwireReceiveRequest: + $ref: "#/components/messages/signalwireReceiveRequest" + signalwireReceiveResponse: + $ref: "#/components/messages/signalwireReceiveResponse" + signalwireUnreceiveRequest: + $ref: "#/components/messages/signalwireUnreceiveRequest" + signalwireUnreceiveResponse: + $ref: "#/components/messages/signalwireUnreceiveResponse" + setupRequest: + $ref: "#/components/messages/setupRequest" + setupResponse: + $ref: "#/components/messages/setupResponse" + authorizationStateEvent: + $ref: "#/components/messages/authorizationStateEvent" + bindings: + ws: {} +operations: + signalwireConnect: + action: send + channel: + $ref: "#/channels/signalwire" + title: signalwire.connect + summary: Authenticate and establish a Relay connection + messages: + - $ref: "#/channels/signalwire/messages/signalwireConnectRequest" + reply: + channel: + $ref: "#/channels/signalwire" + messages: + - $ref: "#/channels/signalwire/messages/signalwireConnectResponse" + signalwireDisconnect: + action: send + channel: + $ref: "#/channels/signalwire" + title: signalwire.disconnect + summary: Service is about to disconnect the client + messages: + - $ref: "#/channels/signalwire/messages/signalwireDisconnectRequest" + reply: + channel: + $ref: "#/channels/signalwire" + messages: + - $ref: "#/channels/signalwire/messages/signalwireDisconnectResponse" + signalwireReceive: + action: send + channel: + $ref: "#/channels/signalwire" + title: signalwire.receive + summary: Subscribe to inbound events on one or more contexts + messages: + - $ref: "#/channels/signalwire/messages/signalwireReceiveRequest" + reply: + channel: + $ref: "#/channels/signalwire" + messages: + - $ref: "#/channels/signalwire/messages/signalwireReceiveResponse" + signalwireUnreceive: + action: send + channel: + $ref: "#/channels/signalwire" + title: signalwire.unreceive + summary: Unsubscribe from inbound events on one or more contexts + messages: + - $ref: "#/channels/signalwire/messages/signalwireUnreceiveRequest" + reply: + channel: + $ref: "#/channels/signalwire" + messages: + - $ref: "#/channels/signalwire/messages/signalwireUnreceiveResponse" + setup: + action: send + channel: + $ref: "#/channels/signalwire" + title: setup + summary: (Deprecated) Request a Relay protocol — use signalwire.connect + messages: + - $ref: "#/channels/signalwire/messages/setupRequest" + reply: + channel: + $ref: "#/channels/signalwire" + messages: + - $ref: "#/channels/signalwire/messages/setupResponse" + onSignalwireEvent: + action: receive + channel: + $ref: "#/channels/signalwire" + title: signalwire.event + summary: Asynchronous events pushed by the server over the signalwire.event carrier. + messages: + - $ref: "#/channels/signalwire/messages/authorizationStateEvent" +components: + schemas: + ConnectParams: + type: object + properties: + version: + description: Protocol version the client speaks. + allOf: + - $ref: "#/components/schemas/Version" + authentication: + description: The authentication token block. + allOf: + - $ref: "#/components/schemas/Authentication" + agent: + type: string + description: Descriptive information about the SDK and application (e.g. `somesdk-1.2.3`). + protocol: + type: string + description: |- + When provided, lets the client attempt to "hijack" a previously-established + protocol (provided the project and signature allow it). + authorization_state: + type: string + description: |- + Encrypted authorization state from a previous `signalwire.authorization.state` + event, used to reestablish permissions/state on a new node after reconnect. + Format: `:`. + contexts: + type: array + items: + type: string + description: Contexts to begin receiving inbound events for on connect. + required: + - version + - authentication + Version: + type: object + properties: + major: + type: integer + format: int32 + description: Major version. Currently `3`. + minor: + type: integer + format: int32 + description: Minor version. Currently `0`. + revision: + type: integer + format: int32 + description: Revision. Currently `0`. + required: + - major + - minor + - revision + description: Relay protocol version. Clients currently send `3.0.0`. + Authentication: + type: object + properties: + jwt_token: + type: string + description: The project JWT used to authenticate the client. + required: + - jwt_token + description: Authentication material for the connection. + SignalwireConnectRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: signalwire.connect + params: + $ref: "#/components/schemas/ConnectParams" + ConnectResult: + type: object + properties: + identity: + type: string + description: The identity of the client on the BLADE network (`@.`). + authorization: + type: object + additionalProperties: {} + description: |- + The current authorization block granted to the client. Opaque to clients; + stored and echoed back. (Field shapes are backend-internal — modeled as a + free-form object pending source confirmation.) + protocol: + type: string + description: The protocol the client should use for subsequent requests. + ice_servers: + type: array + items: + $ref: "#/components/schemas/IceServer" + description: ICE servers for media. + required: + - identity + - authorization + - protocol + - ice_servers + IceServer: + type: object + properties: + urls: + type: array + items: + type: string + description: ICE server URLs. + credential: + type: string + description: Credential for the ICE servers (HMAC-SHA1, base64). + credentialType: + type: string + description: Credential type. Currently always `password`. + username: + type: string + description: Username to use — the project id with an expiration-encoded prefix. + required: + - urls + - credential + - credentialType + - username + description: A STUN/TURN ICE server the client should use for media. + SignalwireConnectResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/ConnectResult" + DisconnectParams: + type: object + properties: + restart: + type: boolean + description: Indicates the client should restart with a fresh connection. + SignalwireDisconnectRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: signalwire.disconnect + params: + $ref: "#/components/schemas/DisconnectParams" + DisconnectResult: + type: object + properties: {} + description: Empty acknowledgement. + SignalwireDisconnectResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/DisconnectResult" + ReceiveParams: + type: object + properties: + context: + type: string + description: |- + A single context to set up for receiving inbound events. + Deprecated — use `contexts`. + deprecated: true + contexts: + type: array + items: + type: string + description: Multiple contexts to set up for receiving inbound events at once. + SignalwireReceiveRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: signalwire.receive + params: + $ref: "#/components/schemas/ReceiveParams" + Acknowledgement: + type: object + properties: + code: + type: string + description: Result code (string). `"200"` on success; e.g. `"402"` Payment required. + message: + type: string + description: Human-readable result message. + required: + - code + - message + description: Standard `{code, message}` acknowledgement used by receive/unreceive. + SignalwireReceiveResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/Acknowledgement" + UnreceiveParams: + type: object + properties: + contexts: + type: array + items: + type: string + description: Contexts to stop receiving events for. + required: + - contexts + SignalwireUnreceiveRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: signalwire.unreceive + params: + $ref: "#/components/schemas/UnreceiveParams" + SignalwireUnreceiveResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/Acknowledgement" + SetupParams: + type: object + properties: + protocol: + type: string + description: A protocol to recover. + SetupRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: setup + params: + $ref: "#/components/schemas/SetupParams" + SetupResult: + type: object + properties: + protocol: + type: string + description: The protocol string the client should use for subsequent requests. + required: + - protocol + SetupResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/SetupResult" + AuthorizationStateEvent: + type: object + properties: + authorization_state: + type: string + description: |- + Encrypted authorization state (and validation tag) the client can present on + reconnect via `connect.authorization_state`. + Format: `:`. + required: + - authorization_state + description: |- + Provides updated authorization state to the client so it can reestablish that + state if it reconnects to another node. + AuthorizationStateEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: signalwire.authorization.state + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/AuthorizationStateEvent" + messages: + signalwireConnectRequest: + name: signalwire.connect.request + title: signalwire.connect request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/SignalwireConnectRequest" + signalwireConnectResponse: + name: signalwire.connect.response + title: signalwire.connect response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/SignalwireConnectResponse" + signalwireDisconnectRequest: + name: signalwire.disconnect.request + title: signalwire.disconnect request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/SignalwireDisconnectRequest" + signalwireDisconnectResponse: + name: signalwire.disconnect.response + title: signalwire.disconnect response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/SignalwireDisconnectResponse" + signalwireReceiveRequest: + name: signalwire.receive.request + title: signalwire.receive request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/SignalwireReceiveRequest" + signalwireReceiveResponse: + name: signalwire.receive.response + title: signalwire.receive response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/SignalwireReceiveResponse" + signalwireUnreceiveRequest: + name: signalwire.unreceive.request + title: signalwire.unreceive request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/SignalwireUnreceiveRequest" + signalwireUnreceiveResponse: + name: signalwire.unreceive.response + title: signalwire.unreceive response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/SignalwireUnreceiveResponse" + setupRequest: + name: setup.request + title: setup request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/SetupRequest" + setupResponse: + name: setup.response + title: setup response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/SetupResponse" + authorizationStateEvent: + name: signalwire.authorization.state + title: signalwire.authorization.state event + contentType: application/json + payload: + $ref: "#/components/schemas/AuthorizationStateEventFrame" + securitySchemes: + httpBearer: + type: http + scheme: bearer + bearerFormat: JWT diff --git a/specs/emitters/typespec-asyncapi/src/emitter.ts b/specs/emitters/typespec-asyncapi/src/emitter.ts index 8aee39abd5..19b809ba84 100644 --- a/specs/emitters/typespec-asyncapi/src/emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/emitter.ts @@ -282,6 +282,6 @@ export async function $onEmit(context: EmitContext): Pro emitSecurity(program, ns, server, components); applyWebSocketBindings(doc, serverCfg.name, channelId); - const outputFile = resolvePath(context.emitterOutputDir, "asyncapi.yaml"); + const outputFile = resolvePath(context.emitterOutputDir, context.options["output-file"] ?? "asyncapi.yaml"); await emitFile(program, { path: outputFile, content: serialize(doc) }); } diff --git a/specs/emitters/typespec-asyncapi/src/schema-emitter.ts b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts index da604423c1..fa78486419 100644 --- a/specs/emitters/typespec-asyncapi/src/schema-emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/schema-emitter.ts @@ -165,14 +165,36 @@ function applyConstraints(program: Program, target: Type, schema: AsyncAPISchema return out; } +/** Property-site metadata: `description`, `default`, `examples`, `deprecated`. */ +function propertyMetadata(program: Program, prop: ModelProperty): AsyncAPISchema { + const meta: AsyncAPISchema = {}; + const doc = getDoc(program, prop); + if (doc) meta.description = doc; + if (prop.defaultValue) { + const def = serializeValueAsJson(program, prop.defaultValue, prop.type); + if (def !== undefined) meta.default = def; + } + const examples = getExamples(program, prop); + if (examples.length) { + meta.examples = examples.map((e) => serializeValueAsJson(program, e.value, prop.type)); + } + if (isDeprecated(program, prop)) meta.deprecated = true; + return meta; +} + /** * Schema for a model property: resolves the type (honoring `@encode`), then layers on - * description, constraints, `default`, `examples`, and `deprecated`. These are only - * merged onto inline schemas — a `$ref` ignores sibling keywords in Draft-07. + * description, constraints, `default`, `examples`, and `deprecated`. For an inline + * schema these merge directly; for a `$ref` (named enum/union/model) Draft-07 ignores + * sibling keywords, so the metadata is attached via an `allOf` wrapper — otherwise a + * property's description and default would be silently dropped. */ export function propertySchema(program: Program, prop: ModelProperty, ref: RefFn): SchemaOrRef { const resolved = encodeSchema(program, prop) ?? schemaForType(program, prop.type, ref); - if ("$ref" in resolved) return resolved; + if ("$ref" in resolved) { + const meta = propertyMetadata(program, prop); + return Object.keys(meta).length ? { ...meta, allOf: [resolved] } : resolved; + } let out: AsyncAPISchema = resolved; const doc = getDoc(program, prop); diff --git a/specs/emitters/typespec-asyncapi/test/host.ts b/specs/emitters/typespec-asyncapi/test/host.ts index 7a2ca130ea..224d3d9ba9 100644 --- a/specs/emitters/typespec-asyncapi/test/host.ts +++ b/specs/emitters/typespec-asyncapi/test/host.ts @@ -11,12 +11,20 @@ export const Tester = createTester(resolvePath(import.meta.dirname, ".."), { /** Compile relay tsp and return the emitted AsyncAPI document (parsed) + raw yaml. */ export async function asyncApiFor(code: string): Promise<{ doc: any; yaml: string }> { - const outPath = "{emitter-output-dir}/asyncapi.yaml"; + const { outputs } = await Tester.compile(code); + const yaml = outputs["asyncapi.yaml"]; + return { doc: parse(yaml), yaml }; +} + +/** Compile with a custom `output-file` option; returns the raw outputs map (keyed by file name). */ +export async function outputsFor( + code: string, + options: Record, +): Promise> { const { outputs } = await Tester.compile(code, { compilerOptions: { - options: { "@signalwire/typespec-asyncapi": { "output-file": outPath } }, + options: { "@signalwire/typespec-asyncapi": options }, }, }); - const yaml = outputs["asyncapi.yaml"]; - return { doc: parse(yaml), yaml }; + return outputs; } diff --git a/specs/emitters/typespec-asyncapi/test/output.test.ts b/specs/emitters/typespec-asyncapi/test/output.test.ts index 65ecd049f7..84eb8d8ae5 100644 --- a/specs/emitters/typespec-asyncapi/test/output.test.ts +++ b/specs/emitters/typespec-asyncapi/test/output.test.ts @@ -1,8 +1,29 @@ import { deepStrictEqual, strictEqual } from "assert"; import { describe, it } from "vitest"; -import { asyncApiFor } from "./host.js"; +import { asyncApiFor, outputsFor } from "./host.js"; + +const SVC = ` + @service(#{ title: "Relay Calling" }) + @server("production", #{ host: "relay.signalwire.com", protocol: "wss" }) + @channel("calling") + namespace Relay.Calling { + model DialResult { code: string; } + @rpcMethod("calling.dial") op dial(): DialResult; + } +`; describe("output", () => { + it("defaults the output file name to asyncapi.yaml", async () => { + const outputs = await outputsFor(SVC, {}); + strictEqual(typeof outputs["asyncapi.yaml"], "string"); + }); + + it("honors the output-file option (one service per channel spec)", async () => { + const outputs = await outputsFor(SVC, { "output-file": "calling.yaml" }); + strictEqual(typeof outputs["calling.yaml"], "string"); + strictEqual(outputs["asyncapi.yaml"], undefined); + }); + it("emits parseable yaml with a bearer security scheme via @useAuth", async () => { const { doc, yaml } = await asyncApiFor(` @service(#{ title: "Relay Calling" }) diff --git a/specs/emitters/typespec-asyncapi/test/schema-emitter.test.ts b/specs/emitters/typespec-asyncapi/test/schema-emitter.test.ts index d82c3128bf..30aa1112a5 100644 --- a/specs/emitters/typespec-asyncapi/test/schema-emitter.test.ts +++ b/specs/emitters/typespec-asyncapi/test/schema-emitter.test.ts @@ -108,6 +108,31 @@ describe("@encode and @encodedName", () => { }); }); +describe("$ref property metadata", () => { + it("attaches description/default to a named-union (enum) property via allOf", async () => { + const program = await compileModels(` + union Reason { "hangup", "busy" } + model Foo { + /** Why the call ended. */ + reason?: Reason = "hangup"; + plain?: Reason; + } + `); + const reg = createSchemaRegistry(program); + const Foo = program.getGlobalNamespaceType().models.get("Foo")!; + reg.refFor(Foo); + const foo: any = reg.schemas.Foo; + // metadata-bearing ref → allOf wrapper preserves description + default + deepStrictEqual(foo.properties.reason, { + description: "Why the call ended.", + default: "hangup", + allOf: [{ $ref: "#/components/schemas/Reason" }], + }); + // bare ref with no metadata stays a clean $ref + deepStrictEqual(foo.properties.plain, { $ref: "#/components/schemas/Reason" }); + }); +}); + describe("createSchemaRegistry — discriminated inheritance", () => { it("emits @discriminator base + extends variants as allOf-inheritance", async () => { const program = await compileModels(` diff --git a/specs/package.json b/specs/package.json index 831d7120ae..cca6eb0a1e 100644 --- a/specs/package.json +++ b/specs/package.json @@ -7,7 +7,13 @@ "build:all": "yarn build:api && yarn build:schema && yarn build:relay", "build:api": "yarn build:signalwire-rest && yarn build:compatibility-api", "build:schema": "yarn build:swml-calling && yarn build:swml-messaging", - "build:relay": "cd ./relay/calling && tsp compile . && cd ../..", + "build:relay": "yarn build:relay-signalwire && yarn build:relay-calling", + "build:relay-signalwire": "cd ./relay/signalwire && tsp compile . && cd ../..", + "build:relay-calling": "cd ./relay/calling && tsp compile . && cd ../..", + "build:relay-messaging": "cd ./relay/messaging && tsp compile . && cd ../..", + "build:relay-tasking": "cd ./relay/tasking && tsp compile . && cd ../..", + "build:relay-provisioning": "cd ./relay/provisioning && tsp compile . && cd ../..", + "build:relay-webrtc": "cd ./relay/webrtc && tsp compile . && cd ../..", "build:swml-calling": "cd ./swml/calling && tsp compile . && cd ../", "build:swml-messaging": "cd ./swml/messaging && tsp compile . && cd ../", "build:signalwire-rest": "cd ./signalwire-rest && tsp compile . && cd ../", diff --git a/specs/relay/calling/common.tsp b/specs/relay/calling/common.tsp new file mode 100644 index 0000000000..67a943a71a --- /dev/null +++ b/specs/relay/calling/common.tsp @@ -0,0 +1,435 @@ +import "@signalwire/typespec-asyncapi"; + +using SignalWire.AsyncAPI; + +namespace Relay.Calling; + +// ═════════════════════════════════════════════════════════════════════════════ +// Shared mixins +// ═════════════════════════════════════════════════════════════════════════════ + +/** Identifies an active call on a node. Spread into nearly every method/event. */ +model CallAddress { + /** Node the call is on. */ + node_id: string; + + /** The call id. */ + call_id: string; +} + +/** + * The common Relay result envelope. `code` is a STRING (`"200"` on success); + * errors are carried in-band via a non-`"200"` `code` plus `message` — there is + * no JSON-RPC `error` object. + */ +model RelayResult { + /** Result code (string), e.g. `"200"`, `"400"`, `"404"`. */ + code: string; + + /** Human-readable result message. */ + message: string; +} + +/** A SIP header. Only `X-`-prefixed custom headers are permitted. */ +model SipHeader { + /** Header name (must start with `X-`). */ + name: string; + + /** Header value. */ + value: string; +} + +// ═════════════════════════════════════════════════════════════════════════════ +// Shared enums +// ═════════════════════════════════════════════════════════════════════════════ + +/** Lifecycle states of a Relay-controlled call (`calling.call.state`). */ +union CallState { + "created", + "ringing", + "answered", + "ending", + "ended", +} + +/** Subset of call states a device may be asked to report via `call_state_events`. */ +union CallStateEventName { + "created", + "ringing", + "answered", + "ended", +} + +/** Reason an outbound/active call is ended (`calling.end`). */ +union CallEndReason { + "hangup", + "cancel", + "busy", + "noAnswer", + "decline", + "error", +} + +/** Text-to-speech voice gender. */ +union TtsGender { + "male", + "female", +} + +/** Codecs negotiable when answering a call (documented superset across call types). */ +union AnswerCodec { + "PCMU", + "PCMA", + "OPUS", + "G729", + "G722", + "AMR-WB", + "VP8", + "H264", +} + +/** Codecs selectable for SIP devices on dial/connect. */ +union SipCodec { + "PCMU", + "PCMA", + "OPUS", + "G729", + "G722", + "VP8", + "H264", +} + +/** Codecs selectable for WebRTC devices on dial/connect. */ +union WebrtcCodec { + "PCMU", + "PCMA", + "OPUS", + "VP8", + "H264", +} + +/** + * Ringtone names (ITU-T country tone codes) usable by the `ringtone` ringback / + * play element. + */ +union ToneName { + "at", "au", "bg", "br", "be", "ch", "cl", "cn", "cz", "de", "dk", "ee", "es", + "fi", "fr", "gr", "hu", "il", "in", "it", "lt", "jp", "mx", "my", "nl", "no", + "nz", "ph", "pl", "pt", "ru", "se", "sg", "th", "uk", "us", "tw", "ve", "za", +} + +// ═════════════════════════════════════════════════════════════════════════════ +// Request device params (shared by dial & connect) +// +// Each device's `params` body. dial and connect accept near-identical phone/sip/ +// webrtc shapes (minor documented per-context omissions are not modeled +// separately); connect additionally accepts call/queue/stream devices. +// ═════════════════════════════════════════════════════════════════════════════ + +/** `phone` device params. */ +model PhoneDeviceParams { + /** Origination number, E.164. */ + from_number: string; + + /** Destination number, E.164. */ + to_number: string; + + /** Seconds to ring before giving up. */ + @minValue(0) + timeout?: int32 = 30; + + /** Maximum call duration in seconds. */ + @minValue(0) + max_duration?: int32; + + /** Webhook to receive call-state events for this leg. */ + call_state_url?: url; + + /** Which call states to deliver to `call_state_url`. Default `["ended"]`. */ + call_state_events?: CallStateEventName[]; + + /** + * A confirmation prompt to require before bridging: a SWML URL string or an + * inline compact SWML document. (Modeled loosely — SWML is documented + * separately.) + */ + confirm?: unknown; +} + +/** `sip` device params. */ +model SipDeviceParams { + /** Origination SIP URI / address. */ + from: string; + + /** Caller name to present. */ + from_name?: string; + + /** Destination SIP URI / address. */ + to: string; + + /** Seconds to ring before giving up. */ + @minValue(0) + timeout?: int32 = 30; + + /** Maximum call duration in seconds. */ + @minValue(0) + max_duration?: int32; + + /** Custom `X-` SIP headers. */ + headers?: SipHeader[]; + + /** Negotiable codecs (SignalWire-picked if unset). */ + codecs?: SipCodec[]; + + /** Use WebRTC media for this leg. */ + webrtc_media?: boolean; + + /** Webhook to receive call-state events for this leg. */ + call_state_url?: url; + + /** Which call states to deliver to `call_state_url`. Default `["ended"]`. */ + call_state_events?: CallStateEventName[]; + + /** A confirmation prompt (SWML URL or inline SWML). */ + confirm?: unknown; +} + +/** `webrtc` device params. */ +model WebrtcDeviceParams { + /** Origination — E.164 or a registered endpoint URI. */ + from: string; + + /** Destination — a WebRTC endpoint URI / resource name. */ + to: string; + + /** Seconds to ring before giving up. */ + @minValue(0) + timeout?: int32 = 30; + + /** Maximum call duration in seconds. */ + @minValue(0) + max_duration?: int32; + + /** Negotiable codecs (SignalWire-picked if unset). */ + codecs?: WebrtcCodec[]; + + /** Webhook to receive call-state events for this leg. */ + call_state_url?: url; + + /** Which call states to deliver to `call_state_url`. Default `["ended"]`. */ + call_state_events?: CallStateEventName[]; + + /** A confirmation prompt (SWML URL or inline SWML). */ + confirm?: unknown; +} + +/** `call` device params (connect only) — bridge to an existing call. */ +model CallRefDeviceParams { + /** Node of the existing call. */ + node_id: string; + + /** Existing call id. */ + call_id: string; +} + +/** `queue` device params (connect only) — pull a call from a queue. */ +model QueueDeviceParams { + /** Node of the queue. */ + node_id: string; + + /** Queue name. */ + queue_name: string; + + /** Queue id. */ + queue_id?: string; +} + +/** `stream` device params (connect only) — bidirectional audio to a WS endpoint. */ +model StreamDeviceParams { + /** Stream target — `wss://` required. */ + url: url; + + /** Optional stream name. */ + name?: string; + + /** + * Codec, optionally with rate/ptime modifiers (e.g. `PCMU@40i`, + * `L16@24000h@40i`). One of `PCMU|PCMA|G722|L16`. Default `PCMU`. + */ + codec?: string = "PCMU"; + + /** Webhook for stream status. */ + status_url?: url; + + /** HTTP method for `status_url`. */ + status_url_method?: "GET" | "POST" = "POST"; + + /** Stream realtime audio. */ + realtime?: boolean = false; + + /** Bearer token sent to the stream endpoint. */ + authorization_bearer_token?: string; + + /** Arbitrary custom parameters forwarded to the stream endpoint. */ + custom_parameters?: Record; +} + +// ═════════════════════════════════════════════════════════════════════════════ +// Request device unions +// +// dial and connect use DISTINCT discriminated bases because the emitter emits +// every derived model of a base — dial accepts 3 variants, connect 6. +// ═════════════════════════════════════════════════════════════════════════════ + +/** A device to dial (`calling.dial` / `calling.begin`). Discriminated on `type`. */ +@discriminator("type") +model DialDevice { + type: string; +} + +model DialPhoneDevice extends DialDevice { + type: "phone"; + params: PhoneDeviceParams; +} + +model DialSipDevice extends DialDevice { + type: "sip"; + params: SipDeviceParams; +} + +model DialWebrtcDevice extends DialDevice { + type: "webrtc"; + params: WebrtcDeviceParams; +} + +/** A device to connect to an active call (`calling.connect`). Discriminated on `type`. */ +@discriminator("type") +model ConnectDevice { + type: string; +} + +model ConnectCallDevice extends ConnectDevice { + type: "call"; + params: CallRefDeviceParams; +} + +model ConnectQueueDevice extends ConnectDevice { + type: "queue"; + params: QueueDeviceParams; +} + +model ConnectPhoneDevice extends ConnectDevice { + type: "phone"; + params: PhoneDeviceParams; +} + +model ConnectSipDevice extends ConnectDevice { + type: "sip"; + params: SipDeviceParams; +} + +model ConnectWebrtcDevice extends ConnectDevice { + type: "webrtc"; + params: WebrtcDeviceParams; +} + +model ConnectStreamDevice extends ConnectDevice { + type: "stream"; + params: StreamDeviceParams; +} + +// ═════════════════════════════════════════════════════════════════════════════ +// Ringback union (calling.connect) +// ═════════════════════════════════════════════════════════════════════════════ + +/** Audio played to the caller while a connect is in progress. Discriminated on `type`. */ +@discriminator("type") +model Ringback { + type: string; +} + +model RingbackAudio extends Ringback { + type: "audio"; + params: { + /** Audio file URL. */ + url: url; + }; +} + +model RingbackTts extends Ringback { + type: "tts"; + params: { + /** Text to speak (plain or SSML). */ + text: string; + + /** TTS language. */ + language?: string = "en-US"; + + /** TTS voice gender. */ + gender?: TtsGender = "female"; + }; +} + +model RingbackSilence extends Ringback { + type: "silence"; + params: { + /** Seconds of silence. */ + duration: float64; + }; +} + +model RingbackRingtone extends Ringback { + type: "ringtone"; + params: { + /** Tone name (country code). */ + name: ToneName; + + /** Seconds to play. */ + @minValueExclusive(0) + duration?: float64; + }; +} + +// ═════════════════════════════════════════════════════════════════════════════ +// Negotiated call device (events) +// +// The device reported in events — the negotiated leg, with simpler params than +// the request device. Discriminated on `type`. +// ═════════════════════════════════════════════════════════════════════════════ + +@discriminator("type") +model CallDevice { + type: string; +} + +model CallPhoneDevice extends CallDevice { + type: "phone"; + params: { + /** Origination number, E.164. */ + from_number: string; + + /** Destination number, E.164. */ + to_number: string; + }; +} + +model CallSipDevice extends CallDevice { + type: "sip"; + params: { + /** Origination SIP address. */ + from: string; + + /** Destination SIP address. */ + to: string; + + /** Custom `X-` SIP headers. */ + headers?: SipHeader[]; + }; +} + +model CallWebrtcDevice extends CallDevice { + type: "webrtc"; + + /** WebRTC device params (shapes not documented in the protocol reference). */ + params: Record; +} diff --git a/specs/relay/calling/events/core.tsp b/specs/relay/calling/events/core.tsp new file mode 100644 index 0000000000..f7060db0ac --- /dev/null +++ b/specs/relay/calling/events/core.tsp @@ -0,0 +1,166 @@ +import "@signalwire/typespec-asyncapi"; +import "../common.tsp"; + +using SignalWire.AsyncAPI; + +namespace Relay.Calling; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.state +// ───────────────────────────────────────────────────────────────────────────── + +/** A parent call referenced by a state event. */ +model CallParentRef { + node_id?: string; + call_id?: string; + + /** The parent device type (flattened, e.g. `sip`). */ + device_type?: string; +} + +/** A peer call referenced by an event. */ +model CallPeerRef { + node_id?: string; + call_id?: string; +} + +model CallStateParams { + ...CallAddress; + + /** Identifier set on the originating dial/connect. */ + tag?: string; + + /** The negotiated device for this call. */ + device?: CallDevice; + + /** The parent call, when this call was created by another. */ + parent?: CallParentRef; + + /** The peer call, when bridged. */ + peer?: CallPeerRef; + + /** The new call state. */ + call_state: CallState; + + /** Epoch milliseconds the call started. */ + start_time?: int64; + + /** Epoch milliseconds the call was answered. */ + answer_time?: int64; + + /** Epoch milliseconds the call ended. */ + end_time?: int64; + + /** What created this call. */ + created_by?: "dial" | "connect" | "receive"; +} + +/** A change in state of an active Relay-controlled call. */ +@event("calling.call.state") +model CallStateEvent { + ...CallStateParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.receive +// ───────────────────────────────────────────────────────────────────────────── + +/** Call states reported on an inbound `calling.call.receive`. */ +union ReceiveCallState { + "created", + "connecting", + "connected", + "disconnecting", + "disconnected", +} + +model CallReceiveParams { + ...CallAddress; + + /** State of the inbound call. */ + call_state: ReceiveCallState; + + /** Routing context the call arrived on (e.g. `pbx`). */ + context?: string; + + /** The inbound device. */ + device: CallDevice; +} + +/** An incoming call available for a Relay client to control. */ +@event("calling.call.receive") +model CallReceiveEvent { + ...CallReceiveParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.connect +// ───────────────────────────────────────────────────────────────────────────── + +/** The peer leg in a connect event. */ +model ConnectPeer { + node_id?: string; + call_id?: string; + tag?: string; + queue_id?: string; + queue_name?: string; + + /** The peer's negotiated device. */ + device?: CallDevice; +} + +model CallConnectParams { + ...CallAddress; + + /** Identifier of the connect operation. */ + tag?: string; + + /** The peer call being connected. */ + peer: ConnectPeer; + + /** The connect (bridge) state. */ + connect_state: "disconnected" | "connecting" | "connected" | "failed"; +} + +/** A call's connect (bridge/unbridge) state. */ +@event("calling.call.connect") +model CallConnectEvent { + ...CallConnectParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.dial +// ───────────────────────────────────────────────────────────────────────────── + +/** The answered call carried by a `calling.call.dial` event. */ +model DialWinnerCall { + node_id?: string; + call_id?: string; + tag?: string; + + /** The negotiated device. */ + device?: CallDevice; + + /** Whether this call is the selected (first-answered) winner. */ + dial_winner?: boolean; +} + +model CallDialParams { + /** Node the dial is on. */ + node_id: string; + + /** Identifier from `calling.dial`. */ + tag: string; + + /** The dial operation state. */ + dial_state: "dialing" | "answered" | "failed"; + + /** The answered call (present when `dial_state` is `answered`). */ + call?: DialWinnerCall; +} + +/** The state of a `calling.dial` operation. */ +@event("calling.call.dial") +model CallDialEvent { + ...CallDialParams; +} diff --git a/specs/relay/calling/main.tsp b/specs/relay/calling/main.tsp index a1a5b481d3..2ebf8ea782 100644 --- a/specs/relay/calling/main.tsp +++ b/specs/relay/calling/main.tsp @@ -1,57 +1,24 @@ import "@signalwire/typespec-asyncapi"; +// Shared models, then one file per method family / event group. +import "./common.tsp"; +import "./methods/core-control.tsp"; +import "./events/core.tsp"; + using SignalWire.AsyncAPI; +/** + * The `calling` service controls voice calls over Relay: dialing, answering, + * bridging, media playback/collection, recording, detection, AI, and the + * asynchronous `calling.call.*` events that report call/leg state. + */ @service(#{ title: "SignalWire Relay — Calling" }) -@server("production", #{ host: "relay.signalwire.com", protocol: "wss", pathname: "/api/relay/wss" }) +@server("production", #{ + host: "relay.signalwire.com", + protocol: "wss", + pathname: "/api/relay/wss", + description: "SignalWire Relay WebSocket endpoint.", +}) @channel("calling") @bearerAuth("JWT") namespace Relay.Calling; - -@discriminator("type") -model Device { - type: string; -} -model PhoneDevice extends Device { - type: "phone"; - params: { - from_number: string; - to_number: string; - @minValue(0) timeout?: int32 = 30; - }; -} -model SipDevice extends Device { - type: "sip"; - params: { - from: string; - to: string; - }; -} - -model DialParams { - /** Identifier added to all call and dial events. */ - tag: string; - region?: string; - devices: Device[][]; -} -model DialResult { - code: string; - message: string; - call_id?: string; - node_id?: string; -} - -@rpcMethod("calling.dial") -@summary("Dial outbound call(s); first to answer wins") -op dial(...DialParams): DialResult; - -model CallStateParams { - node_id: string; - call_id: string; - call_state: "created" | "ringing" | "answered" | "ending" | "ended"; -} - -@event("calling.call.state") -model CallStateEvent { - ...CallStateParams; -} diff --git a/specs/relay/calling/methods/core-control.tsp b/specs/relay/calling/methods/core-control.tsp new file mode 100644 index 0000000000..c474fdf8af --- /dev/null +++ b/specs/relay/calling/methods/core-control.tsp @@ -0,0 +1,171 @@ +import "@signalwire/typespec-asyncapi"; +import "../common.tsp"; + +using SignalWire.AsyncAPI; + +namespace Relay.Calling; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.begin (deprecated) +// ───────────────────────────────────────────────────────────────────────────── + +model BeginParams { + /** Identifier added to all call events. */ + tag?: string; + + /** Region to originate from (account/device default if unset). */ + region?: string; + + /** The single device to call (only `phone` is documented for this method). */ + device: DialDevice; +} + +model BeginResult { + ...RelayResult; + + /** The created call id. */ + call_id?: string; + + /** Node the call is on. */ + node_id?: string; +} + +/** (Deprecated — use `calling.dial`.) Make an outbound call to a single device. */ +@rpcMethod("calling.begin") +@summary("(Deprecated) Make an outbound call to a single device") +op begin(...BeginParams): BeginResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.dial +// ───────────────────────────────────────────────────────────────────────────── + +model DialParams { + /** Identifier added to all call and dial events. */ + tag: string; + + /** Region to originate from. */ + region?: string; + + /** + * Devices to dial. The outer array is sequential ringing groups; the inner + * array is simultaneous (parallel) dials within a group. The first device to + * answer wins. + */ + devices: DialDevice[][]; + + /** Maximum price per minute willing to be paid. */ + max_price_per_minute?: float64; +} + +model DialResult { + ...RelayResult; +} + +/** + * Dial outbound call(s) to device(s). First device to answer wins. Call + * identifiers arrive asynchronously via `calling.call.dial` / `calling.call.state` + * events keyed on `tag` — not in this synchronous result. + */ +@rpcMethod("calling.dial") +@summary("Dial outbound call(s); first to answer wins") +op dial(...DialParams): DialResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.answer +// ───────────────────────────────────────────────────────────────────────────── + +model AnswerParams { + ...CallAddress; + + /** + * Codecs to negotiate (SignalWire-picked if unset). If a listed codec is + * unsupported by the call type the request fails with `"400"`. + */ + codecs?: AnswerCodec[]; +} + +model AnswerResult { + ...RelayResult; +} + +/** Answer an incoming call. */ +@rpcMethod("calling.answer") +@summary("Answer an incoming call") +op answer(...AnswerParams): AnswerResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.end +// ───────────────────────────────────────────────────────────────────────────── + +model EndParams { + ...CallAddress; + + /** Why the call is ending. */ + reason?: CallEndReason = "hangup"; +} + +model EndResult { + ...RelayResult; +} + +/** End an active or ringing call. */ +@rpcMethod("calling.end") +@summary("End a call") +op end(...EndParams): EndResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.connect +// ───────────────────────────────────────────────────────────────────────────── + +model ConnectParams { + ...CallAddress; + + /** Audio to play to the caller while connecting. */ + ringback?: Ringback[]; + + /** Identifier added to created calls' events. */ + tag?: string; + + /** + * Devices to connect. Same sequential/parallel topology as `calling.dial`. + */ + devices: ConnectDevice[][]; + + /** Maximum duration once connected, in MINUTES. */ + max_duration?: int32; + + /** Maximum price per minute willing to be paid. */ + max_price_per_minute?: float64; + + /** URL to POST connect events to. */ + status_url?: url; +} + +model ConnectResult { + ...RelayResult; +} + +/** + * Call a device and connect it to this active call. Only one connect may execute + * at a time per call. + */ +@rpcMethod("calling.connect") +@summary("Connect a device to an active call") +op connect(...ConnectParams): ConnectResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.disconnect +// ───────────────────────────────────────────────────────────────────────────── + +model DisconnectParams { + ...CallAddress; +} + +model DisconnectResult { + ...RelayResult; +} + +/** Disconnect all calls from this call without hanging up on them. */ +@rpcMethod("calling.disconnect") +@summary("Disconnect connected legs without hanging up") +op disconnect(...DisconnectParams): DisconnectResult; diff --git a/specs/relay/calling/tspconfig.yaml b/specs/relay/calling/tspconfig.yaml index 8dd827b272..1ceaf9f55f 100644 --- a/specs/relay/calling/tspconfig.yaml +++ b/specs/relay/calling/tspconfig.yaml @@ -4,3 +4,4 @@ emit: options: "@signalwire/typespec-asyncapi": emitter-output-dir: "{cwd}/../../../fern/apis/relay" + output-file: "calling.yaml" diff --git a/specs/relay/signalwire/main.tsp b/specs/relay/signalwire/main.tsp new file mode 100644 index 0000000000..c161cbca61 --- /dev/null +++ b/specs/relay/signalwire/main.tsp @@ -0,0 +1,218 @@ +import "@signalwire/typespec-asyncapi"; + +using SignalWire.AsyncAPI; + +/** + * The `signalwire` protocol bootstraps a Relay connection: it authenticates the + * client to the network, returns the authorization block and ICE servers, and + * controls context (event) subscriptions. Every other Relay service + * (`calling`, `messaging`, `tasking`, …) rides on top of a connection + * established here. + */ +@service(#{ title: "SignalWire Relay — Signalwire (handshake & control)" }) +@server("production", #{ + host: "relay.signalwire.com", + protocol: "wss", + pathname: "/api/relay/wss", + description: "SignalWire Relay WebSocket endpoint.", +}) +@channel("signalwire") +@bearerAuth("JWT") +namespace Relay.Signalwire; + +// ───────────────────────────────────────────────────────────────────────────── +// signalwire.connect +// ───────────────────────────────────────────────────────────────────────────── + +/** Relay protocol version. Clients currently send `3.0.0`. */ +model Version { + /** Major version. Currently `3`. */ + major: int32; + /** Minor version. Currently `0`. */ + minor: int32; + /** Revision. Currently `0`. */ + revision: int32; +} + +/** Authentication material for the connection. */ +model Authentication { + /** The project JWT used to authenticate the client. */ + jwt_token: string; +} + +model ConnectParams { + /** Protocol version the client speaks. */ + version: Version; + + /** The authentication token block. */ + authentication: Authentication; + + /** Descriptive information about the SDK and application (e.g. `somesdk-1.2.3`). */ + agent?: string; + + /** + * When provided, lets the client attempt to "hijack" a previously-established + * protocol (provided the project and signature allow it). + */ + protocol?: string; + + /** + * Encrypted authorization state from a previous `signalwire.authorization.state` + * event, used to reestablish permissions/state on a new node after reconnect. + * Format: `:`. + */ + authorization_state?: string; + + /** Contexts to begin receiving inbound events for on connect. */ + contexts?: string[]; +} + +/** A STUN/TURN ICE server the client should use for media. */ +model IceServer { + /** ICE server URLs. */ + urls: string[]; + /** Credential for the ICE servers (HMAC-SHA1, base64). */ + credential: string; + /** Credential type. Currently always `password`. */ + credentialType: string; + /** Username to use — the project id with an expiration-encoded prefix. */ + username: string; +} + +model ConnectResult { + /** The identity of the client on the BLADE network (`@.`). */ + identity: string; + + /** + * The current authorization block granted to the client. Opaque to clients; + * stored and echoed back. (Field shapes are backend-internal — modeled as a + * free-form object pending source confirmation.) + */ + authorization: Record; + + /** The protocol the client should use for subsequent requests. */ + protocol: string; + + /** ICE servers for media. */ + ice_servers: IceServer[]; +} + +/** + * Establish connectivity with the network as an edge client and recover critical + * connection information. After a successful connect a + * `signalwire.authorization.state` event is sent with the current authorization + * state for future reconnections. + */ +@rpcMethod("signalwire.connect") +@summary("Authenticate and establish a Relay connection") +op connect(...ConnectParams): ConnectResult; + +// ───────────────────────────────────────────────────────────────────────────── +// signalwire.disconnect (server-initiated) +// ───────────────────────────────────────────────────────────────────────────── + +model DisconnectParams { + /** Indicates the client should restart with a fresh connection. */ + restart?: boolean; +} + +/** Empty acknowledgement. */ +model DisconnectResult {} + +/** + * Tells the client the service is about to disconnect it and that buffers should + * be flushed (e.g. during a deployment). The client is expected to reply; this + * reply should be the last thing it sends before the socket closes. + * + * Note: this message is sent server→client. It is documented here for + * completeness; clients respond rather than initiate it. + */ +@rpcMethod("signalwire.disconnect") +@summary("Service is about to disconnect the client") +op disconnect(...DisconnectParams): DisconnectResult; + +// ───────────────────────────────────────────────────────────────────────────── +// signalwire.receive / signalwire.unreceive +// ───────────────────────────────────────────────────────────────────────────── + +/** Standard `{code, message}` acknowledgement used by receive/unreceive. */ +model Acknowledgement { + /** Result code (string). `"200"` on success; e.g. `"402"` Payment required. */ + code: string; + /** Human-readable result message. */ + message: string; +} + +model ReceiveParams { + /** + * A single context to set up for receiving inbound events. + * Deprecated — use `contexts`. + */ + #deprecated "Use `contexts` instead." + context?: string; + + /** Multiple contexts to set up for receiving inbound events at once. */ + contexts?: string[]; +} + +/** + * Request incoming events from SignalWire contexts on a previously-setup + * protocol. Used for many different inbound receivers. + */ +@rpcMethod("signalwire.receive") +@summary("Subscribe to inbound events on one or more contexts") +op receive(...ReceiveParams): Acknowledgement; + +model UnreceiveParams { + /** Contexts to stop receiving events for. */ + contexts: string[]; +} + +/** Request incoming events from the given SignalWire contexts to stop. */ +@rpcMethod("signalwire.unreceive") +@summary("Unsubscribe from inbound events on one or more contexts") +op unreceive(...UnreceiveParams): Acknowledgement; + +// ───────────────────────────────────────────────────────────────────────────── +// setup (deprecated — superseded by signalwire.connect) +// ───────────────────────────────────────────────────────────────────────────── + +model SetupParams { + /** A protocol to recover. */ + protocol?: string; +} + +model SetupResult { + /** The protocol string the client should use for subsequent requests. */ + protocol: string; +} + +/** + * Request a Relay protocol. Deprecated — use `signalwire.connect`, which + * supersedes setup and also accepts `contexts` directly. + */ +@rpcMethod("setup") +@summary("(Deprecated) Request a Relay protocol — use signalwire.connect") +op setup(...SetupParams): SetupResult; + +// ───────────────────────────────────────────────────────────────────────────── +// Events +// ───────────────────────────────────────────────────────────────────────────── + +model AuthorizationStateParams { + /** + * Encrypted authorization state (and validation tag) the client can present on + * reconnect via `connect.authorization_state`. + * Format: `:`. + */ + authorization_state: string; +} + +/** + * Provides updated authorization state to the client so it can reestablish that + * state if it reconnects to another node. + */ +@event("signalwire.authorization.state") +model AuthorizationStateEvent { + ...AuthorizationStateParams; +} diff --git a/specs/relay/signalwire/tspconfig.yaml b/specs/relay/signalwire/tspconfig.yaml new file mode 100644 index 0000000000..b5cfa57694 --- /dev/null +++ b/specs/relay/signalwire/tspconfig.yaml @@ -0,0 +1,7 @@ +emit: + - "@signalwire/typespec-asyncapi" + +options: + "@signalwire/typespec-asyncapi": + emitter-output-dir: "{cwd}/../../../fern/apis/relay" + output-file: "signalwire.yaml" From 8f35e4c043300b85238aeb93d1109acd81f606a4 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 19:40:21 -0400 Subject: [PATCH 20/25] =?UTF-8?q?feat(asyncapi):=20author=20full=20Relay?= =?UTF-8?q?=20surface=20=E2=80=94=20all=2071=20methods=20+=2026=20events?= =?UTF-8?q?=20across=206=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fern/apis/relay/calling.yaml | 10414 ++++++++++++++-- fern/apis/relay/generators.yml | 4 + fern/apis/relay/messaging.yaml | 374 + fern/apis/relay/provisioning.yaml | 165 + fern/apis/relay/tasking.yaml | 216 + fern/apis/relay/webrtc.yaml | 340 + specs/package.json | 2 +- specs/relay/calling/common.tsp | 59 + specs/relay/calling/events/media-1.tsp | 399 + specs/relay/calling/events/media-2.tsp | 484 + specs/relay/calling/main.tsp | 9 + specs/relay/calling/methods/ai.tsp | 457 + specs/relay/calling/methods/collect-input.tsp | 272 + .../calling/methods/detect-fax-tap-stream.tsp | 548 + specs/relay/calling/methods/pay-play.tsp | 390 + .../calling/methods/queue-record-refer.tsp | 317 + .../relay/calling/methods/transcribe-misc.tsp | 267 + .../methods/transfer-conf-hold-digits.tsp | 300 + specs/relay/messaging/main.tsp | 199 + specs/relay/messaging/tspconfig.yaml | 7 + specs/relay/provisioning/main.tsp | 94 + specs/relay/provisioning/tspconfig.yaml | 7 + specs/relay/tasking/main.tsp | 90 + specs/relay/tasking/tspconfig.yaml | 7 + specs/relay/webrtc/main.tsp | 157 + specs/relay/webrtc/tspconfig.yaml | 7 + 26 files changed, 14674 insertions(+), 911 deletions(-) create mode 100644 fern/apis/relay/messaging.yaml create mode 100644 fern/apis/relay/provisioning.yaml create mode 100644 fern/apis/relay/tasking.yaml create mode 100644 fern/apis/relay/webrtc.yaml create mode 100644 specs/relay/calling/events/media-1.tsp create mode 100644 specs/relay/calling/events/media-2.tsp create mode 100644 specs/relay/calling/methods/ai.tsp create mode 100644 specs/relay/calling/methods/collect-input.tsp create mode 100644 specs/relay/calling/methods/detect-fax-tap-stream.tsp create mode 100644 specs/relay/calling/methods/pay-play.tsp create mode 100644 specs/relay/calling/methods/queue-record-refer.tsp create mode 100644 specs/relay/calling/methods/transcribe-misc.tsp create mode 100644 specs/relay/calling/methods/transfer-conf-hold-digits.tsp create mode 100644 specs/relay/messaging/main.tsp create mode 100644 specs/relay/messaging/tspconfig.yaml create mode 100644 specs/relay/provisioning/main.tsp create mode 100644 specs/relay/provisioning/tspconfig.yaml create mode 100644 specs/relay/tasking/main.tsp create mode 100644 specs/relay/tasking/tspconfig.yaml create mode 100644 specs/relay/webrtc/main.tsp create mode 100644 specs/relay/webrtc/tspconfig.yaml diff --git a/fern/apis/relay/calling.yaml b/fern/apis/relay/calling.yaml index bc7c3da962..b9e00d57b3 100644 --- a/fern/apis/relay/calling.yaml +++ b/fern/apis/relay/calling.yaml @@ -48,6 +48,226 @@ channels: $ref: "#/components/messages/callingDisconnectRequest" callingDisconnectResponse: $ref: "#/components/messages/callingDisconnectResponse" + callingCollectRequest: + $ref: "#/components/messages/callingCollectRequest" + callingCollectResponse: + $ref: "#/components/messages/callingCollectResponse" + callingCollectStopRequest: + $ref: "#/components/messages/callingCollectStopRequest" + callingCollectStopResponse: + $ref: "#/components/messages/callingCollectStopResponse" + callingCollectStartInputTimersRequest: + $ref: "#/components/messages/callingCollectStartInputTimersRequest" + callingCollectStartInputTimersResponse: + $ref: "#/components/messages/callingCollectStartInputTimersResponse" + callingPlayAndCollectRequest: + $ref: "#/components/messages/callingPlayAndCollectRequest" + callingPlayAndCollectResponse: + $ref: "#/components/messages/callingPlayAndCollectResponse" + callingPlayAndCollectStopRequest: + $ref: "#/components/messages/callingPlayAndCollectStopRequest" + callingPlayAndCollectStopResponse: + $ref: "#/components/messages/callingPlayAndCollectStopResponse" + callingPlayAndCollectVolumeRequest: + $ref: "#/components/messages/callingPlayAndCollectVolumeRequest" + callingPlayAndCollectVolumeResponse: + $ref: "#/components/messages/callingPlayAndCollectVolumeResponse" + callingQueueEnterRequest: + $ref: "#/components/messages/callingQueueEnterRequest" + callingQueueEnterResponse: + $ref: "#/components/messages/callingQueueEnterResponse" + callingQueueLeaveRequest: + $ref: "#/components/messages/callingQueueLeaveRequest" + callingQueueLeaveResponse: + $ref: "#/components/messages/callingQueueLeaveResponse" + callingRecordRequest: + $ref: "#/components/messages/callingRecordRequest" + callingRecordResponse: + $ref: "#/components/messages/callingRecordResponse" + callingRecordPauseRequest: + $ref: "#/components/messages/callingRecordPauseRequest" + callingRecordPauseResponse: + $ref: "#/components/messages/callingRecordPauseResponse" + callingRecordResumeRequest: + $ref: "#/components/messages/callingRecordResumeRequest" + callingRecordResumeResponse: + $ref: "#/components/messages/callingRecordResumeResponse" + callingRecordStopRequest: + $ref: "#/components/messages/callingRecordStopRequest" + callingRecordStopResponse: + $ref: "#/components/messages/callingRecordStopResponse" + callingReferRequest: + $ref: "#/components/messages/callingReferRequest" + callingReferResponse: + $ref: "#/components/messages/callingReferResponse" + callingPassRequest: + $ref: "#/components/messages/callingPassRequest" + callingPassResponse: + $ref: "#/components/messages/callingPassResponse" + callingPayRequest: + $ref: "#/components/messages/callingPayRequest" + callingPayResponse: + $ref: "#/components/messages/callingPayResponse" + callingPayStopRequest: + $ref: "#/components/messages/callingPayStopRequest" + callingPayStopResponse: + $ref: "#/components/messages/callingPayStopResponse" + callingPlayRequest: + $ref: "#/components/messages/callingPlayRequest" + callingPlayResponse: + $ref: "#/components/messages/callingPlayResponse" + callingPlayPauseRequest: + $ref: "#/components/messages/callingPlayPauseRequest" + callingPlayPauseResponse: + $ref: "#/components/messages/callingPlayPauseResponse" + callingPlayResumeRequest: + $ref: "#/components/messages/callingPlayResumeRequest" + callingPlayResumeResponse: + $ref: "#/components/messages/callingPlayResumeResponse" + callingPlayStopRequest: + $ref: "#/components/messages/callingPlayStopRequest" + callingPlayStopResponse: + $ref: "#/components/messages/callingPlayStopResponse" + callingPlayVolumeRequest: + $ref: "#/components/messages/callingPlayVolumeRequest" + callingPlayVolumeResponse: + $ref: "#/components/messages/callingPlayVolumeResponse" + callingDetectRequest: + $ref: "#/components/messages/callingDetectRequest" + callingDetectResponse: + $ref: "#/components/messages/callingDetectResponse" + callingDetectStopRequest: + $ref: "#/components/messages/callingDetectStopRequest" + callingDetectStopResponse: + $ref: "#/components/messages/callingDetectStopResponse" + callingSendFaxRequest: + $ref: "#/components/messages/callingSendFaxRequest" + callingSendFaxResponse: + $ref: "#/components/messages/callingSendFaxResponse" + callingSendFaxStopRequest: + $ref: "#/components/messages/callingSendFaxStopRequest" + callingSendFaxStopResponse: + $ref: "#/components/messages/callingSendFaxStopResponse" + callingReceiveFaxRequest: + $ref: "#/components/messages/callingReceiveFaxRequest" + callingReceiveFaxResponse: + $ref: "#/components/messages/callingReceiveFaxResponse" + callingReceiveFaxStopRequest: + $ref: "#/components/messages/callingReceiveFaxStopRequest" + callingReceiveFaxStopResponse: + $ref: "#/components/messages/callingReceiveFaxStopResponse" + callingTapRequest: + $ref: "#/components/messages/callingTapRequest" + callingTapResponse: + $ref: "#/components/messages/callingTapResponse" + callingTapStopRequest: + $ref: "#/components/messages/callingTapStopRequest" + callingTapStopResponse: + $ref: "#/components/messages/callingTapStopResponse" + callingStreamRequest: + $ref: "#/components/messages/callingStreamRequest" + callingStreamResponse: + $ref: "#/components/messages/callingStreamResponse" + callingStreamStopRequest: + $ref: "#/components/messages/callingStreamStopRequest" + callingStreamStopResponse: + $ref: "#/components/messages/callingStreamStopResponse" + callingTransferRequest: + $ref: "#/components/messages/callingTransferRequest" + callingTransferResponse: + $ref: "#/components/messages/callingTransferResponse" + callingJoinConferenceRequest: + $ref: "#/components/messages/callingJoinConferenceRequest" + callingJoinConferenceResponse: + $ref: "#/components/messages/callingJoinConferenceResponse" + callingLeaveConferenceRequest: + $ref: "#/components/messages/callingLeaveConferenceRequest" + callingLeaveConferenceResponse: + $ref: "#/components/messages/callingLeaveConferenceResponse" + callingHoldRequest: + $ref: "#/components/messages/callingHoldRequest" + callingHoldResponse: + $ref: "#/components/messages/callingHoldResponse" + callingUnholdRequest: + $ref: "#/components/messages/callingUnholdRequest" + callingUnholdResponse: + $ref: "#/components/messages/callingUnholdResponse" + callingDenoiseRequest: + $ref: "#/components/messages/callingDenoiseRequest" + callingDenoiseResponse: + $ref: "#/components/messages/callingDenoiseResponse" + callingDenoiseStopRequest: + $ref: "#/components/messages/callingDenoiseStopRequest" + callingDenoiseStopResponse: + $ref: "#/components/messages/callingDenoiseStopResponse" + callingSendDigitsRequest: + $ref: "#/components/messages/callingSendDigitsRequest" + callingSendDigitsResponse: + $ref: "#/components/messages/callingSendDigitsResponse" + callingTranscribeRequest: + $ref: "#/components/messages/callingTranscribeRequest" + callingTranscribeResponse: + $ref: "#/components/messages/callingTranscribeResponse" + callingTranscribeStopRequest: + $ref: "#/components/messages/callingTranscribeStopRequest" + callingTranscribeStopResponse: + $ref: "#/components/messages/callingTranscribeStopResponse" + callingEchoRequest: + $ref: "#/components/messages/callingEchoRequest" + callingEchoResponse: + $ref: "#/components/messages/callingEchoResponse" + callingBindDigitRequest: + $ref: "#/components/messages/callingBindDigitRequest" + callingBindDigitResponse: + $ref: "#/components/messages/callingBindDigitResponse" + callingClearDigitBindingsRequest: + $ref: "#/components/messages/callingClearDigitBindingsRequest" + callingClearDigitBindingsResponse: + $ref: "#/components/messages/callingClearDigitBindingsResponse" + callingLiveTranscribeRequest: + $ref: "#/components/messages/callingLiveTranscribeRequest" + callingLiveTranscribeResponse: + $ref: "#/components/messages/callingLiveTranscribeResponse" + callingLiveTranslateRequest: + $ref: "#/components/messages/callingLiveTranslateRequest" + callingLiveTranslateResponse: + $ref: "#/components/messages/callingLiveTranslateResponse" + callingJoinRoomRequest: + $ref: "#/components/messages/callingJoinRoomRequest" + callingJoinRoomResponse: + $ref: "#/components/messages/callingJoinRoomResponse" + callingLeaveRoomRequest: + $ref: "#/components/messages/callingLeaveRoomRequest" + callingLeaveRoomResponse: + $ref: "#/components/messages/callingLeaveRoomResponse" + callingAiRequest: + $ref: "#/components/messages/callingAiRequest" + callingAiResponse: + $ref: "#/components/messages/callingAiResponse" + callingAiStopRequest: + $ref: "#/components/messages/callingAiStopRequest" + callingAiStopResponse: + $ref: "#/components/messages/callingAiStopResponse" + callingAmazonBedrockRequest: + $ref: "#/components/messages/callingAmazonBedrockRequest" + callingAmazonBedrockResponse: + $ref: "#/components/messages/callingAmazonBedrockResponse" + callingAiMessageRequest: + $ref: "#/components/messages/callingAiMessageRequest" + callingAiMessageResponse: + $ref: "#/components/messages/callingAiMessageResponse" + callingAiHoldRequest: + $ref: "#/components/messages/callingAiHoldRequest" + callingAiHoldResponse: + $ref: "#/components/messages/callingAiHoldResponse" + callingAiUnholdRequest: + $ref: "#/components/messages/callingAiUnholdRequest" + callingAiUnholdResponse: + $ref: "#/components/messages/callingAiUnholdResponse" + callingUserEventRequest: + $ref: "#/components/messages/callingUserEventRequest" + callingUserEventResponse: + $ref: "#/components/messages/callingUserEventResponse" callStateEvent: $ref: "#/components/messages/callStateEvent" callReceiveEvent: @@ -56,6 +276,40 @@ channels: $ref: "#/components/messages/callConnectEvent" callDialEvent: $ref: "#/components/messages/callDialEvent" + callReferEvent: + $ref: "#/components/messages/callReferEvent" + callPlayEvent: + $ref: "#/components/messages/callPlayEvent" + callQueueEvent: + $ref: "#/components/messages/callQueueEvent" + callCollectEvent: + $ref: "#/components/messages/callCollectEvent" + callRecordEvent: + $ref: "#/components/messages/callRecordEvent" + callDetectEvent: + $ref: "#/components/messages/callDetectEvent" + callDenoiseEvent: + $ref: "#/components/messages/callDenoiseEvent" + callFaxEvent: + $ref: "#/components/messages/callFaxEvent" + callTapEvent: + $ref: "#/components/messages/callTapEvent" + callStreamEvent: + $ref: "#/components/messages/callStreamEvent" + callTranscribeEvent: + $ref: "#/components/messages/callTranscribeEvent" + callHoldEvent: + $ref: "#/components/messages/callHoldEvent" + callSendDigitsEvent: + $ref: "#/components/messages/callSendDigitsEvent" + conferenceEvent: + $ref: "#/components/messages/conferenceEvent" + callEchoEvent: + $ref: "#/components/messages/callEchoEvent" + callPayEvent: + $ref: "#/components/messages/callPayEvent" + callErrorEvent: + $ref: "#/components/messages/callErrorEvent" bindings: ws: {} operations: @@ -137,372 +391,7248 @@ operations: $ref: "#/channels/calling" messages: - $ref: "#/channels/calling/messages/callingDisconnectResponse" - onCallingEvent: - action: receive + callingCollect: + action: send channel: $ref: "#/channels/calling" - title: signalwire.event - summary: Asynchronous events pushed by the server over the signalwire.event carrier. + title: calling.collect + summary: Collect digits and/or speech from a call messages: - - $ref: "#/channels/calling/messages/callStateEvent" - - $ref: "#/channels/calling/messages/callReceiveEvent" - - $ref: "#/channels/calling/messages/callConnectEvent" - - $ref: "#/channels/calling/messages/callDialEvent" -components: - schemas: - BeginParams: - type: object - properties: - tag: - type: string - description: Identifier added to all call events. - region: - type: string - description: Region to originate from (account/device default if unset). - device: - description: The single device to call (only `phone` is documented for this method). - allOf: - - $ref: "#/components/schemas/DialDevice" - required: - - device - DialDevice: - type: object - properties: - type: - type: string - required: - - type - description: A device to dial (`calling.dial` / `calling.begin`). Discriminated on `type`. - discriminator: type - DialPhoneDevice: - allOf: - - $ref: "#/components/schemas/DialDevice" - - type: object - properties: - type: - type: string - const: phone - params: - $ref: "#/components/schemas/PhoneDeviceParams" - required: - - type - - params - PhoneDeviceParams: - type: object - properties: - from_number: - type: string - description: Origination number, E.164. - to_number: - type: string - description: Destination number, E.164. - timeout: - type: integer - format: int32 - description: Seconds to ring before giving up. - minimum: 0 - default: 30 - max_duration: - type: integer - format: int32 - description: Maximum call duration in seconds. - minimum: 0 - call_state_url: - type: string - format: uri - description: Webhook to receive call-state events for this leg. - call_state_events: - type: array - items: - $ref: "#/components/schemas/CallStateEventName" - description: Which call states to deliver to `call_state_url`. Default `["ended"]`. - confirm: - description: |- - A confirmation prompt to require before bridging: a SWML URL string or an - inline compact SWML document. (Modeled loosely — SWML is documented - separately.) - required: - - from_number - - to_number - description: "`phone` device params." - CallStateEventName: - type: string - enum: - - created - - ringing - - answered - - ended - DialSipDevice: - allOf: - - $ref: "#/components/schemas/DialDevice" - - type: object - properties: - type: - type: string - const: sip - params: - $ref: "#/components/schemas/SipDeviceParams" - required: - - type - - params - SipDeviceParams: - type: object - properties: - from: - type: string - description: Origination SIP URI / address. - from_name: - type: string - description: Caller name to present. - to: - type: string - description: Destination SIP URI / address. - timeout: - type: integer - format: int32 - description: Seconds to ring before giving up. - minimum: 0 - default: 30 - max_duration: - type: integer - format: int32 - description: Maximum call duration in seconds. - minimum: 0 - headers: - type: array - items: - $ref: "#/components/schemas/SipHeader" - description: Custom `X-` SIP headers. - codecs: - type: array - items: - $ref: "#/components/schemas/SipCodec" - description: Negotiable codecs (SignalWire-picked if unset). - webrtc_media: - type: boolean - description: Use WebRTC media for this leg. - call_state_url: - type: string - format: uri - description: Webhook to receive call-state events for this leg. - call_state_events: - type: array - items: - $ref: "#/components/schemas/CallStateEventName" - description: Which call states to deliver to `call_state_url`. Default `["ended"]`. - confirm: - description: A confirmation prompt (SWML URL or inline SWML). - required: - - from - - to - description: "`sip` device params." - SipHeader: - type: object - properties: - name: - type: string - description: Header name (must start with `X-`). - value: - type: string - description: Header value. - required: - - name - - value - description: A SIP header. Only `X-`-prefixed custom headers are permitted. - SipCodec: - type: string - enum: - - PCMU - - PCMA - - OPUS - - G729 - - G722 - - VP8 - - H264 - DialWebrtcDevice: - allOf: - - $ref: "#/components/schemas/DialDevice" - - type: object - properties: - type: - type: string - const: webrtc - params: - $ref: "#/components/schemas/WebrtcDeviceParams" - required: - - type - - params - WebrtcDeviceParams: - type: object - properties: - from: - type: string - description: Origination — E.164 or a registered endpoint URI. - to: - type: string - description: Destination — a WebRTC endpoint URI / resource name. - timeout: - type: integer - format: int32 - description: Seconds to ring before giving up. - minimum: 0 - default: 30 - max_duration: - type: integer - format: int32 - description: Maximum call duration in seconds. - minimum: 0 - codecs: - type: array - items: - $ref: "#/components/schemas/WebrtcCodec" - description: Negotiable codecs (SignalWire-picked if unset). - call_state_url: - type: string - format: uri - description: Webhook to receive call-state events for this leg. - call_state_events: - type: array - items: - $ref: "#/components/schemas/CallStateEventName" - description: Which call states to deliver to `call_state_url`. Default `["ended"]`. - confirm: - description: A confirmation prompt (SWML URL or inline SWML). - required: - - from - - to - description: "`webrtc` device params." - WebrtcCodec: - type: string - enum: - - PCMU - - PCMA - - OPUS - - VP8 - - H264 - CallingBeginRequest: - type: object - required: - - jsonrpc - - id - - method - - params - properties: - jsonrpc: - type: string - const: "2.0" - id: - type: string - format: uuid - method: + - $ref: "#/channels/calling/messages/callingCollectRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingCollectResponse" + callingCollectStop: + action: send + channel: + $ref: "#/channels/calling" + title: calling.collect.stop + summary: Stop an active collect + messages: + - $ref: "#/channels/calling/messages/callingCollectStopRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingCollectStopResponse" + callingCollectStartInputTimers: + action: send + channel: + $ref: "#/channels/calling" + title: calling.collect.start_input_timers + summary: Start the initial-timeout timer on a collect + messages: + - $ref: "#/channels/calling/messages/callingCollectStartInputTimersRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingCollectStartInputTimersResponse" + callingPlayAndCollect: + action: send + channel: + $ref: "#/channels/calling" + title: calling.play_and_collect + summary: Play media and collect input + messages: + - $ref: "#/channels/calling/messages/callingPlayAndCollectRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingPlayAndCollectResponse" + callingPlayAndCollectStop: + action: send + channel: + $ref: "#/channels/calling" + title: calling.play_and_collect.stop + summary: Stop an active play-and-collect + messages: + - $ref: "#/channels/calling/messages/callingPlayAndCollectStopRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingPlayAndCollectStopResponse" + callingPlayAndCollectVolume: + action: send + channel: + $ref: "#/channels/calling" + title: calling.play_and_collect.volume + summary: Change play-and-collect volume + messages: + - $ref: "#/channels/calling/messages/callingPlayAndCollectVolumeRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingPlayAndCollectVolumeResponse" + callingQueueEnter: + action: send + channel: + $ref: "#/channels/calling" + title: calling.queue.enter + summary: Place the call into a queue + messages: + - $ref: "#/channels/calling/messages/callingQueueEnterRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingQueueEnterResponse" + callingQueueLeave: + action: send + channel: + $ref: "#/channels/calling" + title: calling.queue.leave + summary: Remove the call from a queue + messages: + - $ref: "#/channels/calling/messages/callingQueueLeaveRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingQueueLeaveResponse" + callingRecord: + action: send + channel: + $ref: "#/channels/calling" + title: calling.record + summary: Record a call + messages: + - $ref: "#/channels/calling/messages/callingRecordRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingRecordResponse" + callingRecordPause: + action: send + channel: + $ref: "#/channels/calling" + title: calling.record.pause + summary: Pause an active recording + messages: + - $ref: "#/channels/calling/messages/callingRecordPauseRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingRecordPauseResponse" + callingRecordResume: + action: send + channel: + $ref: "#/channels/calling" + title: calling.record.resume + summary: Resume a paused recording + messages: + - $ref: "#/channels/calling/messages/callingRecordResumeRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingRecordResumeResponse" + callingRecordStop: + action: send + channel: + $ref: "#/channels/calling" + title: calling.record.stop + summary: Stop an active recording + messages: + - $ref: "#/channels/calling/messages/callingRecordStopRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingRecordStopResponse" + callingRefer: + action: send + channel: + $ref: "#/channels/calling" + title: calling.refer + summary: Transfer a SIP call via SIP REFER + messages: + - $ref: "#/channels/calling/messages/callingReferRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingReferResponse" + callingPass: + action: send + channel: + $ref: "#/channels/calling" + title: calling.pass + summary: Pass the call offer to another consumer + messages: + - $ref: "#/channels/calling/messages/callingPassRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingPassResponse" + callingPay: + action: send + channel: + $ref: "#/channels/calling" + title: calling.pay + summary: Collect a payment via the Pay IVR + messages: + - $ref: "#/channels/calling/messages/callingPayRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingPayResponse" + callingPayStop: + action: send + channel: + $ref: "#/channels/calling" + title: calling.pay.stop + summary: Stop an active pay + messages: + - $ref: "#/channels/calling/messages/callingPayStopRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingPayStopResponse" + callingPlay: + action: send + channel: + $ref: "#/channels/calling" + title: calling.play + summary: Play media to a call + messages: + - $ref: "#/channels/calling/messages/callingPlayRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingPlayResponse" + callingPlayPause: + action: send + channel: + $ref: "#/channels/calling" + title: calling.play.pause + summary: Pause an active play + messages: + - $ref: "#/channels/calling/messages/callingPlayPauseRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingPlayPauseResponse" + callingPlayResume: + action: send + channel: + $ref: "#/channels/calling" + title: calling.play.resume + summary: Resume a paused play + messages: + - $ref: "#/channels/calling/messages/callingPlayResumeRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingPlayResumeResponse" + callingPlayStop: + action: send + channel: + $ref: "#/channels/calling" + title: calling.play.stop + summary: Stop an active play + messages: + - $ref: "#/channels/calling/messages/callingPlayStopRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingPlayStopResponse" + callingPlayVolume: + action: send + channel: + $ref: "#/channels/calling" + title: calling.play.volume + summary: Adjust the volume of an active play + messages: + - $ref: "#/channels/calling/messages/callingPlayVolumeRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingPlayVolumeResponse" + callingDetect: + action: send + channel: + $ref: "#/channels/calling" + title: calling.detect + summary: Start a detector (machine/fax/digit) + messages: + - $ref: "#/channels/calling/messages/callingDetectRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingDetectResponse" + callingDetectStop: + action: send + channel: + $ref: "#/channels/calling" + title: calling.detect.stop + summary: Stop a detector + messages: + - $ref: "#/channels/calling/messages/callingDetectStopRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingDetectStopResponse" + callingSendFax: + action: send + channel: + $ref: "#/channels/calling" + title: calling.send_fax + summary: Send a PDF fax + messages: + - $ref: "#/channels/calling/messages/callingSendFaxRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingSendFaxResponse" + callingSendFaxStop: + action: send + channel: + $ref: "#/channels/calling" + title: calling.send_fax.stop + summary: Stop sending a fax + messages: + - $ref: "#/channels/calling/messages/callingSendFaxStopRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingSendFaxStopResponse" + callingReceiveFax: + action: send + channel: + $ref: "#/channels/calling" + title: calling.receive_fax + summary: Receive a fax + messages: + - $ref: "#/channels/calling/messages/callingReceiveFaxRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingReceiveFaxResponse" + callingReceiveFaxStop: + action: send + channel: + $ref: "#/channels/calling" + title: calling.receive_fax.stop + summary: Stop receiving a fax + messages: + - $ref: "#/channels/calling/messages/callingReceiveFaxStopRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingReceiveFaxStopResponse" + callingTap: + action: send + channel: + $ref: "#/channels/calling" + title: calling.tap + summary: Tap call media to an external device + messages: + - $ref: "#/channels/calling/messages/callingTapRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingTapResponse" + callingTapStop: + action: send + channel: + $ref: "#/channels/calling" + title: calling.tap.stop + summary: Stop a call tap + messages: + - $ref: "#/channels/calling/messages/callingTapStopRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingTapStopResponse" + callingStream: + action: send + channel: + $ref: "#/channels/calling" + title: calling.stream + summary: Stream call audio to a WebSocket endpoint + messages: + - $ref: "#/channels/calling/messages/callingStreamRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingStreamResponse" + callingStreamStop: + action: send + channel: + $ref: "#/channels/calling" + title: calling.stream.stop + summary: Stop a call stream + messages: + - $ref: "#/channels/calling/messages/callingStreamStopRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingStreamStopResponse" + callingTransfer: + action: send + channel: + $ref: "#/channels/calling" + title: calling.transfer + summary: Transfer call control to a RELAY app or SWML script + messages: + - $ref: "#/channels/calling/messages/callingTransferRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingTransferResponse" + callingJoinConference: + action: send + channel: + $ref: "#/channels/calling" + title: calling.join_conference + summary: Join an ad-hoc audio conference + messages: + - $ref: "#/channels/calling/messages/callingJoinConferenceRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingJoinConferenceResponse" + callingLeaveConference: + action: send + channel: + $ref: "#/channels/calling" + title: calling.leave_conference + summary: Leave an audio conference + messages: + - $ref: "#/channels/calling/messages/callingLeaveConferenceRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingLeaveConferenceResponse" + callingHold: + action: send + channel: + $ref: "#/channels/calling" + title: calling.hold + summary: (Not implemented) Put a call on hold + messages: + - $ref: "#/channels/calling/messages/callingHoldRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingHoldResponse" + callingUnhold: + action: send + channel: + $ref: "#/channels/calling" + title: calling.unhold + summary: (Not implemented) Release a call from hold + messages: + - $ref: "#/channels/calling/messages/callingUnholdRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingUnholdResponse" + callingDenoise: + action: send + channel: + $ref: "#/channels/calling" + title: calling.denoise + summary: Start call noise reduction + messages: + - $ref: "#/channels/calling/messages/callingDenoiseRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingDenoiseResponse" + callingDenoiseStop: + action: send + channel: + $ref: "#/channels/calling" + title: calling.denoise.stop + summary: Stop call noise reduction + messages: + - $ref: "#/channels/calling/messages/callingDenoiseStopRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingDenoiseStopResponse" + callingSendDigits: + action: send + channel: + $ref: "#/channels/calling" + title: calling.send_digits + summary: Send DTMF digit tones to a call + messages: + - $ref: "#/channels/calling/messages/callingSendDigitsRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingSendDigitsResponse" + callingTranscribe: + action: send + channel: + $ref: "#/channels/calling" + title: calling.transcribe + summary: Start transcribing a call + messages: + - $ref: "#/channels/calling/messages/callingTranscribeRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingTranscribeResponse" + callingTranscribeStop: + action: send + channel: + $ref: "#/channels/calling" + title: calling.transcribe.stop + summary: Stop an active call transcription + messages: + - $ref: "#/channels/calling/messages/callingTranscribeStopRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingTranscribeStopResponse" + callingEcho: + action: send + channel: + $ref: "#/channels/calling" + title: calling.echo + summary: Echo audio back to the caller + messages: + - $ref: "#/channels/calling/messages/callingEchoRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingEchoResponse" + callingBindDigit: + action: send + channel: + $ref: "#/channels/calling" + title: calling.bind_digit + summary: Bind a DTMF digit sequence to a RELAY method + messages: + - $ref: "#/channels/calling/messages/callingBindDigitRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingBindDigitResponse" + callingClearDigitBindings: + action: send + channel: + $ref: "#/channels/calling" + title: calling.clear_digit_bindings + summary: Clear digit bindings + messages: + - $ref: "#/channels/calling/messages/callingClearDigitBindingsRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingClearDigitBindingsResponse" + callingLiveTranscribe: + action: send + channel: + $ref: "#/channels/calling" + title: calling.live_transcribe + summary: Start or stop live transcription on a call + messages: + - $ref: "#/channels/calling/messages/callingLiveTranscribeRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingLiveTranscribeResponse" + callingLiveTranslate: + action: send + channel: + $ref: "#/channels/calling" + title: calling.live_translate + summary: Start or stop live translation on a call + messages: + - $ref: "#/channels/calling/messages/callingLiveTranslateRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingLiveTranslateResponse" + callingJoinRoom: + action: send + channel: + $ref: "#/channels/calling" + title: calling.join_room + summary: Join a video/audio room + messages: + - $ref: "#/channels/calling/messages/callingJoinRoomRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingJoinRoomResponse" + callingLeaveRoom: + action: send + channel: + $ref: "#/channels/calling" + title: calling.leave_room + summary: Leave the current room + messages: + - $ref: "#/channels/calling/messages/callingLeaveRoomRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingLeaveRoomResponse" + callingAi: + action: send + channel: + $ref: "#/channels/calling" + title: calling.ai + summary: Start an AI agent on the call + messages: + - $ref: "#/channels/calling/messages/callingAiRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingAiResponse" + callingAiStop: + action: send + channel: + $ref: "#/channels/calling" + title: calling.ai.stop + summary: Stop an active AI agent session + messages: + - $ref: "#/channels/calling/messages/callingAiStopRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingAiStopResponse" + callingAmazonBedrock: + action: send + channel: + $ref: "#/channels/calling" + title: calling.amazon_bedrock + summary: Connect to an Amazon Bedrock AI agent + messages: + - $ref: "#/channels/calling/messages/callingAmazonBedrockRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingAmazonBedrockResponse" + callingAiMessage: + action: send + channel: + $ref: "#/channels/calling" + title: calling.ai_message + summary: Send a message to an active AI agent session + messages: + - $ref: "#/channels/calling/messages/callingAiMessageRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingAiMessageResponse" + callingAiHold: + action: send + channel: + $ref: "#/channels/calling" + title: calling.ai_hold + summary: Put an AI agent session on hold + messages: + - $ref: "#/channels/calling/messages/callingAiHoldRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingAiHoldResponse" + callingAiUnhold: + action: send + channel: + $ref: "#/channels/calling" + title: calling.ai_unhold + summary: Resume an AI agent session from hold + messages: + - $ref: "#/channels/calling/messages/callingAiUnholdRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingAiUnholdResponse" + callingUserEvent: + action: send + channel: + $ref: "#/channels/calling" + title: calling.user_event + summary: Send a custom user-defined event + messages: + - $ref: "#/channels/calling/messages/callingUserEventRequest" + reply: + channel: + $ref: "#/channels/calling" + messages: + - $ref: "#/channels/calling/messages/callingUserEventResponse" + onCallingEvent: + action: receive + channel: + $ref: "#/channels/calling" + title: signalwire.event + summary: Asynchronous events pushed by the server over the signalwire.event carrier. + messages: + - $ref: "#/channels/calling/messages/callStateEvent" + - $ref: "#/channels/calling/messages/callReceiveEvent" + - $ref: "#/channels/calling/messages/callConnectEvent" + - $ref: "#/channels/calling/messages/callDialEvent" + - $ref: "#/channels/calling/messages/callReferEvent" + - $ref: "#/channels/calling/messages/callPlayEvent" + - $ref: "#/channels/calling/messages/callQueueEvent" + - $ref: "#/channels/calling/messages/callCollectEvent" + - $ref: "#/channels/calling/messages/callRecordEvent" + - $ref: "#/channels/calling/messages/callDetectEvent" + - $ref: "#/channels/calling/messages/callDenoiseEvent" + - $ref: "#/channels/calling/messages/callFaxEvent" + - $ref: "#/channels/calling/messages/callTapEvent" + - $ref: "#/channels/calling/messages/callStreamEvent" + - $ref: "#/channels/calling/messages/callTranscribeEvent" + - $ref: "#/channels/calling/messages/callHoldEvent" + - $ref: "#/channels/calling/messages/callSendDigitsEvent" + - $ref: "#/channels/calling/messages/conferenceEvent" + - $ref: "#/channels/calling/messages/callEchoEvent" + - $ref: "#/channels/calling/messages/callPayEvent" + - $ref: "#/channels/calling/messages/callErrorEvent" +components: + schemas: + BeginParams: + type: object + properties: + tag: + type: string + description: Identifier added to all call events. + region: + type: string + description: Region to originate from (account/device default if unset). + device: + description: The single device to call (only `phone` is documented for this method). + allOf: + - $ref: "#/components/schemas/DialDevice" + required: + - device + DialDevice: + type: object + properties: + type: + type: string + required: + - type + description: A device to dial (`calling.dial` / `calling.begin`). Discriminated on `type`. + discriminator: type + DialPhoneDevice: + allOf: + - $ref: "#/components/schemas/DialDevice" + - type: object + properties: + type: + type: string + const: phone + params: + $ref: "#/components/schemas/PhoneDeviceParams" + required: + - type + - params + PhoneDeviceParams: + type: object + properties: + from_number: + type: string + description: Origination number, E.164. + to_number: + type: string + description: Destination number, E.164. + timeout: + type: integer + format: int32 + description: Seconds to ring before giving up. + minimum: 0 + default: 30 + max_duration: + type: integer + format: int32 + description: Maximum call duration in seconds. + minimum: 0 + call_state_url: + type: string + format: uri + description: Webhook to receive call-state events for this leg. + call_state_events: + type: array + items: + $ref: "#/components/schemas/CallStateEventName" + description: Which call states to deliver to `call_state_url`. Default `["ended"]`. + confirm: + description: |- + A confirmation prompt to require before bridging: a SWML URL string or an + inline compact SWML document. (Modeled loosely — SWML is documented + separately.) + required: + - from_number + - to_number + description: "`phone` device params." + CallStateEventName: + type: string + enum: + - created + - ringing + - answered + - ended + DialSipDevice: + allOf: + - $ref: "#/components/schemas/DialDevice" + - type: object + properties: + type: + type: string + const: sip + params: + $ref: "#/components/schemas/SipDeviceParams" + required: + - type + - params + SipDeviceParams: + type: object + properties: + from: + type: string + description: Origination SIP URI / address. + from_name: + type: string + description: Caller name to present. + to: + type: string + description: Destination SIP URI / address. + timeout: + type: integer + format: int32 + description: Seconds to ring before giving up. + minimum: 0 + default: 30 + max_duration: + type: integer + format: int32 + description: Maximum call duration in seconds. + minimum: 0 + headers: + type: array + items: + $ref: "#/components/schemas/SipHeader" + description: Custom `X-` SIP headers. + codecs: + type: array + items: + $ref: "#/components/schemas/SipCodec" + description: Negotiable codecs (SignalWire-picked if unset). + webrtc_media: + type: boolean + description: Use WebRTC media for this leg. + call_state_url: + type: string + format: uri + description: Webhook to receive call-state events for this leg. + call_state_events: + type: array + items: + $ref: "#/components/schemas/CallStateEventName" + description: Which call states to deliver to `call_state_url`. Default `["ended"]`. + confirm: + description: A confirmation prompt (SWML URL or inline SWML). + required: + - from + - to + description: "`sip` device params." + SipHeader: + type: object + properties: + name: + type: string + description: Header name (must start with `X-`). + value: + type: string + description: Header value. + required: + - name + - value + description: A SIP header. Only `X-`-prefixed custom headers are permitted. + SipCodec: + type: string + enum: + - PCMU + - PCMA + - OPUS + - G729 + - G722 + - VP8 + - H264 + DialWebrtcDevice: + allOf: + - $ref: "#/components/schemas/DialDevice" + - type: object + properties: + type: + type: string + const: webrtc + params: + $ref: "#/components/schemas/WebrtcDeviceParams" + required: + - type + - params + WebrtcDeviceParams: + type: object + properties: + from: + type: string + description: Origination — E.164 or a registered endpoint URI. + to: + type: string + description: Destination — a WebRTC endpoint URI / resource name. + timeout: + type: integer + format: int32 + description: Seconds to ring before giving up. + minimum: 0 + default: 30 + max_duration: + type: integer + format: int32 + description: Maximum call duration in seconds. + minimum: 0 + codecs: + type: array + items: + $ref: "#/components/schemas/WebrtcCodec" + description: Negotiable codecs (SignalWire-picked if unset). + call_state_url: + type: string + format: uri + description: Webhook to receive call-state events for this leg. + call_state_events: + type: array + items: + $ref: "#/components/schemas/CallStateEventName" + description: Which call states to deliver to `call_state_url`. Default `["ended"]`. + confirm: + description: A confirmation prompt (SWML URL or inline SWML). + required: + - from + - to + description: "`webrtc` device params." + WebrtcCodec: + type: string + enum: + - PCMU + - PCMA + - OPUS + - VP8 + - H264 + CallingBeginRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.begin + params: + $ref: "#/components/schemas/BeginParams" + BeginResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + call_id: + type: string + description: The created call id. + node_id: + type: string + description: Node the call is on. + required: + - code + - message + CallingBeginResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/BeginResult" + DialParams: + type: object + properties: + tag: + type: string + description: Identifier added to all call and dial events. + region: + type: string + description: Region to originate from. + devices: + type: array + items: + type: array + items: + $ref: "#/components/schemas/DialDevice" + description: |- + Devices to dial. The outer array is sequential ringing groups; the inner + array is simultaneous (parallel) dials within a group. The first device to + answer wins. + max_price_per_minute: + type: number + format: double + description: Maximum price per minute willing to be paid. + required: + - tag + - devices + CallingDialRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.dial + params: + $ref: "#/components/schemas/DialParams" + DialResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingDialResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/DialResult" + AnswerParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + codecs: + type: array + items: + $ref: "#/components/schemas/AnswerCodec" + description: |- + Codecs to negotiate (SignalWire-picked if unset). If a listed codec is + unsupported by the call type the request fails with `"400"`. + required: + - node_id + - call_id + AnswerCodec: + type: string + enum: + - PCMU + - PCMA + - OPUS + - G729 + - G722 + - AMR-WB + - VP8 + - H264 + CallingAnswerRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.answer + params: + $ref: "#/components/schemas/AnswerParams" + AnswerResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingAnswerResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/AnswerResult" + EndParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + reason: + description: Why the call is ending. + default: hangup + allOf: + - $ref: "#/components/schemas/CallEndReason" + required: + - node_id + - call_id + CallEndReason: + type: string + enum: + - hangup + - cancel + - busy + - noAnswer + - decline + - error + CallingEndRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.end + params: + $ref: "#/components/schemas/EndParams" + EndResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingEndResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/EndResult" + ConnectParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + ringback: + type: array + items: + $ref: "#/components/schemas/Ringback" + description: Audio to play to the caller while connecting. + tag: + type: string + description: Identifier added to created calls' events. + devices: + type: array + items: + type: array + items: + $ref: "#/components/schemas/ConnectDevice" + description: Devices to connect. Same sequential/parallel topology as `calling.dial`. + max_duration: + type: integer + format: int32 + description: Maximum duration once connected, in MINUTES. + max_price_per_minute: + type: number + format: double + description: Maximum price per minute willing to be paid. + status_url: + type: string + format: uri + description: URL to POST connect events to. + required: + - node_id + - call_id + - devices + Ringback: + type: object + properties: + type: + type: string + required: + - type + description: Audio played to the caller while a connect is in progress. Discriminated on `type`. + discriminator: type + RingbackAudio: + allOf: + - $ref: "#/components/schemas/Ringback" + - type: object + properties: + type: + type: string + const: audio + params: + type: object + properties: + url: + type: string + format: uri + description: Audio file URL. + required: + - url + required: + - type + - params + RingbackTts: + allOf: + - $ref: "#/components/schemas/Ringback" + - type: object + properties: + type: + type: string + const: tts + params: + type: object + properties: + text: + type: string + description: Text to speak (plain or SSML). + language: + type: string + description: TTS language. + default: en-US + gender: + description: TTS voice gender. + default: female + allOf: + - $ref: "#/components/schemas/TtsGender" + required: + - text + required: + - type + - params + TtsGender: + type: string + enum: + - male + - female + RingbackSilence: + allOf: + - $ref: "#/components/schemas/Ringback" + - type: object + properties: + type: + type: string + const: silence + params: + type: object + properties: + duration: + type: number + format: double + description: Seconds of silence. + required: + - duration + required: + - type + - params + RingbackRingtone: + allOf: + - $ref: "#/components/schemas/Ringback" + - type: object + properties: + type: + type: string + const: ringtone + params: + type: object + properties: + name: + description: Tone name (country code). + allOf: + - $ref: "#/components/schemas/ToneName" + duration: + type: number + format: double + description: Seconds to play. + exclusiveMinimum: 0 + required: + - name + required: + - type + - params + ToneName: + type: string + enum: + - at + - au + - bg + - br + - be + - ch + - cl + - cn + - cz + - de + - dk + - ee + - es + - fi + - fr + - gr + - hu + - il + - in + - it + - lt + - jp + - mx + - my + - nl + - no + - nz + - ph + - pl + - pt + - ru + - se + - sg + - th + - uk + - us + - tw + - ve + - za + ConnectDevice: + type: object + properties: + type: + type: string + required: + - type + description: A device to connect to an active call (`calling.connect`). Discriminated on `type`. + discriminator: type + ConnectCallDevice: + allOf: + - $ref: "#/components/schemas/ConnectDevice" + - type: object + properties: + type: + type: string + const: call + params: + $ref: "#/components/schemas/CallRefDeviceParams" + required: + - type + - params + CallRefDeviceParams: + type: object + properties: + node_id: + type: string + description: Node of the existing call. + call_id: + type: string + description: Existing call id. + required: + - node_id + - call_id + description: "`call` device params (connect only) — bridge to an existing call." + ConnectQueueDevice: + allOf: + - $ref: "#/components/schemas/ConnectDevice" + - type: object + properties: + type: + type: string + const: queue + params: + $ref: "#/components/schemas/QueueDeviceParams" + required: + - type + - params + QueueDeviceParams: + type: object + properties: + node_id: + type: string + description: Node of the queue. + queue_name: + type: string + description: Queue name. + queue_id: + type: string + description: Queue id. + required: + - node_id + - queue_name + description: "`queue` device params (connect only) — pull a call from a queue." + ConnectPhoneDevice: + allOf: + - $ref: "#/components/schemas/ConnectDevice" + - type: object + properties: + type: + type: string + const: phone + params: + $ref: "#/components/schemas/PhoneDeviceParams" + required: + - type + - params + ConnectSipDevice: + allOf: + - $ref: "#/components/schemas/ConnectDevice" + - type: object + properties: + type: + type: string + const: sip + params: + $ref: "#/components/schemas/SipDeviceParams" + required: + - type + - params + ConnectWebrtcDevice: + allOf: + - $ref: "#/components/schemas/ConnectDevice" + - type: object + properties: + type: + type: string + const: webrtc + params: + $ref: "#/components/schemas/WebrtcDeviceParams" + required: + - type + - params + ConnectStreamDevice: + allOf: + - $ref: "#/components/schemas/ConnectDevice" + - type: object + properties: + type: + type: string + const: stream + params: + $ref: "#/components/schemas/StreamDeviceParams" + required: + - type + - params + StreamDeviceParams: + type: object + properties: + url: + type: string + format: uri + description: Stream target — `wss://` required. + name: + type: string + description: Optional stream name. + codec: + type: string + description: |- + Codec, optionally with rate/ptime modifiers (e.g. `PCMU@40i`, + `L16@24000h@40i`). One of `PCMU|PCMA|G722|L16`. Default `PCMU`. + default: PCMU + status_url: + type: string + format: uri + description: Webhook for stream status. + status_url_method: + type: string + enum: + - GET + - POST + description: HTTP method for `status_url`. + default: POST + realtime: + type: boolean + description: Stream realtime audio. + default: false + authorization_bearer_token: + type: string + description: Bearer token sent to the stream endpoint. + custom_parameters: + type: object + additionalProperties: {} + description: Arbitrary custom parameters forwarded to the stream endpoint. + required: + - url + description: "`stream` device params (connect only) — bidirectional audio to a WS endpoint." + CallingConnectRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.connect + params: + $ref: "#/components/schemas/ConnectParams" + ConnectResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingConnectResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/ConnectResult" + DisconnectParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + required: + - node_id + - call_id + CallingDisconnectRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.disconnect + params: + $ref: "#/components/schemas/DisconnectParams" + DisconnectResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingDisconnectResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/DisconnectResult" + CollectParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier added to the created collect events. + initial_timeout: + type: number + format: double + description: |- + Seconds to wait for initial input. Used only when `start_input_timers: true`. + Default `4.0`. + exclusiveMinimum: 0 + default: 4 + digits: + description: Digit-collection settings. Required if `speech` is not set. + allOf: + - $ref: "#/components/schemas/CollectDigits" + speech: + description: Speech-collection settings. Required if `digits` is not set. + allOf: + - $ref: "#/components/schemas/CollectSpeech" + partial_results: + type: boolean + description: If true, partial-result events are fired. Default `false`. + default: false + continuous: + type: boolean + description: |- + If true, utterances and digits are detected continuously until the collect is + stopped. Default `false`. + default: false + send_start_of_input: + type: boolean + description: If true, the `start_of_input` event is fired when input is detected. Default `false`. + default: false + start_input_timers: + type: boolean + description: If true, the `initial_timeout` timer is started immediately. Default `false`. + default: false + status_url: + type: string + format: uri + description: HTTP(s) URL to POST collect events to. + required: + - node_id + - call_id + - control_id + CollectDigits: + type: object + properties: + max: + type: integer + format: int32 + description: Maximum number of digits to collect. Positive integer. + minimum: 1 + terminators: + type: string + description: Digits that terminate collection (e.g. `"#*"`). Default not set. + digit_timeout: + type: number + format: double + description: |- + Maximum seconds to wait for the next digit after a digit is received. + Default `5.0`. + exclusiveMinimum: 0 + default: 5 + required: + - max + description: DTMF-digit collection settings. + CollectSpeech: + type: object + properties: + end_silence_timeout: + type: number + format: double + description: Silence (seconds) to wait for before declaring end of speech. Default `1`. + exclusiveMinimum: 0 + default: 1 + speech_timeout: + type: number + format: double + description: Maximum seconds to collect speech. Default `60`. + exclusiveMinimum: 0 + default: 60 + language: + type: string + description: Language to detect. Default `en-US`. + default: en-US + hints: + type: array + items: + type: string + description: Expected phrases to bias detection toward. Default not set. + engine: + description: Force a specific speech-recognition engine. Default unset (auto-selected). + allOf: + - $ref: "#/components/schemas/CollectSpeechEngine" + description: Speech-recognition collection settings. + CollectSpeechEngine: + type: string + enum: + - Deepgram + - Google + CallingCollectRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.collect + params: + $ref: "#/components/schemas/CollectParams" + CollectResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echoes the `control_id` from the params. + required: + - code + - message + CallingCollectResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/CollectResult" + CollectStopParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The `control_id` assigned in `calling.collect`. + required: + - node_id + - call_id + - control_id + CallingCollectStopRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.collect.stop + params: + $ref: "#/components/schemas/CollectStopParams" + CollectStopResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingCollectStopResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/CollectStopResult" + CollectStartInputTimersParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The `control_id` assigned in `calling.collect`. + required: + - node_id + - call_id + - control_id + CallingCollectStartInputTimersRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.collect.start_input_timers + params: + $ref: "#/components/schemas/CollectStartInputTimersParams" + CollectStartInputTimersResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingCollectStartInputTimersResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/CollectStartInputTimersResult" + PlayAndCollectParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier added to the created play-and-collect events. + volume: + type: number + format: double + description: |- + Playback volume in dB, from `-40` (muted) to `+40`, where `0` is the original + audio. Follows the standard amplitude voltage gain factor: `10 ^ (value / 20)`. + minimum: -40 + maximum: 40 + play: + type: array + items: + $ref: "#/components/schemas/PlayMedia" + description: Media elements to play. + collect: + description: Collection settings applied while playing. + allOf: + - $ref: "#/components/schemas/PlayAndCollectCollect" + status_url: + type: string + format: uri + description: HTTP(s) URL to POST play-and-collect events to. + required: + - node_id + - call_id + - control_id + - play + - collect + PlayMedia: + type: object + properties: + type: + type: string + required: + - type + description: A media element to play. Discriminated on `type`. + discriminator: type + PlayMediaAudio: + allOf: + - $ref: "#/components/schemas/PlayMedia" + - type: object + properties: + type: + type: string + const: audio + params: + type: object + properties: + url: + type: string + format: uri + description: HTTP(s) URL to the audio resource to play. + required: + - url + required: + - type + - params + PlayMediaTts: + allOf: + - $ref: "#/components/schemas/PlayMedia" + - type: object + properties: + type: + type: string + const: tts + params: + type: object + properties: + text: + type: string + description: Text to speak — plain text or SSML markup. + language: + type: string + description: TTS language (e.g. `en-US`). Default `en-US`. + default: en-US + gender: + description: TTS voice gender. Default `female`. + default: female + allOf: + - $ref: "#/components/schemas/TtsGender" + voice: + type: string + description: Specific voice to use. Highest precedence when selecting the TTS voice. + required: + - text + required: + - type + - params + PlayMediaSilence: + allOf: + - $ref: "#/components/schemas/PlayMedia" + - type: object + properties: + type: + type: string + const: silence + params: + type: object + properties: + duration: + type: number + format: double + description: Seconds of silence to play. + exclusiveMinimum: 0 + required: + - duration + required: + - type + - params + PlayMediaRingtone: + allOf: + - $ref: "#/components/schemas/PlayMedia" + - type: object + properties: + type: + type: string + const: ringtone + params: + type: object + properties: + name: + description: Built-in ringtone name (country code). + allOf: + - $ref: "#/components/schemas/ToneName" + duration: + type: number + format: double + description: Seconds of ringtone to play. + exclusiveMinimum: 0 + required: + - name + required: + - type + - params + PlayAndCollectCollect: + type: object + properties: + initial_timeout: + type: number + format: double + description: Seconds to wait for initial input. Default `4.0`. + exclusiveMinimum: 0 + default: 4 + digits: + description: Digit-collection settings. Required if `speech` is not set. + allOf: + - $ref: "#/components/schemas/CollectDigits" + speech: + description: Speech-collection settings. Required if `digits` is not set. + allOf: + - $ref: "#/components/schemas/CollectSpeech" + description: The `collect` block of `calling.play_and_collect`. At least one of `digits`/`speech`. + CallingPlayAndCollectRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.play_and_collect + params: + $ref: "#/components/schemas/PlayAndCollectParams" + PlayAndCollectResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echoes the `control_id` from the params. + required: + - code + - message + CallingPlayAndCollectResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/PlayAndCollectResult" + PlayAndCollectStopParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The `control_id` assigned in `calling.play_and_collect`. + required: + - node_id + - call_id + - control_id + CallingPlayAndCollectStopRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.play_and_collect.stop + params: + $ref: "#/components/schemas/PlayAndCollectStopParams" + PlayAndCollectStopResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingPlayAndCollectStopResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/PlayAndCollectStopResult" + PlayAndCollectVolumeParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The `control_id` assigned in `calling.play_and_collect`. + volume: + type: number + format: double + description: |- + New playback volume in dB, from `-40` (muted) to `+40`, where `0` is the + original audio. Follows the standard amplitude voltage gain factor: + `10 ^ (value / 20)`. + minimum: -40 + maximum: 40 + required: + - node_id + - call_id + - control_id + - volume + CallingPlayAndCollectVolumeRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.play_and_collect.volume + params: + $ref: "#/components/schemas/PlayAndCollectVolumeParams" + PlayAndCollectVolumeResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingPlayAndCollectVolumeResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/PlayAndCollectVolumeResult" + QueueEnterParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier used to control this queue placement. + queue_name: + type: string + description: |- + Name of the queue to place the call in. If it does not exist, a new queue is + created and the call becomes first in it. + status_url: + type: string + format: uri + description: HTTP(S) URL to deliver RELAY queue event callbacks to. + required: + - node_id + - call_id + - control_id + - queue_name + CallingQueueEnterRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.queue.enter + params: + $ref: "#/components/schemas/QueueEnterParams" + QueueEnterResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the `control_id` supplied in the request. + required: + - code + - message + CallingQueueEnterResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/QueueEnterResult" + QueueLeaveParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier used to control this queue placement. + queue_name: + type: string + description: Name of the queue to remove the call from. + queue_id: + type: string + description: ID of the queue to remove the call from. + status_url: + type: string + format: uri + description: HTTP(S) URL to deliver RELAY queue event callbacks to. + required: + - node_id + - call_id + - control_id + - queue_name + CallingQueueLeaveRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.queue.leave + params: + $ref: "#/components/schemas/QueueLeaveParams" + QueueLeaveResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the `control_id` supplied in the request. + required: + - code + - message + CallingQueueLeaveResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/QueueLeaveResult" + RecordParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier used to control active recordings. + record: + description: The recording spec (subobject-keyed; only `audio` is documented). + allOf: + - $ref: "#/components/schemas/RecordSpec" + status_url: + type: string + format: uri + description: HTTP(S) URL to deliver RELAY recording event callbacks to. + required: + - node_id + - call_id + - control_id + - record + RecordSpec: + type: object + properties: + audio: + description: Audio-recording parameters. + allOf: + - $ref: "#/components/schemas/RecordAudio" + required: + - audio + description: |- + Recording spec. Keyed by subobject name (`audio`) rather than a `type` + discriminator; only the `audio` variant is documented. + RecordAudio: + type: object + properties: + beep: + type: boolean + description: Play a beep before recording starts. Default `false`. + default: false + format: + type: string + enum: + - mp3 + - wav + description: Output file format. Default `mp3`. + default: mp3 + stereo: + type: boolean + description: Record the two call directions on separate channels. Default `false`. + default: false + direction: + description: Which audio direction(s) to capture. Default `speak`. + default: speak + allOf: + - $ref: "#/components/schemas/RecordAudioDirection" + initial_timeout: + type: number + format: double + description: |- + Seconds to wait until something is heard before giving up. Disable with `0`. + Default `5.0`. + minimum: 0 + default: 5 + end_silence_timeout: + type: number + format: double + description: |- + Seconds of silence to wait after the call party stops speaking before ending + the recording. Disable with `0`. Default `1.0`. + minimum: 0 + default: 1 + terminators: + type: string + description: DTMF digits that end the recording. Default `#*`. + default: "#*" + input_sensitivity: + type: number + format: double + description: |- + Input sensitivity: `0` = hear nothing, `100` = hear everything. Default + `44`. + minimum: 0 + maximum: 100 + default: 44 + description: Audio-recording parameters (the `record.audio` subobject). + RecordAudioDirection: + type: string + enum: + - listen + - speak + - both + CallingRecordRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.record + params: + $ref: "#/components/schemas/RecordParams" + RecordResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the `control_id` supplied in the request. + url: + type: string + format: uri + description: URL of the resulting recording. + required: + - code + - message + CallingRecordResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/RecordResult" + RecordPauseParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The `control_id` assigned in `calling.record`. + behavior: + description: Behavior of the recording while paused. Default `skip`. + default: skip + allOf: + - $ref: "#/components/schemas/RecordPauseBehavior" + required: + - node_id + - call_id + - control_id + RecordPauseBehavior: + type: string + enum: + - skip + - silence + CallingRecordPauseRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.record.pause + params: + $ref: "#/components/schemas/RecordPauseParams" + RecordPauseResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the `control_id` supplied in the request. + required: + - code + - message + CallingRecordPauseResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/RecordPauseResult" + RecordResumeParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The `control_id` assigned in `calling.record`. + required: + - node_id + - call_id + - control_id + CallingRecordResumeRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.record.resume + params: + $ref: "#/components/schemas/RecordResumeParams" + RecordResumeResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the `control_id` supplied in the request. + required: + - code + - message + CallingRecordResumeResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/RecordResumeResult" + RecordStopParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The `control_id` assigned in `calling.record`. + required: + - node_id + - call_id + - control_id + CallingRecordStopRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.record.stop + params: + $ref: "#/components/schemas/RecordStopParams" + RecordStopResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the `control_id` supplied in the request. + required: + - code + - message + CallingRecordStopResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/RecordStopResult" + ReferParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + device: + description: The device to transfer the call to (only `sip` is valid). + allOf: + - $ref: "#/components/schemas/ReferDevice" + status_url: + type: string + format: uri + description: HTTP(S) URL to POST refer events to. + required: + - node_id + - call_id + - device + ReferDevice: + type: object + properties: + type: + type: string + required: + - type + description: Target device for a SIP REFER transfer. Discriminated on `type` (`sip` only). + discriminator: type + ReferSipDevice: + allOf: + - $ref: "#/components/schemas/ReferDevice" + - type: object + properties: + type: + type: string + const: sip + params: + $ref: "#/components/schemas/ReferSipDeviceParams" + required: + - type + - params + ReferSipDeviceParams: + type: object + properties: + to: + type: string + description: SIP URI to transfer the call to (e.g. `userb@example.com`). + username: + type: string + description: Username used to authenticate the REFER request. + password: + type: string + description: Password used to authenticate the REFER request. + required: + - to + description: "`sip` REFER device params." + CallingReferRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.refer + params: + $ref: "#/components/schemas/ReferParams" + ReferResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingReferResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/ReferResult" + PassParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + required: + - node_id + - call_id + CallingPassRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.pass + params: + $ref: "#/components/schemas/PassParams" + PassResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingPassResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/PassResult" + PayParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier used to control this active pay (e.g. `calling.pay.stop`). + input: + description: How payment details are collected. Default `dtmf`. (Only `dtmf` supported.) + default: dtmf + allOf: + - $ref: "#/components/schemas/PayInput" + status_url: + type: string + format: uri + description: URL to request on each status change during the payment process. + payment_method: + description: Payment method to use. Default `credit-card`. (Only `credit-card` supported.) + default: credit-card + allOf: + - $ref: "#/components/schemas/PayMethod" + bank_account_type: + description: |- + Bank account type (relevant only for `ach-debit`). Undocumented in the + protocol reference but accepted by the gateway. Default `consumer-checking`. + default: consumer-checking + allOf: + - $ref: "#/components/schemas/PayBankAccountType" + timeout: + type: integer + format: int32 + description: |- + Seconds the Pay IVR waits for the next digit before validating the captured + digits. Default `5`. (Sent as a JSON string on the wire, e.g. `"6"`.) + minimum: 0 + default: 5 + max_attempts: + type: integer + format: int32 + description: |- + Number of times the Pay IVR retries when collecting card details. Default + `1`. (Sent as a JSON string on the wire, e.g. `"3"`.) + minimum: 1 + default: 1 + security_code: + type: boolean + description: |- + Whether to prompt for the card security code. Default `true`. (Sent as a JSON + string on the wire, e.g. `"false"`.) + default: true + postal_code: + type: boolean + description: |- + Whether to prompt for the billing postal code. Default `true`. (Sent as a + JSON string on the wire, e.g. `"false"`. A known postcode may instead be + supplied so the IVR skips the prompt — see open questions.) + default: true + min_postal_code_length: + type: integer + format: int32 + description: |- + Minimum number of digits a caller must enter for the postal code. Default + `0`. (Sent as a JSON string on the wire, e.g. `"6"`.) + minimum: 0 + default: 0 + payment_connector_url: + type: string + format: uri + description: URL to POST collected payment details to upon completion. + token_type: + description: Whether the payment token is one-off or reusable. Default `reusable`. + default: reusable + allOf: + - $ref: "#/components/schemas/PayTokenType" + charge_amount: + type: string + description: |- + Amount to charge against the payment method. Decimal value with no currency + prefix, passed as a string (e.g. `"15.00"`). Default `"0.00"`. + default: "0.00" + currency: + type: string + description: Currency of the charge amount. Default `usd`. + default: usd + language: + type: string + description: Language for prompts played to the caller. Default `en-US`. + default: en-US + voice: + type: string + description: |- + Text-to-speech voice for prompts (free-form; passed through to TTS, e.g. + `woman`, `man`, `polly.Sally`). Default `woman`. + default: woman + description: + type: string + description: Custom description of the payment. + valid_card_types: + type: string + description: |- + SPACE-DELIMITED list of card types allowed in this payment (not an array) — + subset of `visa mastercard amex maestro discover jcb diners-club`. Default + `"visa mastercard amex"`. + default: visa mastercard amex + parameters: + type: array + items: + $ref: "#/components/schemas/PayParameter" + description: Additional name/value pairs to POST to the payment connector. + prompts: + type: array + items: + $ref: "#/components/schemas/PayPrompt" + description: Custom prompts that override the IVR defaults. + required: + - node_id + - call_id + - control_id + - payment_connector_url + PayInput: + type: string + enum: + - dtmf + - speech + PayMethod: + type: string + enum: + - credit-card + - ach-debit + PayBankAccountType: + type: string + enum: + - consumer-checking + - consumer-savings + - commercial-checking + PayTokenType: + type: string + enum: + - one-time + - reusable + PayParameter: + type: object + properties: + name: + type: string + description: Parameter name. + value: + type: string + description: Parameter value. + required: + - name + - value + description: A name/value pair POSTed to the payment connector alongside payment details. + PayPrompt: + type: object + properties: + for: + description: The situation this prompt applies to. + allOf: + - $ref: "#/components/schemas/PayPromptFor" + card_type: + type: string + description: |- + Space-delimited card-type tokens this prompt applies to (subset of + `visa mastercard amex maestro discover jcb diners-club`). Applies to all + card types if unset. + error_type: + type: string + description: |- + Space-delimited error-type tokens this prompt applies to. Documented tokens: + timeout, invalid-card-number, invalid-card-type, invalid-date, + invalid-security-code, invalid-postal-code, session-in-progress, + card-declined. (The gateway parser additionally recognizes + invalid-bank-routing-number, invalid-bank-account-number, and + input-matching-failed.) + actions: + type: array + items: + $ref: "#/components/schemas/PayPromptAction" + description: Actions to execute for this prompt. + required: + - for + - actions + description: |- + A custom prompt overriding the Pay IVR default for a given situation. + + `card_type` and `error_type` are SPACE-DELIMITED token strings on the wire (not + arrays) — e.g. `error_type: "timeout invalid-card-number invalid-card-type"`. + PayPromptFor: + type: string + enum: + - payment-card-number + - expiration-date + - security-code + - postal-code + - bank-routing-number + - bank-account-number + - payment-processing + - payment-completed + - payment-failed + - payment-canceled + PayPromptAction: + type: object + properties: + type: + description: "`Say` for text-to-speech, `Play` for playing an audio file." + allOf: + - $ref: "#/components/schemas/PayPromptActionType" + phrase: + type: string + description: Sentence to speak (for `Say`) or audio URL to play (for `Play`). + required: + - type + - phrase + description: A single action (Say/Play) executed when a custom prompt is reached. + PayPromptActionType: + type: string + enum: + - Say + - Play + CallingPayRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.pay + params: + $ref: "#/components/schemas/PayParams" + PayResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the `control_id` from the request. + required: + - code + - message + CallingPayResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/PayResult" + PayStopParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The `control_id` assigned in `calling.pay`. + required: + - node_id + - call_id + - control_id + CallingPayStopRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.pay.stop + params: + $ref: "#/components/schemas/PayStopParams" + PayStopResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingPayStopResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/PayStopResult" + PlayParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier used to control this active play (pause/resume/stop/volume). + volume: + type: number + format: double + description: |- + Playback volume, -40dB to +40dB (`0` = original audio, `-40` = muted; + amplitude gain factor `10^(value/20)`). + minimum: -40 + maximum: 40 + direction: + type: string + enum: + - listen + - speak + - both + description: Which side of the call hears the media. Default `listen`. + default: listen + status_url: + type: string + format: uri + description: HTTP(s) URL to POST play events to. + play: + type: array + items: + $ref: "#/components/schemas/PlayMedia" + description: Ordered list of media elements to play. + loop: + type: integer + format: int32 + description: |- + Number of times to play the sequence. `0` loops until the call ends or the + play is stopped. Default `1`. + minimum: 0 + default: 1 + required: + - node_id + - call_id + - control_id + - play + CallingPlayRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.play + params: + $ref: "#/components/schemas/PlayParams" + PlayResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the `control_id` from the request. + required: + - code + - message + CallingPlayResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/PlayResult" + PlayPauseParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The playing `control_id` assigned in `calling.play`. + required: + - node_id + - call_id + - control_id + CallingPlayPauseRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.play.pause + params: + $ref: "#/components/schemas/PlayPauseParams" + PlayPauseResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingPlayPauseResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/PlayPauseResult" + PlayResumeParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The playing `control_id` assigned in `calling.play`. + required: + - node_id + - call_id + - control_id + CallingPlayResumeRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.play.resume + params: + $ref: "#/components/schemas/PlayResumeParams" + PlayResumeResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingPlayResumeResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/PlayResumeResult" + PlayStopParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The `control_id` assigned in `calling.play`. + required: + - node_id + - call_id + - control_id + CallingPlayStopRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.play.stop + params: + $ref: "#/components/schemas/PlayStopParams" + PlayStopResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingPlayStopResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/PlayStopResult" + PlayVolumeParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The `control_id` assigned in `calling.play`. + volume: + type: number + format: double + description: |- + Playback volume, -40dB to +40dB (`0` = original audio, `-40` = muted; + amplitude gain factor `10^(value/20)`). + minimum: -40 + maximum: 40 + required: + - node_id + - call_id + - control_id + - volume + CallingPlayVolumeRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.play.volume + params: + $ref: "#/components/schemas/PlayVolumeParams" + PlayVolumeResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingPlayVolumeResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/PlayVolumeResult" + DetectParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier used to control the active detector. + detect: + description: Detector to run (variant keyed on `detect.type`). + allOf: + - $ref: "#/components/schemas/DetectConfig" + timeout: + type: number + format: double + description: Maximum time (sec >= 0) to run the detector. Default `30.0`. + minimum: 0 + status_url: + type: string + format: uri + description: HTTP(s) URL to POST detector events to. + required: + - node_id + - call_id + - control_id + - detect + DetectConfig: + type: object + properties: + type: + type: string + required: + - type + description: Detector to start. Discriminated on `type` (`machine`|`fax`|`digit`). + discriminator: type + DetectMachine: + allOf: + - $ref: "#/components/schemas/DetectConfig" + - type: object + properties: + type: + type: string + const: machine + params: + $ref: "#/components/schemas/DetectMachineParams" + required: + - type + DetectMachineParams: + type: object + properties: + initial_timeout: + type: number + format: double + description: How long to wait (sec > 0) for initial voice before giving up. Default `4.5`. + exclusiveMinimum: 0 + end_silence_timeout: + type: number + format: double + description: How long to wait (sec > 0) for voice to finish. Default `1.0`. + exclusiveMinimum: 0 + machine_ready_timeout: + type: number + format: double + description: |- + How long to wait (sec > 0) for voice to finish before firing the READY + event. Default is `end_silence_timeout`. + exclusiveMinimum: 0 + machine_voice_threshold: + type: number + format: double + description: |- + How much voice (sec > 0) to decide MACHINE. Default `1.25`. (Source says + "sec > 0" but the description says "in ms" — units to confirm.) + exclusiveMinimum: 0 + machine_words_threshold: + type: integer + format: int32 + description: How many words (count > 0) to count to decide MACHINE. Default `6`. + exclusiveMinimum: 0 + detect_interruptions: + type: boolean + description: |- + If true, a NOT_READY event is fired if VAD detects speech after READY. This + lets the application restart message delivery to the answering machine. + Default `false`. + default: false + detect_message_end: + type: boolean + description: |- + If false, stop detection on the machine event and don't wait on the beep / + end of the voicemail greeting. Default `true`. + default: true + description: "`machine` detector params (answering-machine / voicemail detection)." + DetectFax: + allOf: + - $ref: "#/components/schemas/DetectConfig" + - type: object + properties: + type: + type: string + const: fax + params: + $ref: "#/components/schemas/DetectFaxParams" + required: + - type + DetectFaxParams: + type: object + properties: + tone: + description: Tone to detect (remote side only). Default `CED`. + allOf: + - $ref: "#/components/schemas/DetectFaxTone" + description: "`fax` detector params." + DetectFaxTone: + type: string + enum: + - CED + - CNG + DetectDigit: + allOf: + - $ref: "#/components/schemas/DetectConfig" + - type: object + properties: + type: + type: string + const: digit + params: + $ref: "#/components/schemas/DetectDigitParams" + required: + - type + DetectDigitParams: + type: object + properties: + digits: + type: string + description: Digits to detect. Default `0123456789#*`. + description: "`digit` detector params." + CallingDetectRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.detect + params: + $ref: "#/components/schemas/DetectParams" + DetectResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the detector `control_id`. + call_id: + type: string + description: The call id. + required: + - code + - message + CallingDetectResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/DetectResult" + DetectStopParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The detector `control_id` assigned in `calling.detect`. + required: + - node_id + - call_id + - control_id + CallingDetectStopRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.detect.stop + params: + $ref: "#/components/schemas/DetectStopParams" + DetectStopResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the detector `control_id`. + call_id: + type: string + description: The call id. + required: + - code + - message + CallingDetectStopResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/DetectStopResult" + SendFaxParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier used to control the active faxing. + document: + type: string + format: uri + description: Location of the fax document to send. PDF format only. + identity: + type: string + description: Identity to display on the receiving fax. Default is the SignalWire DID. + header_info: + type: string + description: |- + Custom info added to the header of each fax page (alongside identity, date, + and page number). `SignalWire` is the default. Set to empty string to + disable sending any header. + default: SignalWire + status_url: + type: string + format: uri + description: HTTP(s) URL to POST fax events to. + required: + - node_id + - call_id + - control_id + - document + CallingSendFaxRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.send_fax + params: + $ref: "#/components/schemas/SendFaxParams" + SendFaxResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the fax `control_id`. + call_id: + type: string + description: The call id. + required: + - code + - message + CallingSendFaxResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/SendFaxResult" + SendFaxStopParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The send-fax `control_id` assigned in `calling.send_fax`. + required: + - node_id + - call_id + - control_id + CallingSendFaxStopRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.send_fax.stop + params: + $ref: "#/components/schemas/SendFaxStopParams" + SendFaxStopResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the fax `control_id`. + call_id: + type: string + description: The call id. + required: + - code + - message + CallingSendFaxStopResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/SendFaxStopResult" + ReceiveFaxParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier used to control the active faxing. + status_url: + type: string + format: uri + description: HTTP(s) URL to POST fax events to. + required: + - node_id + - call_id + - control_id + CallingReceiveFaxRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.receive_fax + params: + $ref: "#/components/schemas/ReceiveFaxParams" + ReceiveFaxResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the fax `control_id`. + call_id: + type: string + description: The call id. + required: + - code + - message + CallingReceiveFaxResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/ReceiveFaxResult" + ReceiveFaxStopParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The fax `control_id` assigned in `calling.receive_fax`. + required: + - node_id + - call_id + - control_id + CallingReceiveFaxStopRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.receive_fax.stop + params: + $ref: "#/components/schemas/ReceiveFaxStopParams" + ReceiveFaxStopResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the fax `control_id`. + call_id: + type: string + description: The call id. + required: + - code + - message + CallingReceiveFaxStopResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/ReceiveFaxStopResult" + TapParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier used to control the active tap. + tap: + description: Media to intercept (variant keyed on `tap.type`). + allOf: + - $ref: "#/components/schemas/TapConfig" + device: + description: Device to receive the tapped media (variant keyed on `device.type`). + allOf: + - $ref: "#/components/schemas/TapDevice" + status_url: + type: string + format: uri + description: HTTP(s) URL to POST tap events to. + required: + - node_id + - call_id + - control_id + - tap + - device + TapConfig: + type: object + properties: + type: + type: string + required: + - type + description: Media to intercept. Discriminated on `type` (documented value `audio`). + discriminator: type + TapAudio: + allOf: + - $ref: "#/components/schemas/TapConfig" + - type: object + properties: + type: + type: string + const: audio + params: + $ref: "#/components/schemas/TapAudioParams" + required: + - type + - params + TapAudioParams: + type: object + properties: + direction: + description: Side of the call to tap. Default `speak`. + default: speak + allOf: + - $ref: "#/components/schemas/TapDirection" + description: "`audio` tap params." + TapDirection: + type: string + enum: + - listen + - speak + - both + TapDevice: + type: object + properties: + type: + type: string + required: + - type + description: |- + Device to receive the tapped media. Discriminated on `type` (`rtp`|`ws`; + future: `phone`|`webrtc`|`sip`). Echoed back fully-resolved as the result's + `source_device`. + discriminator: type + TapRtpDevice: + allOf: + - $ref: "#/components/schemas/TapDevice" + - type: object + properties: + type: + type: string + const: rtp + params: + $ref: "#/components/schemas/TapRtpDeviceParams" + required: + - type + - params + TapRtpDeviceParams: + type: object + properties: + addr: + type: string + description: |- + RTP IPv4 address. Must be an IP owned by the customer or expecting our + traffic; specifying a private IP or a SignalWire-owned public IP is + forbidden. + port: + type: integer + format: int32 + description: RTP port. + codec: + description: Codec — matches the tapped audio if not set. + allOf: + - $ref: "#/components/schemas/TapCodec" + ptime: + type: integer + format: int32 + description: Packetization time in ms — matches the tapped audio if not set. + rate: + type: integer + format: int32 + description: Sample rate in Hz (present in the resolved `source_device` echo). + required: + - addr + - port + description: "`rtp` device params (delivery target)." + TapCodec: + type: string + enum: + - OPUS + - PCMA + - PCMU + TapWsDevice: + allOf: + - $ref: "#/components/schemas/TapDevice" + - type: object + properties: + type: + type: string + const: ws + params: + $ref: "#/components/schemas/TapWsDeviceParams" + required: + - type + - params + TapWsDeviceParams: + type: object + properties: + uri: + type: string + description: WebSocket URI. + codec: + description: Codec — matches the tapped audio if not set. + allOf: + - $ref: "#/components/schemas/TapCodec" + rate: + type: integer + format: int32 + description: Sample rate in Hz — matches the tapped audio if not set. + required: + - uri + description: "`ws` device params (delivery target)." + CallingTapRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.tap + params: + $ref: "#/components/schemas/TapParams" + TapResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Echo of the tap `control_id`. + source_device: + description: |- + The source device with all params filled in, so the destination knows what + is being delivered (offer/answer model). + allOf: + - $ref: "#/components/schemas/TapDevice" + required: + - code + - message + CallingTapResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/TapResult" + TapStopParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The tap `control_id` assigned in `calling.tap`. + required: + - node_id + - call_id + - control_id + CallingTapStopRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.tap.stop + params: + $ref: "#/components/schemas/TapStopParams" + TapStopResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the tap `control_id`. + call_id: + type: string + description: The call id. + required: + - code + - message + CallingTapStopResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/TapStopResult" + StreamParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier used to control the active stream. + url: + type: string + format: uri + description: WebSocket URI (`wss://`) to stream audio to. + name: + type: string + description: A friendly name for the stream. + codec: + type: string + description: Codec for the streamed audio. Default is the call's native codec. + track: + description: |- + Which audio track to stream. `inbound_track` (what the caller says), + `outbound_track` (what the caller hears), or `both_tracks`. Default + `inbound_track`. + default: inbound_track + allOf: + - $ref: "#/components/schemas/StreamTrack" + status_url: + type: string + format: uri + description: HTTP(s) URL to POST stream status events to. + status_url_method: + type: string + enum: + - GET + - POST + description: HTTP method for `status_url`. Default `POST`. + default: POST + authorization_bearer_token: + type: string + description: Bearer token to include in the WebSocket connection. + custom_parameters: + type: object + additionalProperties: {} + description: |- + JSON object of custom key-value pairs sent to the WebSocket endpoint on + connect. + required: + - node_id + - call_id + - control_id + - url + StreamTrack: + type: string + enum: + - inbound_track + - outbound_track + - both_tracks + CallingStreamRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.stream + params: + $ref: "#/components/schemas/StreamParams" + StreamResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the stream `control_id`. + node_id: + type: string + description: Node the call is on (this method echoes `node_id`, not `call_id`). + required: + - code + - message + CallingStreamResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/StreamResult" + StreamStopParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The stream `control_id` assigned in `calling.stream`. + required: + - node_id + - call_id + - control_id + CallingStreamStopRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.stream.stop + params: + $ref: "#/components/schemas/StreamStopParams" + StreamStopResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: Echo of the stream `control_id`. + call_id: + type: string + description: The call id. + required: + - code + - message + CallingStreamStopResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/StreamStopResult" + TransferParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + dest: + type: string + description: |- + Where to transfer call control. One of: an `https://` script URL to POST, an + inline SWML script, or a relay application prefixed with `context:`. A single + wire string — polymorphic by prefix/scheme. + required: + - node_id + - call_id + - dest + CallingTransferRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.transfer + params: + $ref: "#/components/schemas/TransferParams" + TransferResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + call_id: + type: string + description: The transferred call id (echoed). + required: + - code + - message + CallingTransferResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/TransferResult" + JoinConferenceParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + name: + type: string + description: Name of the conference to join. + muted: + type: boolean + description: Join muted. + default: false + beep: + description: Beep behaviour on enter/exit. Default `true`. + allOf: + - $ref: "#/components/schemas/ConferenceBeep" + start_on_enter: + type: boolean + description: Start the conference when this participant enters. + default: true + end_on_exit: + type: boolean + description: End the conference when this participant exits. + default: false + wait_url: + type: string + format: uri + description: "URL to CXML or an mp3/wav to play while waiting. Default: hold music." + max_participants: + type: integer + format: int32 + description: Maximum number of participants (positive, `<= 250`). Default `250`. + maximum: 250 + exclusiveMinimum: 0 + record: + description: Whether/when to record the conference. Default `do-not-record`. + allOf: + - $ref: "#/components/schemas/ConferenceRecord" + region: + description: Region the conference media is anchored in. Default `global`. + allOf: + - $ref: "#/components/schemas/ConferenceRegion" + trim: + description: Trim silence from the recording. Default `trim-silence`. + allOf: + - $ref: "#/components/schemas/ConferenceTrim" + coach: + type: string + description: "A SWML Call ID or CXML CallSid to coach. Default: not set." + status_callback: + type: string + format: uri + description: "URL to POST conference status callbacks to. Default: not set." + status_callback_event: + type: string + description: |- + Space-separated list of conference events to deliver to `status_callback`. + Tokens: `start end join leave mute hold modify speaker announcement`. + Default: not set. + status_callback_event_type: + description: Encoding of the status callback payload. Default `relay`. + allOf: + - $ref: "#/components/schemas/ConferenceCallbackEventType" + status_callback_method: + description: HTTP method for `status_callback`. Default `POST`. Ignored when `status_callback_event_type` is `relay`. + allOf: + - $ref: "#/components/schemas/ConferenceCallbackMethod" + recording_status_callback: + type: string + format: uri + description: "URL to POST recording status callbacks to. Default: not set." + recording_status_callback_event: + description: |- + Recording lifecycle events to deliver to `recording_status_callback`. + Default `completed`. (Example uses a space-separated token list, e.g. + `"in-progress completed"` — see openQuestions.) + allOf: + - $ref: "#/components/schemas/ConferenceRecordingCallbackEvent" + recording_status_callback_event_type: + description: Encoding of the recording status callback payload. Default `relay`. + allOf: + - $ref: "#/components/schemas/ConferenceCallbackEventType" + recording_status_callback_method: + description: HTTP method for `recording_status_callback`. Default `POST`. Ignored when `recording_status_callback_event_type` is `relay`. + allOf: + - $ref: "#/components/schemas/ConferenceCallbackMethod" + stream: + description: |- + Attach a bidirectional WebSocket stream to the conference. Reuses the same + `call_device_stream` schema as `calling.connect`'s stream device. + allOf: + - $ref: "#/components/schemas/StreamDeviceParams" + required: + - node_id + - call_id + - name + ConferenceBeep: + type: string + enum: + - "true" + - "false" + - onEnter + - onExit + ConferenceRecord: + type: string + enum: + - do-not-record + - record-from-start + ConferenceRegion: + type: string + enum: + - global + - us + - eu + ConferenceTrim: + type: string + enum: + - trim-silence + - do-not-trim + ConferenceCallbackEventType: + type: string + enum: + - relay + - cxml + ConferenceCallbackMethod: + type: string + enum: + - GET + - POST + ConferenceRecordingCallbackEvent: + type: string + enum: + - in-progress + - completed + - absent + CallingJoinConferenceRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.join_conference + params: + $ref: "#/components/schemas/JoinConferenceParams" + JoinConferenceResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingJoinConferenceResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/JoinConferenceResult" + LeaveConferenceParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + conference_id: + type: string + description: The conference identifier. Comes from `calling.conference` events. + required: + - node_id + - call_id + - conference_id + CallingLeaveConferenceRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.leave_conference + params: + $ref: "#/components/schemas/LeaveConferenceParams" + LeaveConferenceResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingLeaveConferenceResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/LeaveConferenceResult" + HoldParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + required: + - node_id + - call_id + CallingHoldRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.hold + params: + $ref: "#/components/schemas/HoldParams" + HoldResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + state: + type: string + description: Resulting hold state (`"hold"`). + required: + - code + - message + CallingHoldResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/HoldResult" + UnholdParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + required: + - node_id + - call_id + CallingUnholdRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.unhold + params: + $ref: "#/components/schemas/UnholdParams" + UnholdResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + state: + type: string + description: Resulting hold state (`"unhold"`). + required: + - code + - message + CallingUnholdResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/UnholdResult" + DenoiseParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + required: + - node_id + - call_id + CallingDenoiseRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.denoise + params: + $ref: "#/components/schemas/DenoiseParams" + DenoiseResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingDenoiseResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/DenoiseResult" + DenoiseStopParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + required: + - node_id + - call_id + CallingDenoiseStopRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.denoise.stop + params: + $ref: "#/components/schemas/DenoiseStopParams" + DenoiseStopResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingDenoiseStopResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/DenoiseStopResult" + SendDigitsParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: An identifier used to control the active send-digits operation. + digits: + type: string + description: |- + The string of digits to play. Allowed: `1234567890*#ABCD`, plus `w` (0.5s + wait) and `W` (1s wait), repeated for longer waits. Any invalid character + rejects the entire operation. + required: + - node_id + - call_id + - control_id + - digits + CallingSendDigitsRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.send_digits + params: + $ref: "#/components/schemas/SendDigitsParams" + SendDigitsResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + control_id: + type: string + description: The send-digits control id (echoed). + call_id: + type: string + description: The call id (echoed). + required: + - code + - message + CallingSendDigitsResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/SendDigitsResult" + TranscribeParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier used to control (e.g. stop) the active transcription. + status_url: + type: string + format: uri + description: http or https URL to deliver transcription status event callbacks to. + required: + - node_id + - call_id + - control_id + CallingTranscribeRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.transcribe + params: + $ref: "#/components/schemas/TranscribeParams" + TranscribeResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + url: + type: string + description: Path/URL of the shadow recording created for the transcription (e.g. `recordings/.wav`). + required: + - code + - message + CallingTranscribeResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/TranscribeResult" + TranscribeStopParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The `control_id` assigned in `calling.transcribe`. + required: + - node_id + - call_id + - control_id + CallingTranscribeStopRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.transcribe.stop + params: + $ref: "#/components/schemas/TranscribeStopParams" + TranscribeStopResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingTranscribeStopResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/TranscribeStopResult" + EchoParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + timeout: + type: integer + format: int32 + description: Echo duration in seconds (`0` = until the call ends). + minimum: 0 + status_url: + type: string + format: uri + description: http or https URL to deliver echo status event callbacks to. + required: + - node_id + - call_id + CallingEchoRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.echo + params: + $ref: "#/components/schemas/EchoParams" + EchoResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingEchoResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/EchoResult" + BindDigitParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + digits: + type: string + description: DTMF digit sequence to bind (e.g. `"*1"`). + bind_method: + type: string + description: Method name to invoke when the digits are pressed (e.g. `calling.play`). + params: + type: object + additionalProperties: {} + description: |- + Parameters to pass to the bound method. Free-form: the shape matches the + params model of `bind_method` (polymorphic by `bind_method`, no own + discriminator). Modeled loosely. + realm: + type: string + description: Namespace for this binding (used for selective clearing). + max_triggers: + type: integer + format: int32 + description: Maximum times this binding can fire (`0` = unlimited). + minimum: 0 + required: + - node_id + - call_id + - digits + - bind_method + CallingBindDigitRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.bind_digit + params: + $ref: "#/components/schemas/BindDigitParams" + BindDigitResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingBindDigitResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/BindDigitResult" + ClearDigitBindingsParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + realm: + type: string + description: Only clear bindings in this realm. Clears all bindings when omitted. + required: + - node_id + - call_id + CallingClearDigitBindingsRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.clear_digit_bindings + params: + $ref: "#/components/schemas/ClearDigitBindingsParams" + ClearDigitBindingsResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingClearDigitBindingsResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/ClearDigitBindingsResult" + LiveTranscribeParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + action: + description: Action to perform (provide exactly one of `start`/`stop`/`summarize`). + allOf: + - $ref: "#/components/schemas/LiveTranscribeAction" + required: + - node_id + - call_id + - action + LiveTranscribeAction: + type: object + properties: + start: + type: object + additionalProperties: {} + description: Begin live transcription. Sub-params undocumented. + stop: + type: object + additionalProperties: {} + description: Stop live transcription. Sub-params undocumented. + summarize: + type: object + additionalProperties: {} + description: Summarize the live transcription. Sub-params undocumented. + description: |- + Live-transcribe action. Key-discriminated: provide exactly one of `start`, + `stop`, or `summarize`. Inner sub-params are undocumented in the protocol + reference (loose-modeled). + CallingLiveTranscribeRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.live_transcribe + params: + $ref: "#/components/schemas/LiveTranscribeParams" + LiveTranscribeResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingLiveTranscribeResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/LiveTranscribeResult" + LiveTranslateParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + action: + description: Action to perform (provide exactly one of `start`/`stop`/`summarize`/`inject`). + allOf: + - $ref: "#/components/schemas/LiveTranslateAction" + status_url: + type: string + format: uri + description: http or https URL to deliver translation status event callbacks to. + required: + - node_id + - call_id + - action + LiveTranslateAction: + type: object + properties: + start: + type: object + additionalProperties: {} + description: Begin live translation. Sub-params undocumented. + stop: + type: object + additionalProperties: {} + description: Stop live translation. Sub-params undocumented. + summarize: + type: object + additionalProperties: {} + description: Summarize the live translation. Sub-params undocumented. + inject: + type: object + additionalProperties: {} + description: Inject content into the live translation. Sub-params undocumented. + description: |- + Live-translate action. Key-discriminated: provide exactly one of `start`, + `stop`, `summarize`, or `inject`. Inner sub-params are undocumented in the + protocol reference (loose-modeled). + CallingLiveTranslateRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.live_translate + params: + $ref: "#/components/schemas/LiveTranslateParams" + LiveTranslateResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingLiveTranslateResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/LiveTranslateResult" + JoinRoomParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + name: + type: string + description: Room name to join. + status_url: + type: string + format: uri + description: http or https URL to deliver room status event callbacks to. + required: + - node_id + - call_id + - name + CallingJoinRoomRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.join_room + params: + $ref: "#/components/schemas/JoinRoomParams" + JoinRoomResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingJoinRoomResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/JoinRoomResult" + LeaveRoomParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + required: + - node_id + - call_id + CallingLeaveRoomRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.leave_room + params: + $ref: "#/components/schemas/LeaveRoomParams" + LeaveRoomResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingLeaveRoomResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/LeaveRoomResult" + AiParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: Identifier used to control (e.g. stop) this AI session. + agent: + type: string + description: |- + Pre-configured agent UUID. If an inline `prompt` is also present, the inline + configuration takes precedence. + prompt: + description: Inline prompt configuration for the AI agent. + allOf: + - $ref: "#/components/schemas/AiPrompt" + post_prompt: + description: Post-conversation prompt configuration. + allOf: + - $ref: "#/components/schemas/AiPostPrompt" + post_prompt_url: + type: string + format: uri + description: URL to receive post-prompt status callbacks. + post_prompt_auth_user: + type: string + description: Basic-auth username for `post_prompt_url`. + post_prompt_auth_password: + type: string + description: Basic-auth password for `post_prompt_url`. + global_data: + type: object + additionalProperties: {} + description: Global data accessible to all SWAIG functions. + pronounce: + type: array + items: + $ref: "#/components/schemas/AiPronounce" + description: Global pronunciation rules. + hints: + type: array + items: + $ref: "#/components/schemas/AiHint" + description: Context hints biasing speech recognition. + languages: + type: array + items: + $ref: "#/components/schemas/AiLanguage" + description: Supported language configurations. + SWAIG: + description: SWAIG function configuration. + allOf: + - $ref: "#/components/schemas/AiSwaig" + params: + type: object + additionalProperties: {} + description: |- + Open-ended AI behavior parameters (ASR, TTS, turn detection, barge-in, LLM + config, video, …). Loose-modeled — the full enumeration lives with the SWML + `ai` verb. Example fields: `end_of_speech_timeout`, `attention_timeout` (ms). + required: + - node_id + - call_id + - control_id + AiPrompt: + type: object + properties: + text: + type: string + description: Instructions sent to the agent (plain text or SSML). + top_p: + type: number + format: double + description: Nucleus-sampling cutoff (0.0–1.0). Alternative to `temperature`. + minimum: 0 + maximum: 1 + temperature: + type: number + format: double + description: Randomness of generation (0.0–1.5). Lower is more deterministic. + minimum: 0 + maximum: 1.5 + confidence: + type: number + format: double + description: End-of-utterance speech-detect threshold (0.0–1.0). + minimum: 0 + maximum: 1 + barge_confidence: + type: number + format: double + description: Confidence threshold for the user barging in over the agent (0.0–1.0). + minimum: 0 + maximum: 1 + presence_penalty: + type: number + format: double + description: Aversion to new topics (-2.0–2.0). Positive values encourage new topics. + minimum: -2 + maximum: 2 + frequency_penalty: + type: number + format: double + description: Aversion to repetition (-2.0–2.0). Positive values reduce verbatim repeats. + minimum: -2 + maximum: 2 + model: + type: string + description: LLM model identifier to use for this prompt. + description: |- + Inline prompt configuration for the AI agent. Overrides a pre-configured + `agent` UUID when both are present. + AiPostPrompt: + type: object + properties: + text: + type: string + description: Instructions sent to the agent after the conversation ends. + top_p: + type: number + format: double + description: Nucleus-sampling cutoff (0.0–1.0). Alternative to `temperature`. + minimum: 0 + maximum: 1 + temperature: + type: number + format: double + description: Randomness of generation (0.0–1.5). Lower is more deterministic. + minimum: 0 + maximum: 1.5 + barge_confidence: + type: number + format: double + description: Confidence threshold for the user barging in over the agent (0.0–1.0). + minimum: 0 + maximum: 1 + presence_penalty: + type: number + format: double + description: Aversion to new topics (-2.0–2.0). Positive values encourage new topics. + minimum: -2 + maximum: 2 + frequency_penalty: + type: number + format: double + description: Aversion to repetition (-2.0–2.0). Positive values reduce verbatim repeats. + minimum: -2 + maximum: 2 + model: + type: string + description: LLM model identifier to use for this post-prompt. + description: |- + Post-conversation prompt configuration. Same shape as `AiPrompt` minus + `confidence` (which has no meaning after the conversation has ended). + AiPronounce: + type: object + properties: + replace: + type: string + description: The expression to replace. + with: + type: string + description: The phonetic spelling to substitute. + ignore_case: + type: boolean + description: Match case-insensitively. Default `true`. + default: true + required: + - replace + - with + description: |- + A global pronunciation rule. Replaces a matched expression with a phonetic + spelling so the TTS engine pronounces it correctly. + AiHint: + type: object + properties: + hint: + type: string + description: The hint phrase to match exactly. + pattern: + type: string + description: A regular expression the hint must match before replacement. + replace: + type: string + description: Text to replace the matched portion of the hint with. + ignore_case: + type: boolean + description: Match case-insensitively. Default `false`. + default: false + required: + - hint + description: |- + A context hint biasing speech recognition. May be a bare string, or an object + that rewrites a matched phrase before it reaches the model. + AiLanguage: + type: object + properties: + name: + type: string + description: Human-readable language name, used in the system prompt (e.g. `French`). + code: + type: string + description: ASR language code (e.g. `fr-FR`). + voice: + type: string + description: Voice in `.` form (e.g. `gcloud.fr-FR-Neural2-B`). + model: + type: string + description: TTS model for the selected engine. + required: + - name + - code + - voice + description: |- + A supported language configuration for the dialogue. (Modeled per the SWML + `ai` verb; additional TTS engine-specific knobs may be accepted.) + AiSwaig: + type: object + properties: + defaults: + description: Default settings inherited by all functions. + allOf: + - $ref: "#/components/schemas/AiSwaigDefaults" + functions: + type: array + items: + $ref: "#/components/schemas/AiSwaigFunction" + description: User-defined functions the agent may call. + includes: + type: array + items: + $ref: "#/components/schemas/AiSwaigIncludes" + description: Remote function-signature includes. + native_functions: + type: array + items: + type: string + description: |- + Names of prebuilt native functions the agent may call (e.g. `check_time`, + `wait_seconds`). Modeled loosely — the available set is documented with the + SWML `ai` verb. + description: SWAIG (SignalWire AI Gateway) function configuration. + AiSwaigDefaults: + type: object + properties: + web_hook_url: + type: string + description: |- + Default webhook URL for function status callbacks. Basic auth may be inlined + as `username:password@url`. + description: Default settings applied to all SWAIG functions unless overridden. + AiSwaigFunction: + type: object + properties: + function: + type: string + description: Unique function name (or a reserved SignalWire hook name). + purpose: + type: string + description: Description of when/why the agent should call this function. + argument: + description: Description of the input the function expects. + web_hook_url: + type: string + description: Per-function webhook URL override. + required: + - function + description: |- + A single SWAIG function definition. Only the historically documented fields + (`function`, `purpose`, `argument`) are typed; the live SWAIG schema accepts + many more (`description`, `parameters`, `data_map`, `web_hook_url`, fillers, + …) — see openQuestions. + AiSwaigIncludes: + type: object + properties: + url: + type: string + description: URL hosting the remote functions. Basic auth may be inlined. + functions: + type: array + items: + type: string + description: Names of the remote functions to include. + required: + - url + - functions + description: Remote SWAIG function include — pull function signatures from a URL. + CallingAiRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.ai + params: + $ref: "#/components/schemas/AiParams" + AiResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + call_id: + type: string + description: Echo of the call id. + control_id: + type: string + description: Echo of the control id for this AI session. + required: + - code + - message + CallingAiResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/AiResult" + AiStopParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + control_id: + type: string + description: The `control_id` assigned in `calling.ai`. + required: + - node_id + - call_id + - control_id + CallingAiStopRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.ai.stop + params: + $ref: "#/components/schemas/AiStopParams" + AiStopResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + call_id: + type: string + description: Echo of the call id. + control_id: + type: string + description: Echo of the control id. + required: + - code + - message + CallingAiStopResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/AiStopResult" + AmazonBedrockParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + prompt: + type: string + description: System prompt for the Bedrock agent. + SWAIG: + description: SWAIG function configuration. + allOf: + - $ref: "#/components/schemas/AiSwaig" + params: + type: object + additionalProperties: {} + description: Open-ended AI behavior parameters. Loose-modeled — see `calling.ai` `params`. + global_data: + type: object + additionalProperties: {} + description: Global data accessible to all SWAIG functions. + post_prompt: + description: Post-conversation prompt configuration. + allOf: + - $ref: "#/components/schemas/AiPostPrompt" + post_prompt_url: + type: string + format: uri + description: URL to receive post-prompt results. + required: + - node_id + - call_id + CallingAmazonBedrockRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.amazon_bedrock + params: + $ref: "#/components/schemas/AmazonBedrockParams" + AmazonBedrockResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingAmazonBedrockResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/AmazonBedrockResult" + AiMessageParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + message_text: + type: string + description: Message text to inject into the session. + role: + description: Role of the message sender. + allOf: + - $ref: "#/components/schemas/AiMessageRole" + reset: + description: Conversation-reset configuration. + allOf: + - $ref: "#/components/schemas/AiMessageReset" + global_data: + type: object + additionalProperties: {} + description: Updated global data for SWAIG functions. + required: + - node_id + - call_id + AiMessageRole: + type: string + enum: + - system + - user + - assistant + AiMessageReset: + type: object + properties: + full_reset: + type: boolean + description: Clear the entire conversation history. + user_prompt: + type: string + description: Replace (or clear) the user prompt context. + system_prompt: + type: string + description: Replace (or clear) the system prompt context. + description: |- + Conversation-reset configuration. Each field clears or replaces part of the + session context. + CallingAiMessageRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.ai_message + params: + $ref: "#/components/schemas/AiMessageParams" + AiMessageResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingAiMessageResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/AiMessageResult" + AiHoldParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + timeout: + type: string + description: Hold timeout. Sent as a string in the example (e.g. `"60"`); unit is seconds. + prompt: + type: string + description: Hold prompt / music (plain string). + required: + - node_id + - call_id + CallingAiHoldRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.ai_hold + params: + $ref: "#/components/schemas/AiHoldParams" + AiHoldResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingAiHoldResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/AiHoldResult" + AiUnholdParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + prompt: + type: string + description: Resume prompt (plain string). + required: + - node_id + - call_id + CallingAiUnholdRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.ai_unhold + params: + $ref: "#/components/schemas/AiUnholdParams" + AiUnholdResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingAiUnholdResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/AiUnholdResult" + UserEventParams: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + event: + type: string + description: The custom event name. + required: + - node_id + - call_id + CallingUserEventRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: calling.user_event + params: + $ref: "#/components/schemas/UserEventParams" + UserEventResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + CallingUserEventResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/UserEventResult" + CallStateEvent: + type: object + properties: + node_id: + type: string + description: Node the call is on. + call_id: + type: string + description: The call id. + tag: + type: string + description: Identifier set on the originating dial/connect. + device: + description: The negotiated device for this call. + allOf: + - $ref: "#/components/schemas/CallDevice" + parent: + description: The parent call, when this call was created by another. + allOf: + - $ref: "#/components/schemas/CallParentRef" + peer: + description: The peer call, when bridged. + allOf: + - $ref: "#/components/schemas/CallPeerRef" + call_state: + description: The new call state. + allOf: + - $ref: "#/components/schemas/CallState" + start_time: + type: integer + format: int64 + description: Epoch milliseconds the call started. + answer_time: + type: integer + format: int64 + description: Epoch milliseconds the call was answered. + end_time: + type: integer + format: int64 + description: Epoch milliseconds the call ended. + created_by: + type: string + enum: + - dial + - connect + - receive + description: What created this call. + required: + - node_id + - call_id + - call_state + description: A change in state of an active Relay-controlled call. + CallDevice: + type: object + properties: + type: + type: string + required: + - type + discriminator: type + CallPhoneDevice: + allOf: + - $ref: "#/components/schemas/CallDevice" + - type: object + properties: + type: + type: string + const: phone + params: + type: object + properties: + from_number: + type: string + description: Origination number, E.164. + to_number: + type: string + description: Destination number, E.164. + required: + - from_number + - to_number + required: + - type + - params + CallSipDevice: + allOf: + - $ref: "#/components/schemas/CallDevice" + - type: object + properties: + type: + type: string + const: sip + params: + type: object + properties: + from: + type: string + description: Origination SIP address. + to: + type: string + description: Destination SIP address. + headers: + type: array + items: + $ref: "#/components/schemas/SipHeader" + description: Custom `X-` SIP headers. + required: + - from + - to + required: + - type + - params + CallWebrtcDevice: + allOf: + - $ref: "#/components/schemas/CallDevice" + - type: object + properties: + type: + type: string + const: webrtc + params: + type: object + additionalProperties: {} + description: WebRTC device params (shapes not documented in the protocol reference). + required: + - type + - params + CallParentRef: + type: object + properties: + node_id: + type: string + call_id: + type: string + device_type: + type: string + description: The parent device type (flattened, e.g. `sip`). + description: A parent call referenced by a state event. + CallPeerRef: + type: object + properties: + node_id: + type: string + call_id: + type: string + description: A peer call referenced by an event. + CallState: + type: string + enum: + - created + - ringing + - answered + - ending + - ended + CallStateEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: type: string - const: calling.begin + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid params: - $ref: "#/components/schemas/BeginParams" - BeginResult: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.state + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallStateEvent" + CallReceiveEvent: type: object properties: - code: - type: string - description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. - message: + node_id: type: string - description: Human-readable result message. + description: Node the call is on. call_id: type: string - description: The created call id. + description: The call id. + call_state: + description: State of the inbound call. + allOf: + - $ref: "#/components/schemas/ReceiveCallState" + context: + type: string + description: Routing context the call arrived on (e.g. `pbx`). + device: + description: The inbound device. + allOf: + - $ref: "#/components/schemas/CallDevice" + required: + - node_id + - call_id + - call_state + - device + description: An incoming call available for a Relay client to control. + ReceiveCallState: + type: string + enum: + - created + - connecting + - connected + - disconnecting + - disconnected + CallReceiveEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.receive + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallReceiveEvent" + CallConnectEvent: + type: object + properties: node_id: type: string description: Node the call is on. + call_id: + type: string + description: The call id. + tag: + type: string + description: Identifier of the connect operation. + peer: + description: The peer call being connected. + allOf: + - $ref: "#/components/schemas/ConnectPeer" + connect_state: + type: string + enum: + - disconnected + - connecting + - connected + - failed + description: The connect (bridge) state. required: - - code - - message - CallingBeginResponse: + - node_id + - call_id + - peer + - connect_state + description: A call's connect (bridge/unbridge) state. + ConnectPeer: + type: object + properties: + node_id: + type: string + call_id: + type: string + tag: + type: string + queue_id: + type: string + queue_name: + type: string + device: + description: The peer's negotiated device. + allOf: + - $ref: "#/components/schemas/CallDevice" + description: The peer leg in a connect event. + CallConnectEventFrame: type: object required: - jsonrpc + - method - id + - params properties: jsonrpc: type: string const: "2.0" + method: + type: string + const: signalwire.event id: type: string format: uuid - result: - $ref: "#/components/schemas/BeginResult" - DialParams: + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.connect + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallConnectEvent" + CallDialEvent: + type: object + properties: + node_id: + type: string + description: Node the dial is on. + tag: + type: string + description: Identifier from `calling.dial`. + dial_state: + type: string + enum: + - dialing + - answered + - failed + description: The dial operation state. + call: + description: The answered call (present when `dial_state` is `answered`). + allOf: + - $ref: "#/components/schemas/DialWinnerCall" + required: + - node_id + - tag + - dial_state + description: The state of a `calling.dial` operation. + DialWinnerCall: + type: object + properties: + node_id: + type: string + call_id: + type: string + tag: + type: string + device: + description: The negotiated device. + allOf: + - $ref: "#/components/schemas/CallDevice" + dial_winner: + type: boolean + description: Whether this call is the selected (first-answered) winner. + description: The answered call carried by a `calling.call.dial` event. + CallDialEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.dial + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallDialEvent" + CallReferEvent: type: object properties: - tag: + node_id: type: string - description: Identifier added to all call and dial events. - region: + description: Node the call is on. + call_id: + type: string + description: The call id. + state: + description: The transfer state. + allOf: + - $ref: "#/components/schemas/ReferState" + sip_refer_to: + type: string + description: The SIP URI the call is being transferred to. + sip_refer_response_code: + type: string + description: SIP response code to the REFER request (string, e.g. `"202"`). + sip_notify_response_code: type: string - description: Region to originate from. - devices: - type: array - items: - type: array - items: - $ref: "#/components/schemas/DialDevice" description: |- - Devices to dial. The outer array is sequential ringing groups; the inner - array is simultaneous (parallel) dials within a group. The first device to - answer wins. - max_price_per_minute: - type: number - format: double - description: Maximum price per minute willing to be paid. + SIP response code to the NOTIFY(s) received after the REFER (string, e.g. + `"200"`). Indicates whether the transfer ultimately succeeded. required: - - tag - - devices - CallingDialRequest: + - node_id + - call_id + - state + description: A change in state of a transferred (SIP-REFER) call. + ReferState: + type: string + enum: + - inProgress + - cancel + - busy + - noAnswer + - error + - success + CallReferEventFrame: type: object required: - jsonrpc - - id - method + - id - params properties: jsonrpc: type: string const: "2.0" + method: + type: string + const: signalwire.event id: type: string format: uuid - method: - type: string - const: calling.dial params: - $ref: "#/components/schemas/DialParams" - DialResult: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.refer + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallReferEvent" + CallPlayEvent: type: object properties: - code: + node_id: type: string - description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. - message: + description: Node the call is on. + call_id: type: string - description: Human-readable result message. + description: The call id. + control_id: + type: string + description: Identifier of the active play (from `calling.play`). + state: + description: The play state. + allOf: + - $ref: "#/components/schemas/CallPlayState" required: - - code - - message - CallingDialResponse: + - node_id + - call_id + - control_id + - state + description: A change in a call's play state. + CallPlayState: + type: string + enum: + - playing + - paused + - error + - finished + CallPlayEventFrame: type: object required: - jsonrpc + - method - id + - params properties: jsonrpc: type: string const: "2.0" + method: + type: string + const: signalwire.event id: type: string format: uuid - result: - $ref: "#/components/schemas/DialResult" - AnswerParams: + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.play + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallPlayEvent" + CallQueueEvent: type: object properties: node_id: @@ -511,73 +7641,263 @@ components: call_id: type: string description: The call id. - codecs: - type: array - items: - $ref: "#/components/schemas/AnswerCodec" - description: |- - Codecs to negotiate (SignalWire-picked if unset). If a listed codec is - unsupported by the call type the request fails with `"400"`. + control_id: + type: string + description: Identifier of the active queue (from `calling.queue.enter`). + status: + description: The queue transition. + allOf: + - $ref: "#/components/schemas/CallQueueStatus" + id: + type: string + description: Queue id. + name: + type: string + description: Queue name. + position: + type: number + format: double + description: Position of the call within the queue. + size: + type: number + format: double + description: Number of calls in the queue. + avg_time: + type: number + format: double + description: Average time (seconds) calls spend in the queue. + enqueue_ts: + type: number + format: double + description: Epoch (seconds) the call entered the queue. + dequeue_ts: + type: number + format: double + description: Epoch (seconds) the call was dequeued. + leave_ts: + type: number + format: double + description: Epoch (seconds) the call left the queue. required: - node_id - call_id - AnswerCodec: + - control_id + description: A change in a call's queue state. + CallQueueStatus: type: string enum: - - PCMU - - PCMA - - OPUS - - G729 - - G722 - - AMR-WB - - VP8 - - H264 - CallingAnswerRequest: + - enqueue + - dequeue + - leave + CallQueueEventFrame: type: object required: - jsonrpc - - id - method + - id - params properties: jsonrpc: type: string const: "2.0" + method: + type: string + const: signalwire.event id: type: string format: uuid - method: - type: string - const: calling.answer params: - $ref: "#/components/schemas/AnswerParams" - AnswerResult: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.queue + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallQueueEvent" + CallCollectEvent: type: object properties: - code: + node_id: type: string - description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. - message: + description: Node the call is on. + call_id: type: string - description: Human-readable result message. + description: The call id. + control_id: + type: string + description: Identifier of the active collect (from `calling.collect`). + state: + description: The collect state. `error` means the detector ended with an error. + allOf: + - $ref: "#/components/schemas/CallCollectState" + result: + description: The collect result. + allOf: + - $ref: "#/components/schemas/CallCollectResult" + final: + type: boolean + description: |- + Meaningful when `partial_results`/`continuous` was set: `true` once utterance + detection has completed. With `continuous: true` the collector restarts for + the next utterance. required: - - code - - message - CallingAnswerResponse: + - node_id + - call_id + - control_id + - state + description: A call's collect result. + CallCollectState: + type: string + enum: + - collecting + - error + - finished + CallCollectResult: + type: object + properties: + type: + type: string + required: + - type + description: |- + The collected input. Discriminated on `type`. The `error`, `no_input`, + `no_match` and `start_of_input` variants carry no `params`; `digit` and + `speech` carry a `params` payload. + discriminator: type + CallCollectResultError: + allOf: + - $ref: "#/components/schemas/CallCollectResult" + - type: object + properties: + type: + type: string + const: error + required: + - type + CallCollectResultNoInput: + allOf: + - $ref: "#/components/schemas/CallCollectResult" + - type: object + properties: + type: + type: string + const: no_input + required: + - type + CallCollectResultNoMatch: + allOf: + - $ref: "#/components/schemas/CallCollectResult" + - type: object + properties: + type: + type: string + const: no_match + required: + - type + CallCollectResultStartOfInput: + allOf: + - $ref: "#/components/schemas/CallCollectResult" + - type: object + properties: + type: + type: string + const: start_of_input + required: + - type + description: Fired only when using the `calling.collect` API (start-of-speech marker). + CallCollectResultDigit: + allOf: + - $ref: "#/components/schemas/CallCollectResult" + - type: object + properties: + type: + type: string + const: digit + params: + type: object + properties: + digits: + type: string + description: The collected DTMF digits. + terminator: + type: string + description: The terminator digit that ended collection, if any. + required: + - digits + required: + - type + - params + CallCollectResultSpeech: + allOf: + - $ref: "#/components/schemas/CallCollectResult" + - type: object + properties: + type: + type: string + const: speech + params: + type: object + properties: + text: + type: string + description: The recognized utterance. + confidence: + type: number + format: double + description: Recognition confidence (e.g. `83.2`). + required: + - text + required: + - type + - params + CallCollectEventFrame: type: object required: - jsonrpc + - method - id + - params properties: jsonrpc: type: string const: "2.0" + method: + type: string + const: signalwire.event id: type: string format: uuid - result: - $ref: "#/components/schemas/AnswerResult" - EndParams: + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.collect + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallCollectEvent" + CallRecordEvent: type: object properties: node_id: @@ -586,69 +7906,113 @@ components: call_id: type: string description: The call id. - reason: - description: Why the call is ending. - default: hangup + control_id: + type: string + description: Identifier of the active recording (from `calling.record`). + state: + description: The recording state. allOf: - - $ref: "#/components/schemas/CallEndReason" + - $ref: "#/components/schemas/CallRecordState" + url: + type: string + format: uri + description: Location of the recording — not accessible until `finished`. + duration: + type: number + format: double + description: Length of the recording in seconds — set when `finished`. + size: + type: integer + format: int32 + description: Size of the recording in bytes — set when `finished`. + record: + description: The recording configuration. + allOf: + - $ref: "#/components/schemas/RecordEventSpec" required: - node_id - call_id - CallEndReason: + - control_id + - state + description: A change in a call recording's state. + CallRecordState: type: string enum: - - hangup - - cancel - - busy - - noAnswer - - decline - - error - CallingEndRequest: + - recording + - paused + - finished + - no_input + RecordEventSpec: type: object - required: - - jsonrpc - - id - - method - - params properties: - jsonrpc: - type: string - const: "2.0" - id: - type: string - format: uuid - method: - type: string - const: calling.end - params: - $ref: "#/components/schemas/EndParams" - EndResult: + audio: + description: Audio-recording configuration (present when recording audio). + allOf: + - $ref: "#/components/schemas/RecordEventAudio" + description: |- + Reported recording spec. Keyed by the subobject name (`audio`) rather than a + `type` discriminator; only the `audio` variant is documented. (Prose also + references a `record.params` subobject for non-audio types — shape + undocumented.) + RecordEventAudio: type: object properties: - code: - type: string - description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. - message: + format: type: string - description: Human-readable result message. - required: - - code - - message - CallingEndResponse: + description: Output file format (e.g. `mp3`, `wav`). + stereo: + type: boolean + description: Whether the recording was captured in stereo. + direction: + description: Which audio direction(s) were captured. + allOf: + - $ref: "#/components/schemas/RecordEventDirection" + description: |- + The reported `record.audio` subobject — a slimmer echo of the recording + configuration than the request-side `RecordAudio`. + RecordEventDirection: + type: string + enum: + - listen + - speak + - both + CallRecordEventFrame: type: object required: - jsonrpc + - method - id + - params properties: jsonrpc: type: string const: "2.0" + method: + type: string + const: signalwire.event id: type: string format: uuid - result: - $ref: "#/components/schemas/EndResult" - ConnectParams: + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.record + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallRecordEvent" + CallDetectEvent: type: object properties: node_id: @@ -657,389 +8021,509 @@ components: call_id: type: string description: The call id. - ringback: - type: array - items: - $ref: "#/components/schemas/Ringback" - description: Audio to play to the caller while connecting. - tag: - type: string - description: Identifier added to created calls' events. - devices: - type: array - items: - type: array - items: - $ref: "#/components/schemas/ConnectDevice" - description: Devices to connect. Same sequential/parallel topology as `calling.dial`. - max_duration: - type: integer - format: int32 - description: Maximum duration once connected, in MINUTES. - max_price_per_minute: - type: number - format: double - description: Maximum price per minute willing to be paid. - status_url: + control_id: type: string - format: uri - description: URL to POST connect events to. + description: Identifier of the active detector (from `calling.detect`). + detect: + description: The detector-specific information. + allOf: + - $ref: "#/components/schemas/CallDetectResult" required: - node_id - call_id - - devices - Ringback: + - control_id + - detect + description: A call-detection event from an active detector. + CallDetectResult: type: object properties: type: type: string required: - type - description: Audio played to the caller while a connect is in progress. Discriminated on `type`. + description: |- + A detector's event payload. Discriminated on `type` (`fax|machine|digit`). + Every variant's `params.event` may also surface the generic `finished` (on + completion) or `error` (if unable to start) values noted in the source prose. discriminator: type - RingbackAudio: - allOf: - - $ref: "#/components/schemas/Ringback" - - type: object - properties: - type: - type: string - const: audio - params: - type: object - properties: - url: - type: string - format: uri - description: Audio file URL. - required: - - url - required: - - type - - params - RingbackTts: + CallDetectFax: allOf: - - $ref: "#/components/schemas/Ringback" + - $ref: "#/components/schemas/CallDetectResult" - type: object properties: type: type: string - const: tts + const: fax params: type: object properties: - text: - type: string - description: Text to speak (plain or SSML). - language: - type: string - description: TTS language. - default: en-US - gender: - description: TTS voice gender. - default: female + event: + description: The fax-detector event. allOf: - - $ref: "#/components/schemas/TtsGender" + - $ref: "#/components/schemas/CallDetectFaxEvent" required: - - text + - event required: - type - params - TtsGender: + CallDetectFaxEvent: type: string enum: - - male - - female - RingbackSilence: + - CED + - CNG + CallDetectMachine: allOf: - - $ref: "#/components/schemas/Ringback" + - $ref: "#/components/schemas/CallDetectResult" - type: object properties: type: type: string - const: silence + const: machine params: type: object properties: - duration: - type: number - format: double - description: Seconds of silence. + event: + description: The machine-detector event. + allOf: + - $ref: "#/components/schemas/CallDetectMachineEvent" + beep: + type: boolean + description: Whether a beep has been detected. required: - - duration + - event required: - type - params - RingbackRingtone: + CallDetectMachineEvent: + type: string + enum: + - MACHINE + - HUMAN + - UNKNOWN + - READY + - NOT_READY + CallDetectDigit: allOf: - - $ref: "#/components/schemas/Ringback" + - $ref: "#/components/schemas/CallDetectResult" - type: object properties: type: type: string - const: ringtone + const: digit params: type: object properties: - name: - description: Tone name (country code). - allOf: - - $ref: "#/components/schemas/ToneName" - duration: - type: number - format: double - description: Seconds to play. - exclusiveMinimum: 0 + event: + type: string + description: The detected DTMF digit (one of `0-9`, `#`, `*`). required: - - name + - event required: - type - params - ToneName: - type: string - enum: - - at - - au - - bg - - br - - be - - ch - - cl - - cn - - cz - - de - - dk - - ee - - es - - fi - - fr - - gr - - hu - - il - - in - - it - - lt - - jp - - mx - - my - - nl - - no - - nz - - ph - - pl - - pt - - ru - - se - - sg - - th - - uk - - us - - tw - - ve - - za - ConnectDevice: + CallDetectEventFrame: type: object + required: + - jsonrpc + - method + - id + - params properties: - type: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: type: string - required: - - type - description: A device to connect to an active call (`calling.connect`). Discriminated on `type`. - discriminator: type - ConnectCallDevice: - allOf: - - $ref: "#/components/schemas/ConnectDevice" - - type: object + format: uuid + params: + type: object + required: + - event_type + - params properties: - type: + event_type: + type: string + const: calling.call.detect + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: type: string - const: call params: - $ref: "#/components/schemas/CallRefDeviceParams" - required: - - type - - params - CallRefDeviceParams: + $ref: "#/components/schemas/CallDetectEvent" + CallDenoiseEvent: type: object properties: node_id: type: string - description: Node of the existing call. + description: Node the call is on. call_id: type: string - description: Existing call id. + description: The call id. + denoised: + type: boolean + description: Whether noise reduction is enabled (`true`) or disabled. required: - node_id - call_id - description: "`call` device params (connect only) — bridge to an existing call." - ConnectQueueDevice: - allOf: - - $ref: "#/components/schemas/ConnectDevice" - - type: object + - denoised + description: A call-denoiser state event. (Carries no `control_id`.) + CallDenoiseEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params properties: - type: + event_type: + type: string + const: calling.call.denoise + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: type: string - const: queue params: - $ref: "#/components/schemas/QueueDeviceParams" - required: - - type - - params - QueueDeviceParams: + $ref: "#/components/schemas/CallDenoiseEvent" + CallFaxEvent: type: object properties: node_id: type: string - description: Node of the queue. - queue_name: + description: Node the call is on. + call_id: type: string - description: Queue name. - queue_id: + description: The call id. + control_id: type: string - description: Queue id. + description: The ID used to control the active fax. + fax: + description: Fax event information. + allOf: + - $ref: "#/components/schemas/CallFax" required: - node_id - - queue_name - description: "`queue` device params (connect only) — pull a call from a queue." - ConnectPhoneDevice: - allOf: - - $ref: "#/components/schemas/ConnectDevice" - - type: object - properties: - type: - type: string - const: phone - params: - $ref: "#/components/schemas/PhoneDeviceParams" - required: - - type - - params - ConnectSipDevice: + - call_id + - control_id + - fax + description: A fax event (page / finished / error). + CallFax: + type: object + properties: + type: + type: string + required: + - type + description: A fax event payload, discriminated on `type`. + discriminator: type + FaxPage: allOf: - - $ref: "#/components/schemas/ConnectDevice" + - $ref: "#/components/schemas/CallFax" - type: object properties: type: type: string - const: sip + const: page params: - $ref: "#/components/schemas/SipDeviceParams" + type: object + properties: + direction: + description: Whether the page was sent or received. + allOf: + - $ref: "#/components/schemas/FaxDirection" + number: + type: integer + format: int32 + description: Page number. + required: + - direction + - number required: - type - params - ConnectWebrtcDevice: + description: A single page was sent or received. + FaxDirection: + type: string + enum: + - send + - receive + FaxFinished: allOf: - - $ref: "#/components/schemas/ConnectDevice" + - $ref: "#/components/schemas/CallFax" - type: object properties: type: type: string - const: webrtc + const: finished params: - $ref: "#/components/schemas/WebrtcDeviceParams" + type: object + properties: + direction: + description: Whether the fax was sent or received. + allOf: + - $ref: "#/components/schemas/FaxDirection" + identity: + type: string + description: Local fax identity (e.g. an E.164 number). + remote_identity: + type: string + description: Remote fax identity (e.g. an E.164 number). + document: + type: string + format: uri + description: Document URL location. + pages: + type: integer + format: int32 + description: Number of pages sent / received. + success: + type: boolean + description: Whether the fax completed successfully. + result: + type: integer + format: int32 + description: Fax result code (e.g. `1231`). + result_text: + type: string + description: Human-readable fax result text. + required: + - direction required: - type - params - ConnectStreamDevice: + description: The fax transmission finished. + FaxError: allOf: - - $ref: "#/components/schemas/ConnectDevice" + - $ref: "#/components/schemas/CallFax" - type: object properties: type: type: string - const: stream + const: error params: - $ref: "#/components/schemas/StreamDeviceParams" + type: object + additionalProperties: {} + description: Error-variant params (shape undocumented). required: - type - - params - StreamDeviceParams: - type: object - properties: - url: - type: string - format: uri - description: Stream target — `wss://` required. - name: - type: string - description: Optional stream name. - codec: - type: string description: |- - Codec, optionally with rate/ptime modifiers (e.g. `PCMU@40i`, - `L16@24000h@40i`). One of `PCMU|PCMA|G722|L16`. Default `PCMU`. - default: PCMU - status_url: - type: string - format: uri - description: Webhook for stream status. - status_url_method: - type: string - enum: - - GET - - POST - description: HTTP method for `status_url`. - default: POST - realtime: - type: boolean - description: Stream realtime audio. - default: false - authorization_bearer_token: - type: string - description: Bearer token sent to the stream endpoint. - custom_parameters: - type: object - additionalProperties: {} - description: Arbitrary custom parameters forwarded to the stream endpoint. - required: - - url - description: "`stream` device params (connect only) — bidirectional audio to a WS endpoint." - CallingConnectRequest: + The fax transmission errored. The wire shape for this variant is not + documented in the protocol reference; it is modeled loosely and likely + shares the `finished` result/result_text fields. + CallFaxEventFrame: type: object required: - jsonrpc - - id - method + - id - params properties: jsonrpc: type: string const: "2.0" + method: + type: string + const: signalwire.event id: type: string format: uuid - method: - type: string - const: calling.connect params: - $ref: "#/components/schemas/ConnectParams" - ConnectResult: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.fax + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallFaxEvent" + CallTapEvent: type: object properties: - code: + node_id: type: string - description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. - message: + description: Node the call is on. + call_id: type: string - description: Human-readable result message. + description: The call id. + control_id: + type: string + description: The ID used to control the active tap. + state: + description: The tap state. + allOf: + - $ref: "#/components/schemas/TapState" + tap: + description: The tapped media info. + allOf: + - $ref: "#/components/schemas/TapMedia" + device: + description: The device receiving the tapped media. + allOf: + - $ref: "#/components/schemas/CallTapDevice" required: - - code - - message - CallingConnectResponse: + - node_id + - call_id + - control_id + - state + - tap + - device + description: A call-tap state event. + TapState: + type: string + enum: + - tapping + - finished + TapMedia: + type: object + properties: + type: + type: string + required: + - type + description: The tapped media, discriminated on `type`. (Only `audio` is documented.) + discriminator: type + CallTapAudio: + allOf: + - $ref: "#/components/schemas/TapMedia" + - type: object + properties: + type: + type: string + const: audio + params: + type: object + properties: + direction: + description: Which side(s) of the media are tapped. + allOf: + - $ref: "#/components/schemas/CallTapDirection" + required: + - direction + required: + - type + - params + description: Audio tap. + CallTapDirection: + type: string + enum: + - speak + - listen + - both + CallTapDevice: + type: object + properties: + type: + type: string + required: + - type + description: The device receiving the tapped media, discriminated on `type`. (Only `rtp` is documented.) + discriminator: type + CallTapRtpDevice: + allOf: + - $ref: "#/components/schemas/CallTapDevice" + - type: object + properties: + type: + type: string + const: rtp + params: + type: object + properties: + addr: + type: string + description: Destination address. + port: + type: integer + format: int32 + description: Destination port. + codec: + type: string + description: Negotiated codec. + ptime: + type: integer + format: int32 + description: Packetization time, in milliseconds. + required: + - addr + - port + - codec + - ptime + required: + - type + - params + description: RTP tap sink. + CallTapEventFrame: type: object required: - jsonrpc + - method - id + - params properties: jsonrpc: type: string const: "2.0" + method: + type: string + const: signalwire.event id: type: string format: uuid - result: - $ref: "#/components/schemas/ConnectResult" - DisconnectParams: + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.tap + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallTapEvent" + CallStreamEvent: type: object properties: node_id: @@ -1048,55 +8532,159 @@ components: call_id: type: string description: The call id. + control_id: + type: string + description: The ID used to control the active stream. + state: + description: The stream state. + allOf: + - $ref: "#/components/schemas/StreamState" + url: + type: string + format: uri + description: The WebSocket URL being streamed to. + name: + type: string + description: The friendly name of the stream (if provided). required: - node_id - call_id - CallingDisconnectRequest: + - control_id + - state + - url + description: A call-stream state change. + StreamState: + type: string + enum: + - streaming + - finished + CallStreamEventFrame: type: object required: - jsonrpc - - id - method + - id - params properties: jsonrpc: type: string const: "2.0" + method: + type: string + const: signalwire.event id: type: string format: uuid - method: - type: string - const: calling.disconnect params: - $ref: "#/components/schemas/DisconnectParams" - DisconnectResult: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.stream + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallStreamEvent" + CallTranscribeEvent: type: object properties: - code: + node_id: type: string - description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. - message: + description: Node the call is on. + call_id: type: string - description: Human-readable result message. + description: The call id. + control_id: + type: string + description: The ID used to control the active transcription. + state: + description: The transcription state. + allOf: + - $ref: "#/components/schemas/TranscribeState" + url: + type: string + description: Location of the recording (e.g. `recordings/.wav`). + recording_id: + type: string + description: The UUID of the shadow recording. + status_url: + type: string + description: The callback URL, if one was provided. + duration: + type: number + format: double + description: Length of the recording in seconds. Set only on `finished`. + size: + type: integer + format: int32 + description: Size of the recording in bytes. Set only on `finished`. + start_time: + type: number + format: double + description: Unix timestamp when recording started. Set only on `finished`. + end_time: + type: number + format: double + description: Unix timestamp when recording ended. Set only on `finished`. required: - - code - - message - CallingDisconnectResponse: + - node_id + - call_id + - control_id + - state + - url + - recording_id + description: A call-transcription state. + TranscribeState: + type: string + enum: + - transcribing + - finished + CallTranscribeEventFrame: type: object required: - jsonrpc + - method - id + - params properties: jsonrpc: type: string const: "2.0" + method: + type: string + const: signalwire.event id: type: string format: uuid - result: - $ref: "#/components/schemas/DisconnectResult" - CallStateEvent: + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.transcribe + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallTranscribeEvent" + CallHoldEvent: type: object properties: node_id: @@ -1105,151 +8693,227 @@ components: call_id: type: string description: The call id. - tag: - type: string - description: Identifier set on the originating dial/connect. - device: - description: The negotiated device for this call. - allOf: - - $ref: "#/components/schemas/CallDevice" - parent: - description: The parent call, when this call was created by another. + state: + description: The hold state. allOf: - - $ref: "#/components/schemas/CallParentRef" - peer: - description: The peer call, when bridged. - allOf: - - $ref: "#/components/schemas/CallPeerRef" - call_state: - description: The new call state. - allOf: - - $ref: "#/components/schemas/CallState" - start_time: - type: integer - format: int64 - description: Epoch milliseconds the call started. - answer_time: - type: integer - format: int64 - description: Epoch milliseconds the call was answered. - end_time: - type: integer - format: int64 - description: Epoch milliseconds the call ended. - created_by: - type: string - enum: - - dial - - connect - - receive - description: What created this call. + - $ref: "#/components/schemas/HoldState" required: - node_id - call_id - - call_state - description: A change in state of an active Relay-controlled call. - CallDevice: + - state + description: A call hold-state event. (No `control_id`.) + HoldState: + type: string + enum: + - hold + - unhold + CallHoldEventFrame: type: object + required: + - jsonrpc + - method + - id + - params properties: - type: + jsonrpc: type: string - required: - - type - discriminator: type - CallPhoneDevice: - allOf: - - $ref: "#/components/schemas/CallDevice" - - type: object - properties: - type: - type: string - const: phone - params: - type: object - properties: - from_number: - type: string - description: Origination number, E.164. - to_number: - type: string - description: Destination number, E.164. - required: - - from_number - - to_number + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object required: - - type + - event_type - params - CallSipDevice: - allOf: - - $ref: "#/components/schemas/CallDevice" - - type: object properties: - type: + event_type: type: string - const: sip - params: - type: object - properties: - from: - type: string - description: Origination SIP address. - to: - type: string - description: Destination SIP address. - headers: - type: array - items: - $ref: "#/components/schemas/SipHeader" - description: Custom `X-` SIP headers. - required: - - from - - to - required: - - type - - params - CallWebrtcDevice: - allOf: - - $ref: "#/components/schemas/CallDevice" - - type: object - properties: - type: + const: calling.call.hold + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: type: string - const: webrtc params: - type: object - additionalProperties: {} - description: WebRTC device params (shapes not documented in the protocol reference). - required: - - type - - params - CallParentRef: + $ref: "#/components/schemas/CallHoldEvent" + CallSendDigitsEvent: type: object properties: node_id: type: string + description: Node the call is on. call_id: type: string - device_type: + description: The call id. + control_id: type: string - description: The parent device type (flattened, e.g. `sip`). - description: A parent call referenced by a state event. - CallPeerRef: + description: The ID used to control the active send_digits operation. + state: + type: string + enum: + - finished + description: The send_digits state. (Only `finished` is documented.) + required: + - node_id + - call_id + - control_id + - state + description: A send-digits completion event. + CallSendDigitsEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: calling.call.send_digits + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/CallSendDigitsEvent" + ConferenceEvent: type: object properties: node_id: type: string + description: The UUID of the node this conference is on. + conference_id: + type: string + description: The UUID of the conference. + name: + type: string + description: The name of the conference. + status: + description: The conference event status (secondary discriminator). + allOf: + - $ref: "#/components/schemas/ConferenceStatus" call_id: type: string - description: A peer call referenced by an event. - CallState: + description: Participant call id. Set on participant statuses. + muted: + type: boolean + description: Whether the participant is muted. Set on participant statuses. + hold: + type: boolean + description: Whether the participant is on hold. Set on participant statuses. + coaching: + type: boolean + description: Whether the participant is coaching. Set on participant statuses. + end_on_exit: + type: boolean + description: Whether the conference ends when this participant exits. Set on participant statuses. + start_on_enter: + type: boolean + description: Whether the conference starts when this participant enters. Set on participant statuses. + participant_call_status: + description: The participant's final call status. Set on `participant-leave`. + allOf: + - $ref: "#/components/schemas/ConferenceParticipantCallStatus" + reason_participant_left: + description: Why the participant left. Set on `participant-leave`. + allOf: + - $ref: "#/components/schemas/ConferenceReasonParticipantLeft" + call_ending_conference: + type: string + description: UUID of the call that ended the conference. Set on `conference-end`. + reason_ended: + description: Why the conference ended. Set on `conference-end`. + allOf: + - $ref: "#/components/schemas/ConferenceReasonEnded" + recording_url: + type: string + format: uri + description: URL of the conference recording. Set on `conference-end`. + recording_duration: + type: integer + format: int32 + description: Recording duration in seconds. Set on `conference-end`. + recording_file_size: + type: integer + format: int32 + description: Recording file size in bytes. Set on `conference-end`. + announce_url: + type: string + format: uri + description: Announcement URL. Set on announcement statuses. + required: + - node_id + - conference_id + - status + description: A conference lifecycle / participant event. + ConferenceStatus: type: string enum: - - created - - ringing - - answered - - ending - - ended - CallStateEventFrame: + - conference-end + - conference-start + - participant-leave + - participant-join + - participant-mute + - participant-unmute + - participant-hold + - participant-unhold + - participant-modify + - participant-speech-start + - participant-speech-stop + - announcement-end + - announcement-fail + ConferenceParticipantCallStatus: + type: string + enum: + - no-answer + - busy + - in-progress + - failed + - canceled + - completed + ConferenceReasonParticipantLeft: + type: string + enum: + - conference_ended_via_api + - moderator_ended_conference + - participant_updated_via_api + - participant_hung_up + - participant_add_failed + ConferenceReasonEnded: + type: string + enum: + - conference-ended-via-api + - last-participant-kicked + - last-participant-left + - participant-with-end-conference-on-exit-kicked + - participant-with-end-conference-on-exit-left + ConferenceEventFrame: type: object required: - jsonrpc @@ -1274,7 +8938,7 @@ components: properties: event_type: type: string - const: calling.call.state + const: calling.conference event_channel: type: string timestamp: @@ -1284,8 +8948,8 @@ components: project_id: type: string params: - $ref: "#/components/schemas/CallStateEvent" - CallReceiveEvent: + $ref: "#/components/schemas/ConferenceEvent" + CallEchoEvent: type: object properties: node_id: @@ -1294,32 +8958,21 @@ components: call_id: type: string description: The call id. - call_state: - description: State of the inbound call. - allOf: - - $ref: "#/components/schemas/ReceiveCallState" - context: - type: string - description: Routing context the call arrived on (e.g. `pbx`). - device: - description: The inbound device. + state: + description: The echo state. allOf: - - $ref: "#/components/schemas/CallDevice" + - $ref: "#/components/schemas/EchoState" required: - node_id - call_id - - call_state - - device - description: An incoming call available for a Relay client to control. - ReceiveCallState: + - state + description: A call echo state event. (No `control_id`.) + EchoState: type: string enum: - - created - - connecting - - connected - - disconnecting - - disconnected - CallReceiveEventFrame: + - echoing + - finished + CallEchoEventFrame: type: object required: - jsonrpc @@ -1344,7 +8997,7 @@ components: properties: event_type: type: string - const: calling.call.receive + const: calling.call.echo event_channel: type: string timestamp: @@ -1354,8 +9007,8 @@ components: project_id: type: string params: - $ref: "#/components/schemas/CallReceiveEvent" - CallConnectEvent: + $ref: "#/components/schemas/CallEchoEvent" + CallPayEvent: type: object properties: node_id: @@ -1364,46 +9017,26 @@ components: call_id: type: string description: The call id. - tag: + control_id: type: string - description: Identifier of the connect operation. - peer: - description: The peer call being connected. + description: The ID used to control the active pay. + state: + description: The payment state. allOf: - - $ref: "#/components/schemas/ConnectPeer" - connect_state: - type: string - enum: - - disconnected - - connecting - - connected - - failed - description: The connect (bridge) state. + - $ref: "#/components/schemas/PayState" required: - node_id - call_id - - peer - - connect_state - description: A call's connect (bridge/unbridge) state. - ConnectPeer: - type: object - properties: - node_id: - type: string - call_id: - type: string - tag: - type: string - queue_id: - type: string - queue_name: - type: string - device: - description: The peer's negotiated device. - allOf: - - $ref: "#/components/schemas/CallDevice" - description: The peer leg in a connect event. - CallConnectEventFrame: + - control_id + - state + description: A call payment state event. + PayState: + type: string + enum: + - processing + - finished + - error + CallPayEventFrame: type: object required: - jsonrpc @@ -1428,7 +9061,7 @@ components: properties: event_type: type: string - const: calling.call.connect + const: calling.call.pay event_channel: type: string timestamp: @@ -1438,50 +9071,29 @@ components: project_id: type: string params: - $ref: "#/components/schemas/CallConnectEvent" - CallDialEvent: + $ref: "#/components/schemas/CallPayEvent" + CallErrorEvent: type: object properties: node_id: type: string - description: Node the dial is on. - tag: + description: Node the call is on. + call_id: type: string - description: Identifier from `calling.dial`. - dial_state: + description: The call id. + code: type: string - enum: - - dialing - - answered - - failed - description: The dial operation state. - call: - description: The answered call (present when `dial_state` is `answered`). - allOf: - - $ref: "#/components/schemas/DialWinnerCall" + description: Error code (string, e.g. `"500"`). + message: + type: string + description: Error description. required: - node_id - - tag - - dial_state - description: The state of a `calling.dial` operation. - DialWinnerCall: - type: object - properties: - node_id: - type: string - call_id: - type: string - tag: - type: string - device: - description: The negotiated device. - allOf: - - $ref: "#/components/schemas/CallDevice" - dial_winner: - type: boolean - description: Whether this call is the selected (first-answered) winner. - description: The answered call carried by a `calling.call.dial` event. - CallDialEventFrame: + - call_id + - code + - message + description: A server-pushed calling error associated with a call. + CallErrorEventFrame: type: object required: - jsonrpc @@ -1506,7 +9118,7 @@ components: properties: event_type: type: string - const: calling.call.dial + const: calling.error event_channel: type: string timestamp: @@ -1516,7 +9128,7 @@ components: project_id: type: string params: - $ref: "#/components/schemas/CallDialEvent" + $ref: "#/components/schemas/CallErrorEvent" messages: callingBeginRequest: name: calling.begin.request @@ -1525,95 +9137,975 @@ components: correlationId: location: $message.payload#/id payload: - $ref: "#/components/schemas/CallingBeginRequest" - callingBeginResponse: - name: calling.begin.response - title: calling.begin response + $ref: "#/components/schemas/CallingBeginRequest" + callingBeginResponse: + name: calling.begin.response + title: calling.begin response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingBeginResponse" + callingDialRequest: + name: calling.dial.request + title: calling.dial request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDialRequest" + callingDialResponse: + name: calling.dial.response + title: calling.dial response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDialResponse" + callingAnswerRequest: + name: calling.answer.request + title: calling.answer request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAnswerRequest" + callingAnswerResponse: + name: calling.answer.response + title: calling.answer response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAnswerResponse" + callingEndRequest: + name: calling.end.request + title: calling.end request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingEndRequest" + callingEndResponse: + name: calling.end.response + title: calling.end response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingEndResponse" + callingConnectRequest: + name: calling.connect.request + title: calling.connect request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingConnectRequest" + callingConnectResponse: + name: calling.connect.response + title: calling.connect response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingConnectResponse" + callingDisconnectRequest: + name: calling.disconnect.request + title: calling.disconnect request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDisconnectRequest" + callingDisconnectResponse: + name: calling.disconnect.response + title: calling.disconnect response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDisconnectResponse" + callingCollectRequest: + name: calling.collect.request + title: calling.collect request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingCollectRequest" + callingCollectResponse: + name: calling.collect.response + title: calling.collect response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingCollectResponse" + callingCollectStopRequest: + name: calling.collect.stop.request + title: calling.collect.stop request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingCollectStopRequest" + callingCollectStopResponse: + name: calling.collect.stop.response + title: calling.collect.stop response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingCollectStopResponse" + callingCollectStartInputTimersRequest: + name: calling.collect.start_input_timers.request + title: calling.collect.start_input_timers request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingCollectStartInputTimersRequest" + callingCollectStartInputTimersResponse: + name: calling.collect.start_input_timers.response + title: calling.collect.start_input_timers response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingCollectStartInputTimersResponse" + callingPlayAndCollectRequest: + name: calling.play_and_collect.request + title: calling.play_and_collect request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayAndCollectRequest" + callingPlayAndCollectResponse: + name: calling.play_and_collect.response + title: calling.play_and_collect response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayAndCollectResponse" + callingPlayAndCollectStopRequest: + name: calling.play_and_collect.stop.request + title: calling.play_and_collect.stop request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayAndCollectStopRequest" + callingPlayAndCollectStopResponse: + name: calling.play_and_collect.stop.response + title: calling.play_and_collect.stop response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayAndCollectStopResponse" + callingPlayAndCollectVolumeRequest: + name: calling.play_and_collect.volume.request + title: calling.play_and_collect.volume request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayAndCollectVolumeRequest" + callingPlayAndCollectVolumeResponse: + name: calling.play_and_collect.volume.response + title: calling.play_and_collect.volume response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayAndCollectVolumeResponse" + callingQueueEnterRequest: + name: calling.queue.enter.request + title: calling.queue.enter request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingQueueEnterRequest" + callingQueueEnterResponse: + name: calling.queue.enter.response + title: calling.queue.enter response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingQueueEnterResponse" + callingQueueLeaveRequest: + name: calling.queue.leave.request + title: calling.queue.leave request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingQueueLeaveRequest" + callingQueueLeaveResponse: + name: calling.queue.leave.response + title: calling.queue.leave response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingQueueLeaveResponse" + callingRecordRequest: + name: calling.record.request + title: calling.record request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingRecordRequest" + callingRecordResponse: + name: calling.record.response + title: calling.record response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingRecordResponse" + callingRecordPauseRequest: + name: calling.record.pause.request + title: calling.record.pause request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingRecordPauseRequest" + callingRecordPauseResponse: + name: calling.record.pause.response + title: calling.record.pause response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingRecordPauseResponse" + callingRecordResumeRequest: + name: calling.record.resume.request + title: calling.record.resume request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingRecordResumeRequest" + callingRecordResumeResponse: + name: calling.record.resume.response + title: calling.record.resume response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingRecordResumeResponse" + callingRecordStopRequest: + name: calling.record.stop.request + title: calling.record.stop request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingRecordStopRequest" + callingRecordStopResponse: + name: calling.record.stop.response + title: calling.record.stop response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingRecordStopResponse" + callingReferRequest: + name: calling.refer.request + title: calling.refer request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingReferRequest" + callingReferResponse: + name: calling.refer.response + title: calling.refer response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingReferResponse" + callingPassRequest: + name: calling.pass.request + title: calling.pass request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPassRequest" + callingPassResponse: + name: calling.pass.response + title: calling.pass response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPassResponse" + callingPayRequest: + name: calling.pay.request + title: calling.pay request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPayRequest" + callingPayResponse: + name: calling.pay.response + title: calling.pay response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPayResponse" + callingPayStopRequest: + name: calling.pay.stop.request + title: calling.pay.stop request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPayStopRequest" + callingPayStopResponse: + name: calling.pay.stop.response + title: calling.pay.stop response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPayStopResponse" + callingPlayRequest: + name: calling.play.request + title: calling.play request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayRequest" + callingPlayResponse: + name: calling.play.response + title: calling.play response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayResponse" + callingPlayPauseRequest: + name: calling.play.pause.request + title: calling.play.pause request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayPauseRequest" + callingPlayPauseResponse: + name: calling.play.pause.response + title: calling.play.pause response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayPauseResponse" + callingPlayResumeRequest: + name: calling.play.resume.request + title: calling.play.resume request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayResumeRequest" + callingPlayResumeResponse: + name: calling.play.resume.response + title: calling.play.resume response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayResumeResponse" + callingPlayStopRequest: + name: calling.play.stop.request + title: calling.play.stop request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayStopRequest" + callingPlayStopResponse: + name: calling.play.stop.response + title: calling.play.stop response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayStopResponse" + callingPlayVolumeRequest: + name: calling.play.volume.request + title: calling.play.volume request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayVolumeRequest" + callingPlayVolumeResponse: + name: calling.play.volume.response + title: calling.play.volume response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingPlayVolumeResponse" + callingDetectRequest: + name: calling.detect.request + title: calling.detect request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDetectRequest" + callingDetectResponse: + name: calling.detect.response + title: calling.detect response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDetectResponse" + callingDetectStopRequest: + name: calling.detect.stop.request + title: calling.detect.stop request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDetectStopRequest" + callingDetectStopResponse: + name: calling.detect.stop.response + title: calling.detect.stop response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDetectStopResponse" + callingSendFaxRequest: + name: calling.send_fax.request + title: calling.send_fax request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingSendFaxRequest" + callingSendFaxResponse: + name: calling.send_fax.response + title: calling.send_fax response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingSendFaxResponse" + callingSendFaxStopRequest: + name: calling.send_fax.stop.request + title: calling.send_fax.stop request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingSendFaxStopRequest" + callingSendFaxStopResponse: + name: calling.send_fax.stop.response + title: calling.send_fax.stop response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingSendFaxStopResponse" + callingReceiveFaxRequest: + name: calling.receive_fax.request + title: calling.receive_fax request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingReceiveFaxRequest" + callingReceiveFaxResponse: + name: calling.receive_fax.response + title: calling.receive_fax response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingReceiveFaxResponse" + callingReceiveFaxStopRequest: + name: calling.receive_fax.stop.request + title: calling.receive_fax.stop request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingReceiveFaxStopRequest" + callingReceiveFaxStopResponse: + name: calling.receive_fax.stop.response + title: calling.receive_fax.stop response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingReceiveFaxStopResponse" + callingTapRequest: + name: calling.tap.request + title: calling.tap request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingTapRequest" + callingTapResponse: + name: calling.tap.response + title: calling.tap response contentType: application/json correlationId: location: $message.payload#/id payload: - $ref: "#/components/schemas/CallingBeginResponse" - callingDialRequest: - name: calling.dial.request - title: calling.dial request + $ref: "#/components/schemas/CallingTapResponse" + callingTapStopRequest: + name: calling.tap.stop.request + title: calling.tap.stop request contentType: application/json correlationId: location: $message.payload#/id payload: - $ref: "#/components/schemas/CallingDialRequest" - callingDialResponse: - name: calling.dial.response - title: calling.dial response + $ref: "#/components/schemas/CallingTapStopRequest" + callingTapStopResponse: + name: calling.tap.stop.response + title: calling.tap.stop response contentType: application/json correlationId: location: $message.payload#/id payload: - $ref: "#/components/schemas/CallingDialResponse" - callingAnswerRequest: - name: calling.answer.request - title: calling.answer request + $ref: "#/components/schemas/CallingTapStopResponse" + callingStreamRequest: + name: calling.stream.request + title: calling.stream request contentType: application/json correlationId: location: $message.payload#/id payload: - $ref: "#/components/schemas/CallingAnswerRequest" - callingAnswerResponse: - name: calling.answer.response - title: calling.answer response + $ref: "#/components/schemas/CallingStreamRequest" + callingStreamResponse: + name: calling.stream.response + title: calling.stream response contentType: application/json correlationId: location: $message.payload#/id payload: - $ref: "#/components/schemas/CallingAnswerResponse" - callingEndRequest: - name: calling.end.request - title: calling.end request + $ref: "#/components/schemas/CallingStreamResponse" + callingStreamStopRequest: + name: calling.stream.stop.request + title: calling.stream.stop request contentType: application/json correlationId: location: $message.payload#/id payload: - $ref: "#/components/schemas/CallingEndRequest" - callingEndResponse: - name: calling.end.response - title: calling.end response + $ref: "#/components/schemas/CallingStreamStopRequest" + callingStreamStopResponse: + name: calling.stream.stop.response + title: calling.stream.stop response contentType: application/json correlationId: location: $message.payload#/id payload: - $ref: "#/components/schemas/CallingEndResponse" - callingConnectRequest: - name: calling.connect.request - title: calling.connect request + $ref: "#/components/schemas/CallingStreamStopResponse" + callingTransferRequest: + name: calling.transfer.request + title: calling.transfer request contentType: application/json correlationId: location: $message.payload#/id payload: - $ref: "#/components/schemas/CallingConnectRequest" - callingConnectResponse: - name: calling.connect.response - title: calling.connect response + $ref: "#/components/schemas/CallingTransferRequest" + callingTransferResponse: + name: calling.transfer.response + title: calling.transfer response contentType: application/json correlationId: location: $message.payload#/id payload: - $ref: "#/components/schemas/CallingConnectResponse" - callingDisconnectRequest: - name: calling.disconnect.request - title: calling.disconnect request + $ref: "#/components/schemas/CallingTransferResponse" + callingJoinConferenceRequest: + name: calling.join_conference.request + title: calling.join_conference request contentType: application/json correlationId: location: $message.payload#/id payload: - $ref: "#/components/schemas/CallingDisconnectRequest" - callingDisconnectResponse: - name: calling.disconnect.response - title: calling.disconnect response + $ref: "#/components/schemas/CallingJoinConferenceRequest" + callingJoinConferenceResponse: + name: calling.join_conference.response + title: calling.join_conference response contentType: application/json correlationId: location: $message.payload#/id payload: - $ref: "#/components/schemas/CallingDisconnectResponse" + $ref: "#/components/schemas/CallingJoinConferenceResponse" + callingLeaveConferenceRequest: + name: calling.leave_conference.request + title: calling.leave_conference request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingLeaveConferenceRequest" + callingLeaveConferenceResponse: + name: calling.leave_conference.response + title: calling.leave_conference response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingLeaveConferenceResponse" + callingHoldRequest: + name: calling.hold.request + title: calling.hold request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingHoldRequest" + callingHoldResponse: + name: calling.hold.response + title: calling.hold response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingHoldResponse" + callingUnholdRequest: + name: calling.unhold.request + title: calling.unhold request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingUnholdRequest" + callingUnholdResponse: + name: calling.unhold.response + title: calling.unhold response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingUnholdResponse" + callingDenoiseRequest: + name: calling.denoise.request + title: calling.denoise request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDenoiseRequest" + callingDenoiseResponse: + name: calling.denoise.response + title: calling.denoise response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDenoiseResponse" + callingDenoiseStopRequest: + name: calling.denoise.stop.request + title: calling.denoise.stop request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDenoiseStopRequest" + callingDenoiseStopResponse: + name: calling.denoise.stop.response + title: calling.denoise.stop response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingDenoiseStopResponse" + callingSendDigitsRequest: + name: calling.send_digits.request + title: calling.send_digits request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingSendDigitsRequest" + callingSendDigitsResponse: + name: calling.send_digits.response + title: calling.send_digits response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingSendDigitsResponse" + callingTranscribeRequest: + name: calling.transcribe.request + title: calling.transcribe request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingTranscribeRequest" + callingTranscribeResponse: + name: calling.transcribe.response + title: calling.transcribe response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingTranscribeResponse" + callingTranscribeStopRequest: + name: calling.transcribe.stop.request + title: calling.transcribe.stop request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingTranscribeStopRequest" + callingTranscribeStopResponse: + name: calling.transcribe.stop.response + title: calling.transcribe.stop response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingTranscribeStopResponse" + callingEchoRequest: + name: calling.echo.request + title: calling.echo request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingEchoRequest" + callingEchoResponse: + name: calling.echo.response + title: calling.echo response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingEchoResponse" + callingBindDigitRequest: + name: calling.bind_digit.request + title: calling.bind_digit request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingBindDigitRequest" + callingBindDigitResponse: + name: calling.bind_digit.response + title: calling.bind_digit response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingBindDigitResponse" + callingClearDigitBindingsRequest: + name: calling.clear_digit_bindings.request + title: calling.clear_digit_bindings request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingClearDigitBindingsRequest" + callingClearDigitBindingsResponse: + name: calling.clear_digit_bindings.response + title: calling.clear_digit_bindings response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingClearDigitBindingsResponse" + callingLiveTranscribeRequest: + name: calling.live_transcribe.request + title: calling.live_transcribe request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingLiveTranscribeRequest" + callingLiveTranscribeResponse: + name: calling.live_transcribe.response + title: calling.live_transcribe response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingLiveTranscribeResponse" + callingLiveTranslateRequest: + name: calling.live_translate.request + title: calling.live_translate request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingLiveTranslateRequest" + callingLiveTranslateResponse: + name: calling.live_translate.response + title: calling.live_translate response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingLiveTranslateResponse" + callingJoinRoomRequest: + name: calling.join_room.request + title: calling.join_room request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingJoinRoomRequest" + callingJoinRoomResponse: + name: calling.join_room.response + title: calling.join_room response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingJoinRoomResponse" + callingLeaveRoomRequest: + name: calling.leave_room.request + title: calling.leave_room request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingLeaveRoomRequest" + callingLeaveRoomResponse: + name: calling.leave_room.response + title: calling.leave_room response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingLeaveRoomResponse" + callingAiRequest: + name: calling.ai.request + title: calling.ai request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAiRequest" + callingAiResponse: + name: calling.ai.response + title: calling.ai response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAiResponse" + callingAiStopRequest: + name: calling.ai.stop.request + title: calling.ai.stop request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAiStopRequest" + callingAiStopResponse: + name: calling.ai.stop.response + title: calling.ai.stop response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAiStopResponse" + callingAmazonBedrockRequest: + name: calling.amazon_bedrock.request + title: calling.amazon_bedrock request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAmazonBedrockRequest" + callingAmazonBedrockResponse: + name: calling.amazon_bedrock.response + title: calling.amazon_bedrock response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAmazonBedrockResponse" + callingAiMessageRequest: + name: calling.ai_message.request + title: calling.ai_message request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAiMessageRequest" + callingAiMessageResponse: + name: calling.ai_message.response + title: calling.ai_message response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAiMessageResponse" + callingAiHoldRequest: + name: calling.ai_hold.request + title: calling.ai_hold request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAiHoldRequest" + callingAiHoldResponse: + name: calling.ai_hold.response + title: calling.ai_hold response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAiHoldResponse" + callingAiUnholdRequest: + name: calling.ai_unhold.request + title: calling.ai_unhold request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAiUnholdRequest" + callingAiUnholdResponse: + name: calling.ai_unhold.response + title: calling.ai_unhold response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingAiUnholdResponse" + callingUserEventRequest: + name: calling.user_event.request + title: calling.user_event request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingUserEventRequest" + callingUserEventResponse: + name: calling.user_event.response + title: calling.user_event response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/CallingUserEventResponse" callStateEvent: name: calling.call.state title: calling.call.state event @@ -1638,6 +10130,108 @@ components: contentType: application/json payload: $ref: "#/components/schemas/CallDialEventFrame" + callReferEvent: + name: calling.call.refer + title: calling.call.refer event + contentType: application/json + payload: + $ref: "#/components/schemas/CallReferEventFrame" + callPlayEvent: + name: calling.call.play + title: calling.call.play event + contentType: application/json + payload: + $ref: "#/components/schemas/CallPlayEventFrame" + callQueueEvent: + name: calling.call.queue + title: calling.call.queue event + contentType: application/json + payload: + $ref: "#/components/schemas/CallQueueEventFrame" + callCollectEvent: + name: calling.call.collect + title: calling.call.collect event + contentType: application/json + payload: + $ref: "#/components/schemas/CallCollectEventFrame" + callRecordEvent: + name: calling.call.record + title: calling.call.record event + contentType: application/json + payload: + $ref: "#/components/schemas/CallRecordEventFrame" + callDetectEvent: + name: calling.call.detect + title: calling.call.detect event + contentType: application/json + payload: + $ref: "#/components/schemas/CallDetectEventFrame" + callDenoiseEvent: + name: calling.call.denoise + title: calling.call.denoise event + contentType: application/json + payload: + $ref: "#/components/schemas/CallDenoiseEventFrame" + callFaxEvent: + name: calling.call.fax + title: calling.call.fax event + contentType: application/json + payload: + $ref: "#/components/schemas/CallFaxEventFrame" + callTapEvent: + name: calling.call.tap + title: calling.call.tap event + contentType: application/json + payload: + $ref: "#/components/schemas/CallTapEventFrame" + callStreamEvent: + name: calling.call.stream + title: calling.call.stream event + contentType: application/json + payload: + $ref: "#/components/schemas/CallStreamEventFrame" + callTranscribeEvent: + name: calling.call.transcribe + title: calling.call.transcribe event + contentType: application/json + payload: + $ref: "#/components/schemas/CallTranscribeEventFrame" + callHoldEvent: + name: calling.call.hold + title: calling.call.hold event + contentType: application/json + payload: + $ref: "#/components/schemas/CallHoldEventFrame" + callSendDigitsEvent: + name: calling.call.send_digits + title: calling.call.send_digits event + contentType: application/json + payload: + $ref: "#/components/schemas/CallSendDigitsEventFrame" + conferenceEvent: + name: calling.conference + title: calling.conference event + contentType: application/json + payload: + $ref: "#/components/schemas/ConferenceEventFrame" + callEchoEvent: + name: calling.call.echo + title: calling.call.echo event + contentType: application/json + payload: + $ref: "#/components/schemas/CallEchoEventFrame" + callPayEvent: + name: calling.call.pay + title: calling.call.pay event + contentType: application/json + payload: + $ref: "#/components/schemas/CallPayEventFrame" + callErrorEvent: + name: calling.error + title: calling.error event + contentType: application/json + payload: + $ref: "#/components/schemas/CallErrorEventFrame" securitySchemes: httpBearer: type: http diff --git a/fern/apis/relay/generators.yml b/fern/apis/relay/generators.yml index c3680635d2..e21bb68774 100644 --- a/fern/apis/relay/generators.yml +++ b/fern/apis/relay/generators.yml @@ -3,3 +3,7 @@ api: specs: - asyncapi: signalwire.yaml - asyncapi: calling.yaml + - asyncapi: messaging.yaml + - asyncapi: tasking.yaml + - asyncapi: provisioning.yaml + - asyncapi: webrtc.yaml diff --git a/fern/apis/relay/messaging.yaml b/fern/apis/relay/messaging.yaml new file mode 100644 index 0000000000..6844d3ca05 --- /dev/null +++ b/fern/apis/relay/messaging.yaml @@ -0,0 +1,374 @@ +asyncapi: 3.0.0 +info: + title: SignalWire Relay — Messaging + version: 1.0.0 + description: |- + The Relay **Messaging** service sends outbound SMS/MMS to PSTN numbers and + delivers inbound-message and delivery-state events. It rides on a connection + established by `signalwire.connect`; messages are routed by **context**. +defaultContentType: application/json +servers: + production: + host: relay.signalwire.com + protocol: wss + pathname: /api/relay/wss + description: SignalWire Relay WebSocket endpoint. + security: + - $ref: "#/components/securitySchemes/httpBearer" + bindings: + ws: {} +channels: + messaging: + address: null + title: SignalWire Relay — Messaging + servers: + - $ref: "#/servers/production" + messages: + messagingSendRequest: + $ref: "#/components/messages/messagingSendRequest" + messagingSendResponse: + $ref: "#/components/messages/messagingSendResponse" + receiveEvent: + $ref: "#/components/messages/receiveEvent" + stateEvent: + $ref: "#/components/messages/stateEvent" + bindings: + ws: {} +operations: + messagingSend: + action: send + channel: + $ref: "#/channels/messaging" + title: messaging.send + summary: Send an outbound message + messages: + - $ref: "#/channels/messaging/messages/messagingSendRequest" + reply: + channel: + $ref: "#/channels/messaging" + messages: + - $ref: "#/channels/messaging/messages/messagingSendResponse" + onMessagingEvent: + action: receive + channel: + $ref: "#/channels/messaging" + title: signalwire.event + summary: Asynchronous events pushed by the server over the signalwire.event carrier. + messages: + - $ref: "#/channels/messaging/messages/receiveEvent" + - $ref: "#/channels/messaging/messages/stateEvent" +components: + schemas: + SendParams: + type: object + properties: + context: + type: string + description: The context to receive inbound events for this message. + tags: + type: array + items: + type: string + description: Optional client-defined tags, surfaced for searching in the UI. + region: + type: string + description: |- + Region of the world to originate the message from. Defaults to a value + picked from account preferences or device location. + to_number: + type: string + description: Destination phone number, in E.164 format. + from_number: + type: string + description: Origin phone number, in E.164 format. + body: + type: string + description: |- + Body of the message. Required if `media` is absent; at least one of `body` + or `media` must be present (both may be supplied). + media: + type: array + items: + type: string + description: |- + An array of media URLs to send (MMS). Required if `body` is absent; at + least one of `body` or `media` must be present (both may be supplied). + required: + - context + - to_number + - from_number + MessagingSendRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: messaging.send + params: + $ref: "#/components/schemas/SendParams" + SendResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + message_id: + type: string + description: The UUID of the accepted message (present on success). + required: + - code + - message + - message_id + MessagingSendResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/SendResult" + ReceiveEvent: + type: object + properties: + message_id: + type: string + description: The UUID of the message. + context: + type: string + description: The context the message was set on. + direction: + description: The message's direction. Always `inbound` for this event. + allOf: + - $ref: "#/components/schemas/MessageDirection" + tags: + type: array + items: + type: string + description: Optional client data this message is tagged with. + from_number: + type: string + description: Origin phone number, in E.164 format. + to_number: + type: string + description: Destination phone number, in E.164 format. + body: + type: string + description: Body of the message. + media: + type: array + items: + type: string + description: An array of media URLs included with the message. + segments: + type: integer + format: int32 + description: Number of segments the message was split into. + message_state: + type: string + enum: + - received + description: The message state. Always `received` for an inbound message. + required: + - message_id + - context + - direction + - from_number + - to_number + - body + - media + - segments + - message_state + description: An inbound message has been received. + MessageDirection: + type: string + enum: + - inbound + - outbound + ReceiveEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: messaging.receive + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/ReceiveEvent" + StateEvent: + type: object + properties: + message_id: + type: string + description: The UUID of the message. + context: + type: string + description: The context the message was set on. + direction: + description: The message's direction. + allOf: + - $ref: "#/components/schemas/MessageDirection" + tags: + type: array + items: + type: string + description: Optional client data this message is tagged with. + from_number: + type: string + description: Origin phone number, in E.164 format. + to_number: + type: string + description: Destination phone number, in E.164 format. + body: + type: string + description: Body of the message. + media: + type: array + items: + type: string + description: An array of media URLs included with the message. + segments: + type: integer + format: int32 + description: Number of segments the message was split into. + message_state: + description: The new delivery-lifecycle state of the message. + allOf: + - $ref: "#/components/schemas/MessageState" + reason: + type: string + description: Explanation of the state. Present only on `undelivered`/`failed`. + required: + - message_id + - context + - direction + - from_number + - to_number + - body + - media + - segments + - message_state + description: A change in the delivery state of a message. + MessageState: + type: string + enum: + - queued + - initiated + - sent + - delivered + - undelivered + - failed + StateEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: messaging.state + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/StateEvent" + messages: + messagingSendRequest: + name: messaging.send.request + title: messaging.send request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/MessagingSendRequest" + messagingSendResponse: + name: messaging.send.response + title: messaging.send response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/MessagingSendResponse" + receiveEvent: + name: messaging.receive + title: messaging.receive event + contentType: application/json + payload: + $ref: "#/components/schemas/ReceiveEventFrame" + stateEvent: + name: messaging.state + title: messaging.state event + contentType: application/json + payload: + $ref: "#/components/schemas/StateEventFrame" + securitySchemes: + httpBearer: + type: http + scheme: bearer + bearerFormat: JWT diff --git a/fern/apis/relay/provisioning.yaml b/fern/apis/relay/provisioning.yaml new file mode 100644 index 0000000000..0192298b9e --- /dev/null +++ b/fern/apis/relay/provisioning.yaml @@ -0,0 +1,165 @@ +asyncapi: 3.0.0 +info: + title: SignalWire Relay — Connector Provisioning + version: 1.0.0 + description: |- + The `provisioning` protocol lets a Relay **connector** request its runtime + configuration from SignalWire. The connector reports its identity and network + endpoints, and SignalWire returns the rendered connector configuration (for a + FreeSWITCH connector, a SIP profile as XML). This service is + connector-internal: a single method, no server-pushed events, and currently + only the `freeswitch` connector target is supported. +defaultContentType: application/json +servers: + production: + host: relay.signalwire.com + protocol: wss + pathname: /api/relay/wss + description: SignalWire Relay WebSocket endpoint. + security: + - $ref: "#/components/securitySchemes/httpBearer" + bindings: + ws: {} +channels: + provisioning: + address: null + title: SignalWire Relay — Connector Provisioning + servers: + - $ref: "#/servers/production" + messages: + provisioningConfigureRequest: + $ref: "#/components/messages/provisioningConfigureRequest" + provisioningConfigureResponse: + $ref: "#/components/messages/provisioningConfigureResponse" + bindings: + ws: {} +operations: + provisioningConfigure: + action: send + channel: + $ref: "#/channels/provisioning" + title: provisioning.configure + summary: Request SignalWire connector configuration + messages: + - $ref: "#/channels/provisioning/messages/provisioningConfigureRequest" + reply: + channel: + $ref: "#/channels/provisioning" + messages: + - $ref: "#/channels/provisioning/messages/provisioningConfigureResponse" +components: + schemas: + ConfigureParams: + type: object + properties: + target: + description: The connector type to provision. Currently only `freeswitch` is supported. + allOf: + - $ref: "#/components/schemas/ConnectorTarget" + local_endpoint: + type: string + description: The connector's local (internal) endpoint as an IPv4 address, e.g. `10.10.0.2`. + external_endpoint: + type: string + description: The connector's external (public) endpoint as an IPv4 address, e.g. `8.8.8.8`. + relay_connector_id: + type: string + description: UUID of the Relay connector being configured. + required: + - target + - local_endpoint + - external_endpoint + - relay_connector_id + ConnectorTarget: + oneOf: + - type: string + - type: string + enum: + - freeswitch + ProvisioningConfigureRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: provisioning.configure + params: + $ref: "#/components/schemas/ConfigureParams" + ConfigureResult: + type: object + properties: + code: + type: string + description: Result code (string). `"200"` on success; e.g. `"400"`/`"404"` on error. + message: + type: string + description: Human-readable result message. + configuration: + description: The rendered connector configuration. + allOf: + - $ref: "#/components/schemas/Configuration" + required: + - code + - message + - configuration + Configuration: + type: object + properties: + profile: + type: string + description: The FreeSWITCH SIP profile, rendered as an XML document. + required: + - profile + description: |- + The rendered connector configuration returned to the connector. + + Note: `profile` is the raw FreeSWITCH SIP profile **rendered as XML**, carried + as a single string. The precise shape (raw-XML string vs. a structured object) + is not specified by the source and is modeled here as an opaque string. Other + keys under `configuration` for non-`freeswitch` targets are unconfirmed. + ProvisioningConfigureResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/ConfigureResult" + messages: + provisioningConfigureRequest: + name: provisioning.configure.request + title: provisioning.configure request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/ProvisioningConfigureRequest" + provisioningConfigureResponse: + name: provisioning.configure.response + title: provisioning.configure response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/ProvisioningConfigureResponse" + securitySchemes: + httpBearer: + type: http + scheme: bearer + bearerFormat: JWT diff --git a/fern/apis/relay/tasking.yaml b/fern/apis/relay/tasking.yaml new file mode 100644 index 0000000000..b5401ec3d8 --- /dev/null +++ b/fern/apis/relay/tasking.yaml @@ -0,0 +1,216 @@ +asyncapi: 3.0.0 +info: + title: SignalWire Relay — Tasking + version: 1.0.0 + description: |- + The `tasking` service delivers arbitrary, caller-defined JSON messages to + Relay consumers subscribed to a context. A client calls `tasking.deliver` + with a `context` and an opaque `message`; SignalWire queues the task and + pushes a `queuing.relay.tasks` event to every consumer listening on that + context. The `message` payload is never inspected — it is echoed verbatim + from the deliver request into the task event. +defaultContentType: application/json +servers: + production: + host: relay.signalwire.com + protocol: wss + pathname: /api/relay/wss + description: SignalWire Relay WebSocket endpoint. + security: + - $ref: "#/components/securitySchemes/httpBearer" + bindings: + ws: {} +channels: + tasking: + address: null + title: SignalWire Relay — Tasking + servers: + - $ref: "#/servers/production" + messages: + taskingDeliverRequest: + $ref: "#/components/messages/taskingDeliverRequest" + taskingDeliverResponse: + $ref: "#/components/messages/taskingDeliverResponse" + tasksEvent: + $ref: "#/components/messages/tasksEvent" + bindings: + ws: {} +operations: + taskingDeliver: + action: send + channel: + $ref: "#/channels/tasking" + title: tasking.deliver + summary: Deliver a task message to a context + messages: + - $ref: "#/channels/tasking/messages/taskingDeliverRequest" + reply: + channel: + $ref: "#/channels/tasking" + messages: + - $ref: "#/channels/tasking/messages/taskingDeliverResponse" + onTaskingEvent: + action: receive + channel: + $ref: "#/channels/tasking" + title: signalwire.event + summary: Asynchronous events pushed by the server over the signalwire.event carrier. + messages: + - $ref: "#/channels/tasking/messages/tasksEvent" +components: + schemas: + DeliverParams: + type: object + properties: + context: + type: string + description: The context to deliver the task to. Consumers subscribed to this context receive the `queuing.relay.tasks` event. + message: + type: object + additionalProperties: {} + description: |- + The message to send. Opaque, caller-defined JSON (e.g. `{ "foo": 123 }`); + SignalWire imposes no schema and echoes it verbatim into the task event. + required: + - context + - message + TaskingDeliverRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: tasking.deliver + params: + $ref: "#/components/schemas/DeliverParams" + DeliverResult: + type: object + properties: + code: + type: string + description: Result code (string). `"200"` on success. + message: + type: string + description: Human-readable result message. + required: + - code + - message + description: Acknowledgement of a `tasking.deliver` request. + TaskingDeliverResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/DeliverResult" + TasksEvent: + type: object + properties: + context: + type: string + description: The context that received the event. + timestamp: + type: number + format: double + description: Seconds since the epoch, with up to microsecond resolution. The time the task was received. + space_id: + type: string + description: The SignalWire space the task belongs to. + project_id: + type: string + description: The SignalWire project the task belongs to. + message: + type: object + additionalProperties: {} + description: The opaque message passed to the task, echoed verbatim from `tasking.deliver`. + required: + - context + - timestamp + - space_id + - project_id + - message + description: |- + A task has been received. Pushed to consumers subscribed to the task's + `context`, carrying the opaque `message` echoed verbatim from the + originating `tasking.deliver` call. + TasksEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: queuing.relay.tasks + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/TasksEvent" + messages: + taskingDeliverRequest: + name: tasking.deliver.request + title: tasking.deliver request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/TaskingDeliverRequest" + taskingDeliverResponse: + name: tasking.deliver.response + title: tasking.deliver response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/TaskingDeliverResponse" + tasksEvent: + name: queuing.relay.tasks + title: queuing.relay.tasks event + contentType: application/json + payload: + $ref: "#/components/schemas/TasksEventFrame" + securitySchemes: + httpBearer: + type: http + scheme: bearer + bearerFormat: JWT diff --git a/fern/apis/relay/webrtc.yaml b/fern/apis/relay/webrtc.yaml new file mode 100644 index 0000000000..d75c797fa7 --- /dev/null +++ b/fern/apis/relay/webrtc.yaml @@ -0,0 +1,340 @@ +asyncapi: 3.0.0 +info: + title: SignalWire Relay — WebRTC + version: 1.0.0 + description: |- + The Relay **WebRTC** service is a thin transport wrapper around the Verto + signaling sub-protocol. Methods are dispatched as `blade.execute`; the + `message` method tunnels an opaque inner Verto JSON-RPC frame to FreeSWITCH, + and `conference.list` enumerates joinable conferences. It rides on a + connection established by `signalwire.connect`. + + The inner Verto message protocol itself (`verto.invite`/`verto.answer`/ + `verto.bye`/`verto.modify`, `dialogParams`, conference control) is OUT OF + SCOPE here and is modeled as a loose pass-through. Its full union is defined + separately in `verto_messages.md`. +defaultContentType: application/json +servers: + production: + host: relay.signalwire.com + protocol: wss + pathname: /api/relay/wss + description: SignalWire Relay WebSocket endpoint. + security: + - $ref: "#/components/securitySchemes/httpBearer" + bindings: + ws: {} +channels: + webrtc: + address: null + title: SignalWire Relay — WebRTC + servers: + - $ref: "#/servers/production" + messages: + messageRequest: + $ref: "#/components/messages/messageRequest" + messageResponse: + $ref: "#/components/messages/messageResponse" + conferenceListRequest: + $ref: "#/components/messages/conferenceListRequest" + conferenceListResponse: + $ref: "#/components/messages/conferenceListResponse" + messageEvent: + $ref: "#/components/messages/messageEvent" + bindings: + ws: {} +operations: + message: + action: send + channel: + $ref: "#/channels/webrtc" + title: message + summary: Transport a Verto message to FreeSWITCH + messages: + - $ref: "#/channels/webrtc/messages/messageRequest" + reply: + channel: + $ref: "#/channels/webrtc" + messages: + - $ref: "#/channels/webrtc/messages/messageResponse" + conferenceList: + action: send + channel: + $ref: "#/channels/webrtc" + title: conference.list + summary: List joinable conferences + messages: + - $ref: "#/channels/webrtc/messages/conferenceListRequest" + reply: + channel: + $ref: "#/channels/webrtc" + messages: + - $ref: "#/channels/webrtc/messages/conferenceListResponse" + onWebrtcEvent: + action: receive + channel: + $ref: "#/channels/webrtc" + title: signalwire.event + summary: Asynchronous events pushed by the server over the signalwire.event carrier. + messages: + - $ref: "#/channels/webrtc/messages/messageEvent" +components: + schemas: + MessageParams: + type: object + properties: + node_id: + type: string + description: |- + The FreeSWITCH node id this message targets. Set by the client to the FS + nodeid once a call exists (sourced from prior events/responses); absent on + the very first message before a call is established. + message: + type: object + additionalProperties: {} + description: |- + The inner Verto JSON-RPC 2.0 frame to transport to FreeSWITCH (e.g. a + `verto.invite` with `dialogParams`/`sdp`/`layout`/`positions`). Modeled as + a loose pass-through: the full Verto method/`params` union is out of scope + here (see `verto_messages.md`). + subscribe: + type: array + items: + type: string + description: |- + "Event channel" subscriptions to apply alongside this request — intended + for the case of joining a conference and wanting its event feed. Values are + conference/room event channels (e.g. `member.joined`, `member.left`, + `room.ended`, `room.updated`, `layout.changed`, `member.updated`); + illustrative, not exhaustive. + required: + - message + MessageRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: message + params: + $ref: "#/components/schemas/MessageParams" + MessageResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + required: + - code + - message + description: Acknowledgement that the Verto message was received and forwarded. + MessageResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/MessageResult" + ConferenceListParams: + type: object + properties: {} + description: Empty parameters — `conference.list` takes no arguments. + ConferenceListRequest: + type: object + required: + - jsonrpc + - id + - method + - params + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + method: + type: string + const: conference.list + params: + $ref: "#/components/schemas/ConferenceListParams" + ConferenceListResult: + type: object + properties: + code: + type: string + description: Result code (string), e.g. `"200"`, `"400"`, `"404"`. + message: + type: string + description: Human-readable result message. + data: + type: array + items: + $ref: "#/components/schemas/Conference" + description: The active conferences the current client can join. + required: + - code + - message + - data + Conference: + type: object + properties: + node_id: + type: string + description: The FreeSWITCH node id hosting the conference. + conference_id: + type: string + description: The conference's UUID. + name: + type: string + description: Human-readable conference name (e.g. `Awesome Room!`). + extension: + type: string + description: Extension to dial to reach the conference. + timestamp: + type: number + format: double + description: Creation/last-activity time, in seconds since epoch (microsecond resolution). + required: + - node_id + - conference_id + - name + - extension + - timestamp + description: A single active conference the client may join. + ConferenceListResponse: + type: object + required: + - jsonrpc + - id + properties: + jsonrpc: + type: string + const: "2.0" + id: + type: string + format: uuid + result: + $ref: "#/components/schemas/ConferenceListResult" + MessageEvent: + type: object + properties: + node_id: + type: string + description: |- + The FreeSWITCH node id sending the event. Sent by FS so the client can + capture the specific nodeid once a call is started. + params: + type: object + additionalProperties: {} + description: |- + The Verto JSON-RPC frame being transported (a Verto response or a + conference/room event). Loose pass-through — see `verto_messages.md`. + required: + - params + description: |- + Transport event delivering a Verto JSON-RPC message from FreeSWITCH back to + the client — both Verto responses and subscribed conference/room events. + + The inner `params` is the opaque Verto frame; its full union is out of scope + here (see `verto_messages.md`). + MessageEventFrame: + type: object + required: + - jsonrpc + - method + - id + - params + properties: + jsonrpc: + type: string + const: "2.0" + method: + type: string + const: signalwire.event + id: + type: string + format: uuid + params: + type: object + required: + - event_type + - params + properties: + event_type: + type: string + const: webrtc.message + event_channel: + type: string + timestamp: + type: number + space_id: + type: string + project_id: + type: string + params: + $ref: "#/components/schemas/MessageEvent" + messages: + messageRequest: + name: message.request + title: message request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/MessageRequest" + messageResponse: + name: message.response + title: message response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/MessageResponse" + conferenceListRequest: + name: conference.list.request + title: conference.list request + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/ConferenceListRequest" + conferenceListResponse: + name: conference.list.response + title: conference.list response + contentType: application/json + correlationId: + location: $message.payload#/id + payload: + $ref: "#/components/schemas/ConferenceListResponse" + messageEvent: + name: webrtc.message + title: webrtc.message event + contentType: application/json + payload: + $ref: "#/components/schemas/MessageEventFrame" + securitySchemes: + httpBearer: + type: http + scheme: bearer + bearerFormat: JWT diff --git a/specs/package.json b/specs/package.json index cca6eb0a1e..f65e7bd254 100644 --- a/specs/package.json +++ b/specs/package.json @@ -7,7 +7,7 @@ "build:all": "yarn build:api && yarn build:schema && yarn build:relay", "build:api": "yarn build:signalwire-rest && yarn build:compatibility-api", "build:schema": "yarn build:swml-calling && yarn build:swml-messaging", - "build:relay": "yarn build:relay-signalwire && yarn build:relay-calling", + "build:relay": "yarn build:relay-signalwire && yarn build:relay-calling && yarn build:relay-messaging && yarn build:relay-tasking && yarn build:relay-provisioning && yarn build:relay-webrtc", "build:relay-signalwire": "cd ./relay/signalwire && tsp compile . && cd ../..", "build:relay-calling": "cd ./relay/calling && tsp compile . && cd ../..", "build:relay-messaging": "cd ./relay/messaging && tsp compile . && cd ../..", diff --git a/specs/relay/calling/common.tsp b/specs/relay/calling/common.tsp index 67a943a71a..6a2fd380f8 100644 --- a/specs/relay/calling/common.tsp +++ b/specs/relay/calling/common.tsp @@ -433,3 +433,62 @@ model CallWebrtcDevice extends CallDevice { /** WebRTC device params (shapes not documented in the protocol reference). */ params: Record; } + +// ═════════════════════════════════════════════════════════════════════════════ +// Play media union (calling.play / calling.play_and_collect `play[]`) +// +// Discriminated on `type`. Mirrors Ringback, but the `tts` variant additionally +// accepts a `voice` field. Shared by calling.play and calling.play_and_collect. +// ═════════════════════════════════════════════════════════════════════════════ + +/** A media element to play. Discriminated on `type`. */ +@discriminator("type") +model PlayMedia { + type: string; +} + +model PlayMediaAudio extends PlayMedia { + type: "audio"; + params: { + /** HTTP(s) URL to the audio resource to play. */ + url: url; + }; +} + +model PlayMediaTts extends PlayMedia { + type: "tts"; + params: { + /** Text to speak — plain text or SSML markup. */ + text: string; + + /** TTS language (e.g. `en-US`). Default `en-US`. */ + language?: string = "en-US"; + + /** TTS voice gender. Default `female`. */ + gender?: TtsGender = "female"; + + /** Specific voice to use. Highest precedence when selecting the TTS voice. */ + voice?: string; + }; +} + +model PlayMediaSilence extends PlayMedia { + type: "silence"; + params: { + /** Seconds of silence to play. */ + @minValueExclusive(0) + duration: float64; + }; +} + +model PlayMediaRingtone extends PlayMedia { + type: "ringtone"; + params: { + /** Built-in ringtone name (country code). */ + name: ToneName; + + /** Seconds of ringtone to play. */ + @minValueExclusive(0) + duration?: float64; + }; +} diff --git a/specs/relay/calling/events/media-1.tsp b/specs/relay/calling/events/media-1.tsp new file mode 100644 index 0000000000..a406ec7696 --- /dev/null +++ b/specs/relay/calling/events/media-1.tsp @@ -0,0 +1,399 @@ +import "@signalwire/typespec-asyncapi"; +import "../common.tsp"; + +using SignalWire.AsyncAPI; + +namespace Relay.Calling; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.refer +// ───────────────────────────────────────────────────────────────────────────── + +/** State of a SIP-REFER transfer (`calling.call.refer`). */ +union ReferState { + "inProgress", + "cancel", + "busy", + "noAnswer", + "error", + "success", +} + +model CallReferParams { + ...CallAddress; + + /** The transfer state. */ + state: ReferState; + + /** The SIP URI the call is being transferred to. */ + sip_refer_to?: string; + + /** SIP response code to the REFER request (string, e.g. `"202"`). */ + sip_refer_response_code?: string; + + /** + * SIP response code to the NOTIFY(s) received after the REFER (string, e.g. + * `"200"`). Indicates whether the transfer ultimately succeeded. + */ + sip_notify_response_code?: string; +} + +/** A change in state of a transferred (SIP-REFER) call. */ +@event("calling.call.refer") +model CallReferEvent { + ...CallReferParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.play +// ───────────────────────────────────────────────────────────────────────────── + +/** State of an active play (`calling.call.play`). */ +union CallPlayState { + "playing", + "paused", + "error", + "finished", +} + +model CallPlayParams { + ...CallAddress; + + /** Identifier of the active play (from `calling.play`). */ + control_id: string; + + /** The play state. */ + state: CallPlayState; +} + +/** A change in a call's play state. */ +@event("calling.call.play") +model CallPlayEvent { + ...CallPlayParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.queue +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Queue transition reported by `calling.call.queue`. (The prose param-list + * mislabels this field `state` with play-style values; the wire payload field is + * `status` with these values — payload is authoritative.) + */ +union CallQueueStatus { + "enqueue", + "dequeue", + "leave", +} + +model CallQueueParams { + ...CallAddress; + + /** Identifier of the active queue (from `calling.queue.enter`). */ + control_id: string; + + /** The queue transition. */ + status?: CallQueueStatus; + + /** Queue id. */ + id?: string; + + /** Queue name. */ + name?: string; + + /** Position of the call within the queue. */ + position?: float64; + + /** Number of calls in the queue. */ + size?: float64; + + /** Average time (seconds) calls spend in the queue. */ + avg_time?: float64; + + /** Epoch (seconds) the call entered the queue. */ + enqueue_ts?: float64; + + /** Epoch (seconds) the call was dequeued. */ + dequeue_ts?: float64; + + /** Epoch (seconds) the call left the queue. */ + leave_ts?: float64; +} + +/** A change in a call's queue state. */ +@event("calling.call.queue") +model CallQueueEvent { + ...CallQueueParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.collect +// ───────────────────────────────────────────────────────────────────────────── + +/** Overall state of an active collect (`calling.call.collect`). */ +union CallCollectState { + "collecting", + "error", + "finished", +} + +/** + * The collected input. Discriminated on `type`. The `error`, `no_input`, + * `no_match` and `start_of_input` variants carry no `params`; `digit` and + * `speech` carry a `params` payload. + */ +@discriminator("type") +model CallCollectResult { + type: string; +} + +model CallCollectResultError extends CallCollectResult { + type: "error"; +} + +model CallCollectResultNoInput extends CallCollectResult { + type: "no_input"; +} + +model CallCollectResultNoMatch extends CallCollectResult { + type: "no_match"; +} + +/** Fired only when using the `calling.collect` API (start-of-speech marker). */ +model CallCollectResultStartOfInput extends CallCollectResult { + type: "start_of_input"; +} + +model CallCollectResultDigit extends CallCollectResult { + type: "digit"; + params: { + /** The collected DTMF digits. */ + digits: string; + + /** The terminator digit that ended collection, if any. */ + terminator?: string; + }; +} + +model CallCollectResultSpeech extends CallCollectResult { + type: "speech"; + params: { + /** The recognized utterance. */ + text: string; + + /** Recognition confidence (e.g. `83.2`). */ + confidence?: float64; + }; +} + +model CallCollectParams { + ...CallAddress; + + /** Identifier of the active collect (from `calling.collect`). */ + control_id: string; + + /** The collect state. `error` means the detector ended with an error. */ + state: CallCollectState; + + /** The collect result. */ + result?: CallCollectResult; + + /** + * Meaningful when `partial_results`/`continuous` was set: `true` once utterance + * detection has completed. With `continuous: true` the collector restarts for + * the next utterance. + */ + final?: boolean; +} + +/** A call's collect result. */ +@event("calling.call.collect") +model CallCollectEvent { + ...CallCollectParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.record +// ───────────────────────────────────────────────────────────────────────────── + +/** State of an active recording (`calling.call.record`). */ +union CallRecordState { + "recording", + "paused", + "finished", + "no_input", +} + +/** Audio direction reported on a recording event. */ +union RecordEventDirection { + /** What the call party hears. */ + "listen", + + /** What the call party says. */ + "speak", + + /** Both directions. */ + "both", +} + +/** + * The reported `record.audio` subobject — a slimmer echo of the recording + * configuration than the request-side `RecordAudio`. + */ +model RecordEventAudio { + /** Output file format (e.g. `mp3`, `wav`). */ + format?: string; + + /** Whether the recording was captured in stereo. */ + stereo?: boolean; + + /** Which audio direction(s) were captured. */ + direction?: RecordEventDirection; +} + +/** + * Reported recording spec. Keyed by the subobject name (`audio`) rather than a + * `type` discriminator; only the `audio` variant is documented. (Prose also + * references a `record.params` subobject for non-audio types — shape + * undocumented.) + */ +model RecordEventSpec { + /** Audio-recording configuration (present when recording audio). */ + audio?: RecordEventAudio; +} + +model CallRecordParams { + ...CallAddress; + + /** Identifier of the active recording (from `calling.record`). */ + control_id: string; + + /** The recording state. */ + state: CallRecordState; + + /** Location of the recording — not accessible until `finished`. */ + url?: url; + + /** Length of the recording in seconds — set when `finished`. */ + duration?: float64; + + /** Size of the recording in bytes — set when `finished`. */ + size?: int32; + + /** The recording configuration. */ + record?: RecordEventSpec; +} + +/** A change in a call recording's state. */ +@event("calling.call.record") +model CallRecordEvent { + ...CallRecordParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.detect +// ───────────────────────────────────────────────────────────────────────────── + +/** Fax-detector event values. */ +union CallDetectFaxEvent { + /** Called-station fax tone. */ + "CED", + + /** Calling-station fax tone. */ + "CNG", +} + +/** Answering-machine-detector event values. */ +union CallDetectMachineEvent { + /** Machine detected. */ + "MACHINE", + + /** Human detected — a final event. */ + "HUMAN", + + /** Not sure. */ + "UNKNOWN", + + /** + * Machine ready for voicemail delivery — final if `detect_interruptions=false` + * or `beep=true`. + */ + "READY", + + /** + * Machine voicemail restarted, interrupting delivery. Only fired if + * `detect_interruptions=true`. + */ + "NOT_READY", +} + +/** + * A detector's event payload. Discriminated on `type` (`fax|machine|digit`). + * Every variant's `params.event` may also surface the generic `finished` (on + * completion) or `error` (if unable to start) values noted in the source prose. + */ +@discriminator("type") +model CallDetectResult { + type: string; +} + +model CallDetectFax extends CallDetectResult { + type: "fax"; + params: { + /** The fax-detector event. */ + event: CallDetectFaxEvent; + }; +} + +model CallDetectMachine extends CallDetectResult { + type: "machine"; + params: { + /** The machine-detector event. */ + event: CallDetectMachineEvent; + + /** Whether a beep has been detected. */ + beep?: boolean; + }; +} + +model CallDetectDigit extends CallDetectResult { + type: "digit"; + params: { + /** The detected DTMF digit (one of `0-9`, `#`, `*`). */ + event: string; + }; +} + +model CallDetectParams { + ...CallAddress; + + /** Identifier of the active detector (from `calling.detect`). */ + control_id: string; + + /** The detector-specific information. */ + detect: CallDetectResult; +} + +/** A call-detection event from an active detector. */ +@event("calling.call.detect") +model CallDetectEvent { + ...CallDetectParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.denoise +// ───────────────────────────────────────────────────────────────────────────── + +model CallDenoiseParams { + ...CallAddress; + + /** Whether noise reduction is enabled (`true`) or disabled. */ + denoised: boolean; +} + +/** A call-denoiser state event. (Carries no `control_id`.) */ +@event("calling.call.denoise") +model CallDenoiseEvent { + ...CallDenoiseParams; +} diff --git a/specs/relay/calling/events/media-2.tsp b/specs/relay/calling/events/media-2.tsp new file mode 100644 index 0000000000..23456723bf --- /dev/null +++ b/specs/relay/calling/events/media-2.tsp @@ -0,0 +1,484 @@ +import "@signalwire/typespec-asyncapi"; +import "../common.tsp"; + +using SignalWire.AsyncAPI; + +namespace Relay.Calling; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.fax +// ───────────────────────────────────────────────────────────────────────────── + +/** Direction of a fax page / transmission. */ +union FaxDirection { + "send", + "receive", +} + +/** A fax event payload, discriminated on `type`. */ +@discriminator("type") +model CallFax { + type: string; +} + +/** A single page was sent or received. */ +model FaxPage extends CallFax { + type: "page"; + params: { + /** Whether the page was sent or received. */ + direction: FaxDirection; + + /** Page number. */ + number: int32; + }; +} + +/** The fax transmission finished. */ +model FaxFinished extends CallFax { + type: "finished"; + params: { + /** Whether the fax was sent or received. */ + direction: FaxDirection; + + /** Local fax identity (e.g. an E.164 number). */ + identity?: string; + + /** Remote fax identity (e.g. an E.164 number). */ + remote_identity?: string; + + /** Document URL location. */ + document?: url; + + /** Number of pages sent / received. */ + pages?: int32; + + /** Whether the fax completed successfully. */ + success?: boolean; + + /** Fax result code (e.g. `1231`). */ + result?: int32; + + /** Human-readable fax result text. */ + result_text?: string; + }; +} + +/** + * The fax transmission errored. The wire shape for this variant is not + * documented in the protocol reference; it is modeled loosely and likely + * shares the `finished` result/result_text fields. + */ +model FaxError extends CallFax { + type: "error"; + + /** Error-variant params (shape undocumented). */ + params?: Record; +} + +model CallFaxParams { + ...CallAddress; + + /** The ID used to control the active fax. */ + control_id: string; + + /** Fax event information. */ + fax: CallFax; +} + +/** A fax event (page / finished / error). */ +@event("calling.call.fax") +model CallFaxEvent { + ...CallFaxParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.tap +// ───────────────────────────────────────────────────────────────────────────── + +/** State of an active tap. */ +union TapState { + "tapping", + "finished", +} + +/** Direction of tapped media. (Only `speak` is shown in the reference.) */ +union CallTapDirection { + "speak", + "listen", + "both", +} + +/** The tapped media, discriminated on `type`. (Only `audio` is documented.) */ +@discriminator("type") +model TapMedia { + type: string; +} + +/** Audio tap. */ +model CallTapAudio extends TapMedia { + type: "audio"; + params: { + /** Which side(s) of the media are tapped. */ + direction: CallTapDirection; + }; +} + +/** The device receiving the tapped media, discriminated on `type`. (Only `rtp` is documented.) */ +@discriminator("type") +model CallTapDevice { + type: string; +} + +/** RTP tap sink. */ +model CallTapRtpDevice extends CallTapDevice { + type: "rtp"; + params: { + /** Destination address. */ + addr: string; + + /** Destination port. */ + port: int32; + + /** Negotiated codec. */ + codec: string; + + /** Packetization time, in milliseconds. */ + ptime: int32; + }; +} + +model CallTapParams { + ...CallAddress; + + /** The ID used to control the active tap. */ + control_id: string; + + /** The tap state. */ + state: TapState; + + /** The tapped media info. */ + tap: TapMedia; + + /** The device receiving the tapped media. */ + device: CallTapDevice; +} + +/** A call-tap state event. */ +@event("calling.call.tap") +model CallTapEvent { + ...CallTapParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.stream +// ───────────────────────────────────────────────────────────────────────────── + +/** State of an active stream. */ +union StreamState { + "streaming", + "finished", +} + +model CallStreamParams { + ...CallAddress; + + /** The ID used to control the active stream. */ + control_id: string; + + /** The stream state. */ + state: StreamState; + + /** The WebSocket URL being streamed to. */ + url: url; + + /** The friendly name of the stream (if provided). */ + name?: string; +} + +/** A call-stream state change. */ +@event("calling.call.stream") +model CallStreamEvent { + ...CallStreamParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.transcribe +// ───────────────────────────────────────────────────────────────────────────── + +/** State of an active transcription. */ +union TranscribeState { + "transcribing", + "finished", +} + +model CallTranscribeParams { + ...CallAddress; + + /** The ID used to control the active transcription. */ + control_id: string; + + /** The transcription state. */ + state: TranscribeState; + + /** Location of the recording (e.g. `recordings/.wav`). */ + url: string; + + /** The UUID of the shadow recording. */ + recording_id: string; + + /** The callback URL, if one was provided. */ + status_url?: string; + + /** Length of the recording in seconds. Set only on `finished`. */ + duration?: float64; + + /** Size of the recording in bytes. Set only on `finished`. */ + size?: int32; + + /** Unix timestamp when recording started. Set only on `finished`. */ + start_time?: float64; + + /** Unix timestamp when recording ended. Set only on `finished`. */ + end_time?: float64; +} + +/** A call-transcription state. */ +@event("calling.call.transcribe") +model CallTranscribeEvent { + ...CallTranscribeParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.hold +// ───────────────────────────────────────────────────────────────────────────── + +/** A call's hold state. */ +union HoldState { + "hold", + "unhold", +} + +model CallHoldParams { + ...CallAddress; + + /** The hold state. */ + state: HoldState; +} + +/** A call hold-state event. (No `control_id`.) */ +@event("calling.call.hold") +model CallHoldEvent { + ...CallHoldParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.send_digits +// ───────────────────────────────────────────────────────────────────────────── + +model CallSendDigitsParams { + ...CallAddress; + + /** The ID used to control the active send_digits operation. */ + control_id: string; + + /** The send_digits state. (Only `finished` is documented.) */ + state: "finished"; +} + +/** A send-digits completion event. */ +@event("calling.call.send_digits") +model CallSendDigitsEvent { + ...CallSendDigitsParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.conference +// ───────────────────────────────────────────────────────────────────────────── + +/** + * The conference event status — a secondary discriminator multiplexing all + * `calling.conference` payloads. + */ +union ConferenceStatus { + "conference-end", + "conference-start", + "participant-leave", + "participant-join", + "participant-mute", + "participant-unmute", + "participant-hold", + "participant-unhold", + "participant-modify", + "participant-speech-start", + "participant-speech-stop", + "announcement-end", + "announcement-fail", +} + +/** Final call status of a participant who left (`participant-leave`). */ +union ConferenceParticipantCallStatus { + "no-answer", + "busy", + "in-progress", + "failed", + "canceled", + "completed", +} + +/** Why a participant left (`participant-leave`). */ +union ConferenceReasonParticipantLeft { + "conference_ended_via_api", + "moderator_ended_conference", + "participant_updated_via_api", + "participant_hung_up", + "participant_add_failed", +} + +/** Why a conference ended (`conference-end`). */ +union ConferenceReasonEnded { + "conference-ended-via-api", + "last-participant-kicked", + "last-participant-left", + "participant-with-end-conference-on-exit-kicked", + "participant-with-end-conference-on-exit-left", +} + +/** + * A multiplexed conference event. `status` selects which conditional fields are + * present; all conditional fields are optional here since they depend on + * `status`. + */ +model ConferenceParams { + /** The UUID of the node this conference is on. */ + node_id: string; + + /** The UUID of the conference. */ + conference_id: string; + + /** The name of the conference. */ + name?: string; + + /** The conference event status (secondary discriminator). */ + status: ConferenceStatus; + + /** Participant call id. Set on participant statuses. */ + call_id?: string; + + /** Whether the participant is muted. Set on participant statuses. */ + muted?: boolean; + + /** Whether the participant is on hold. Set on participant statuses. */ + hold?: boolean; + + /** Whether the participant is coaching. Set on participant statuses. */ + coaching?: boolean; + + /** Whether the conference ends when this participant exits. Set on participant statuses. */ + end_on_exit?: boolean; + + /** Whether the conference starts when this participant enters. Set on participant statuses. */ + start_on_enter?: boolean; + + /** The participant's final call status. Set on `participant-leave`. */ + participant_call_status?: ConferenceParticipantCallStatus; + + /** Why the participant left. Set on `participant-leave`. */ + reason_participant_left?: ConferenceReasonParticipantLeft; + + /** UUID of the call that ended the conference. Set on `conference-end`. */ + call_ending_conference?: string; + + /** Why the conference ended. Set on `conference-end`. */ + reason_ended?: ConferenceReasonEnded; + + /** URL of the conference recording. Set on `conference-end`. */ + recording_url?: url; + + /** Recording duration in seconds. Set on `conference-end`. */ + recording_duration?: int32; + + /** Recording file size in bytes. Set on `conference-end`. */ + recording_file_size?: int32; + + /** Announcement URL. Set on announcement statuses. */ + announce_url?: url; +} + +/** A conference lifecycle / participant event. */ +@event("calling.conference") +model ConferenceEvent { + ...ConferenceParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.echo +// ───────────────────────────────────────────────────────────────────────────── + +/** A call's echo state. */ +union EchoState { + "echoing", + "finished", +} + +model CallEchoParams { + ...CallAddress; + + /** The echo state. */ + state: EchoState; +} + +/** A call echo state event. (No `control_id`.) */ +@event("calling.call.echo") +model CallEchoEvent { + ...CallEchoParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.call.pay +// ───────────────────────────────────────────────────────────────────────────── + +/** + * A call payment state. The example shows `processing|finished|error`; the full + * set is not confirmed in the reference. + */ +union PayState { + "processing", + "finished", + "error", +} + +model CallPayParams { + ...CallAddress; + + /** The ID used to control the active pay. */ + control_id: string; + + /** The payment state. */ + state: PayState; +} + +/** A call payment state event. */ +@event("calling.call.pay") +model CallPayEvent { + ...CallPayParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.error +// ───────────────────────────────────────────────────────────────────────────── + +model CallErrorParams { + ...CallAddress; + + /** Error code (string, e.g. `"500"`). */ + code: string; + + /** Error description. */ + message: string; +} + +/** A server-pushed calling error associated with a call. */ +@event("calling.error") +model CallErrorEvent { + ...CallErrorParams; +} diff --git a/specs/relay/calling/main.tsp b/specs/relay/calling/main.tsp index 2ebf8ea782..28f930946e 100644 --- a/specs/relay/calling/main.tsp +++ b/specs/relay/calling/main.tsp @@ -3,7 +3,16 @@ import "@signalwire/typespec-asyncapi"; // Shared models, then one file per method family / event group. import "./common.tsp"; import "./methods/core-control.tsp"; +import "./methods/collect-input.tsp"; +import "./methods/queue-record-refer.tsp"; +import "./methods/pay-play.tsp"; +import "./methods/detect-fax-tap-stream.tsp"; +import "./methods/transfer-conf-hold-digits.tsp"; +import "./methods/transcribe-misc.tsp"; +import "./methods/ai.tsp"; import "./events/core.tsp"; +import "./events/media-1.tsp"; +import "./events/media-2.tsp"; using SignalWire.AsyncAPI; diff --git a/specs/relay/calling/methods/ai.tsp b/specs/relay/calling/methods/ai.tsp new file mode 100644 index 0000000000..e6db142135 --- /dev/null +++ b/specs/relay/calling/methods/ai.tsp @@ -0,0 +1,457 @@ +import "@signalwire/typespec-asyncapi"; +import "../common.tsp"; + +using SignalWire.AsyncAPI; + +namespace Relay.Calling; + +// ═════════════════════════════════════════════════════════════════════════════ +// Shared AI sub-shapes (calling.ai / calling.amazon_bedrock) +// +// The Relay `calling.ai` protocol surface is a simplified projection of the SWML +// `ai` verb. Only the documented fields are modeled below; genuinely open-ended +// surfaces (the AI behavior `params` object, SWAIG function extras) are +// loose-modeled as `Record`/`unknown` with openQuestions. +// ═════════════════════════════════════════════════════════════════════════════ + +/** + * Inline prompt configuration for the AI agent. Overrides a pre-configured + * `agent` UUID when both are present. + */ +model AiPrompt { + /** Instructions sent to the agent (plain text or SSML). */ + text?: string; + + /** Nucleus-sampling cutoff (0.0–1.0). Alternative to `temperature`. */ + @minValue(0.0) + @maxValue(1.0) + top_p?: float64; + + /** Randomness of generation (0.0–1.5). Lower is more deterministic. */ + @minValue(0.0) + @maxValue(1.5) + temperature?: float64; + + /** End-of-utterance speech-detect threshold (0.0–1.0). */ + @minValue(0.0) + @maxValue(1.0) + confidence?: float64; + + /** Confidence threshold for the user barging in over the agent (0.0–1.0). */ + @minValue(0.0) + @maxValue(1.0) + barge_confidence?: float64; + + /** Aversion to new topics (-2.0–2.0). Positive values encourage new topics. */ + @minValue(-2.0) + @maxValue(2.0) + presence_penalty?: float64; + + /** Aversion to repetition (-2.0–2.0). Positive values reduce verbatim repeats. */ + @minValue(-2.0) + @maxValue(2.0) + frequency_penalty?: float64; + + /** LLM model identifier to use for this prompt. */ + `model`?: string; +} + +/** + * Post-conversation prompt configuration. Same shape as `AiPrompt` minus + * `confidence` (which has no meaning after the conversation has ended). + */ +model AiPostPrompt { + /** Instructions sent to the agent after the conversation ends. */ + text?: string; + + /** Nucleus-sampling cutoff (0.0–1.0). Alternative to `temperature`. */ + @minValue(0.0) + @maxValue(1.0) + top_p?: float64; + + /** Randomness of generation (0.0–1.5). Lower is more deterministic. */ + @minValue(0.0) + @maxValue(1.5) + temperature?: float64; + + /** Confidence threshold for the user barging in over the agent (0.0–1.0). */ + @minValue(0.0) + @maxValue(1.0) + barge_confidence?: float64; + + /** Aversion to new topics (-2.0–2.0). Positive values encourage new topics. */ + @minValue(-2.0) + @maxValue(2.0) + presence_penalty?: float64; + + /** Aversion to repetition (-2.0–2.0). Positive values reduce verbatim repeats. */ + @minValue(-2.0) + @maxValue(2.0) + frequency_penalty?: float64; + + /** LLM model identifier to use for this post-prompt. */ + `model`?: string; +} + +/** + * A global pronunciation rule. Replaces a matched expression with a phonetic + * spelling so the TTS engine pronounces it correctly. + */ +model AiPronounce { + /** The expression to replace. */ + replace: string; + + /** The phonetic spelling to substitute. */ + with: string; + + /** Match case-insensitively. Default `true`. */ + ignore_case?: boolean = true; +} + +/** + * A context hint biasing speech recognition. May be a bare string, or an object + * that rewrites a matched phrase before it reaches the model. + */ +model AiHint { + /** The hint phrase to match exactly. */ + hint: string; + + /** A regular expression the hint must match before replacement. */ + pattern?: string; + + /** Text to replace the matched portion of the hint with. */ + replace?: string; + + /** Match case-insensitively. Default `false`. */ + ignore_case?: boolean = false; +} + +/** + * A supported language configuration for the dialogue. (Modeled per the SWML + * `ai` verb; additional TTS engine-specific knobs may be accepted.) + */ +model AiLanguage { + /** Human-readable language name, used in the system prompt (e.g. `French`). */ + name: string; + + /** ASR language code (e.g. `fr-FR`). */ + code: string; + + /** Voice in `.` form (e.g. `gcloud.fr-FR-Neural2-B`). */ + voice: string; + + /** TTS model for the selected engine. */ + `model`?: string; +} + +/** Default settings applied to all SWAIG functions unless overridden. */ +model AiSwaigDefaults { + /** + * Default webhook URL for function status callbacks. Basic auth may be inlined + * as `username:password@url`. + */ + web_hook_url?: string; +} + +/** + * A single SWAIG function definition. Only the historically documented fields + * (`function`, `purpose`, `argument`) are typed; the live SWAIG schema accepts + * many more (`description`, `parameters`, `data_map`, `web_hook_url`, fillers, + * …) — see openQuestions. + */ +model AiSwaigFunction { + /** Unique function name (or a reserved SignalWire hook name). */ + function: string; + + /** Description of when/why the agent should call this function. */ + purpose?: string; + + /** Description of the input the function expects. */ + argument?: unknown; + + /** Per-function webhook URL override. */ + web_hook_url?: string; +} + +/** Remote SWAIG function include — pull function signatures from a URL. */ +model AiSwaigIncludes { + /** URL hosting the remote functions. Basic auth may be inlined. */ + url: string; + + /** Names of the remote functions to include. */ + functions: string[]; +} + +/** SWAIG (SignalWire AI Gateway) function configuration. */ +model AiSwaig { + /** Default settings inherited by all functions. */ + defaults?: AiSwaigDefaults; + + /** User-defined functions the agent may call. */ + functions?: AiSwaigFunction[]; + + /** Remote function-signature includes. */ + includes?: AiSwaigIncludes[]; + + /** + * Names of prebuilt native functions the agent may call (e.g. `check_time`, + * `wait_seconds`). Modeled loosely — the available set is documented with the + * SWML `ai` verb. + */ + native_functions?: string[]; +} + +// ═════════════════════════════════════════════════════════════════════════════ +// calling.ai +// ═════════════════════════════════════════════════════════════════════════════ + +model AiParams { + ...CallAddress; + + /** Identifier used to control (e.g. stop) this AI session. */ + control_id: string; + + /** + * Pre-configured agent UUID. If an inline `prompt` is also present, the inline + * configuration takes precedence. + */ + agent?: string; + + /** Inline prompt configuration for the AI agent. */ + prompt?: AiPrompt; + + /** Post-conversation prompt configuration. */ + post_prompt?: AiPostPrompt; + + /** URL to receive post-prompt status callbacks. */ + post_prompt_url?: url; + + /** Basic-auth username for `post_prompt_url`. */ + post_prompt_auth_user?: string; + + /** Basic-auth password for `post_prompt_url`. */ + post_prompt_auth_password?: string; + + /** Global data accessible to all SWAIG functions. */ + global_data?: Record; + + /** Global pronunciation rules. */ + pronounce?: AiPronounce[]; + + /** Context hints biasing speech recognition. */ + hints?: AiHint[]; + + /** Supported language configurations. */ + languages?: AiLanguage[]; + + /** SWAIG function configuration. */ + SWAIG?: AiSwaig; + + /** + * Open-ended AI behavior parameters (ASR, TTS, turn detection, barge-in, LLM + * config, video, …). Loose-modeled — the full enumeration lives with the SWML + * `ai` verb. Example fields: `end_of_speech_timeout`, `attention_timeout` (ms). + */ + params?: Record; +} + +model AiResult { + ...RelayResult; + + /** Echo of the call id. */ + call_id?: string; + + /** Echo of the control id for this AI session. */ + control_id?: string; +} + +/** + * Start an AI agent on the call. Blocking — acquires a block (preventing other + * blocking operations like `connect` or `play_and_collect`) and runs the AI + * session; the block is released when the session ends. Events continue to flow + * while the session is active. + */ +@rpcMethod("calling.ai") +@summary("Start an AI agent on the call") +op ai(...AiParams): AiResult; + +// ═════════════════════════════════════════════════════════════════════════════ +// calling.ai.stop +// ═════════════════════════════════════════════════════════════════════════════ + +model AiStopParams { + ...CallAddress; + + /** The `control_id` assigned in `calling.ai`. */ + control_id: string; +} + +model AiStopResult { + ...RelayResult; + + /** Echo of the call id. */ + call_id?: string; + + /** Echo of the control id. */ + control_id?: string; +} + +/** (async-safe) Stop an active AI agent session on the call. */ +@rpcMethod("calling.ai.stop") +@summary("Stop an active AI agent session") +op aiStop(...AiStopParams): AiStopResult; + +// ═════════════════════════════════════════════════════════════════════════════ +// calling.amazon_bedrock +// +// Parallels calling.ai but with a smaller surface: no control_id, agent, +// post_prompt_auth_*, pronounce, hints, or languages. `prompt` is a plain string +// (the system prompt), unlike calling.ai's object form. +// ═════════════════════════════════════════════════════════════════════════════ + +model AmazonBedrockParams { + ...CallAddress; + + /** System prompt for the Bedrock agent. */ + prompt?: string; + + /** SWAIG function configuration. */ + SWAIG?: AiSwaig; + + /** + * Open-ended AI behavior parameters. Loose-modeled — see `calling.ai` `params`. + */ + params?: Record; + + /** Global data accessible to all SWAIG functions. */ + global_data?: Record; + + /** Post-conversation prompt configuration. */ + post_prompt?: AiPostPrompt; + + /** URL to receive post-prompt results. */ + post_prompt_url?: url; +} + +model AmazonBedrockResult { + ...RelayResult; +} + +/** Connect to an Amazon Bedrock AI agent. */ +@rpcMethod("calling.amazon_bedrock") +@summary("Connect to an Amazon Bedrock AI agent") +op amazonBedrock(...AmazonBedrockParams): AmazonBedrockResult; + +// ═════════════════════════════════════════════════════════════════════════════ +// calling.ai_message +// ═════════════════════════════════════════════════════════════════════════════ + +/** Role of an injected AI-message sender. */ +union AiMessageRole { + "system", + "user", + "assistant", +} + +/** + * Conversation-reset configuration. Each field clears or replaces part of the + * session context. + */ +model AiMessageReset { + /** Clear the entire conversation history. */ + full_reset?: boolean; + + /** Replace (or clear) the user prompt context. */ + user_prompt?: string; + + /** Replace (or clear) the system prompt context. */ + system_prompt?: string; +} + +model AiMessageParams { + ...CallAddress; + + /** Message text to inject into the session. */ + message_text?: string; + + /** Role of the message sender. */ + role?: AiMessageRole; + + /** Conversation-reset configuration. */ + reset?: AiMessageReset; + + /** Updated global data for SWAIG functions. */ + global_data?: Record; +} + +model AiMessageResult { + ...RelayResult; +} + +/** (async-safe) Send (inject) a message into an active AI agent session. */ +@rpcMethod("calling.ai_message") +@summary("Send a message to an active AI agent session") +op aiMessage(...AiMessageParams): AiMessageResult; + +// ═════════════════════════════════════════════════════════════════════════════ +// calling.ai_hold +// ═════════════════════════════════════════════════════════════════════════════ + +model AiHoldParams { + ...CallAddress; + + /** Hold timeout. Sent as a string in the example (e.g. `"60"`); unit is seconds. */ + timeout?: string; + + /** Hold prompt / music (plain string). */ + prompt?: string; +} + +model AiHoldResult { + ...RelayResult; +} + +/** Put an AI agent session on hold. */ +@rpcMethod("calling.ai_hold") +@summary("Put an AI agent session on hold") +op aiHold(...AiHoldParams): AiHoldResult; + +// ═════════════════════════════════════════════════════════════════════════════ +// calling.ai_unhold +// ═════════════════════════════════════════════════════════════════════════════ + +model AiUnholdParams { + ...CallAddress; + + /** Resume prompt (plain string). */ + prompt?: string; +} + +model AiUnholdResult { + ...RelayResult; +} + +/** Resume an AI agent session from hold. */ +@rpcMethod("calling.ai_unhold") +@summary("Resume an AI agent session from hold") +op aiUnhold(...AiUnholdParams): AiUnholdResult; + +// ═════════════════════════════════════════════════════════════════════════════ +// calling.user_event +// ═════════════════════════════════════════════════════════════════════════════ + +model UserEventParams { + ...CallAddress; + + /** The custom event name. */ + event?: string; +} + +model UserEventResult { + ...RelayResult; +} + +/** Send a custom user-defined event. */ +@rpcMethod("calling.user_event") +@summary("Send a custom user-defined event") +op userEvent(...UserEventParams): UserEventResult; diff --git a/specs/relay/calling/methods/collect-input.tsp b/specs/relay/calling/methods/collect-input.tsp new file mode 100644 index 0000000000..7de1f9e919 --- /dev/null +++ b/specs/relay/calling/methods/collect-input.tsp @@ -0,0 +1,272 @@ +import "@signalwire/typespec-asyncapi"; +import "../common.tsp"; + +using SignalWire.AsyncAPI; + +namespace Relay.Calling; + +// ═════════════════════════════════════════════════════════════════════════════ +// Shared collect sub-objects (digits / speech) +// +// The same `digits` and `speech` shapes are used by `calling.collect` (top-level) +// and `calling.play_and_collect` (nested under `collect`). At least one of the +// two must be present. +// ═════════════════════════════════════════════════════════════════════════════ + +/** + * Speech-recognition engine to force for a collect. Must support the requested + * `language`. Unset means SignalWire picks the engine. + */ +union CollectSpeechEngine { + "Deepgram", + "Google", +} + +/** DTMF-digit collection settings. */ +model CollectDigits { + /** Maximum number of digits to collect. Positive integer. */ + @minValue(1) + max: int32; + + /** Digits that terminate collection (e.g. `"#*"`). Default not set. */ + terminators?: string; + + /** + * Maximum seconds to wait for the next digit after a digit is received. + * Default `5.0`. + */ + @minValueExclusive(0) + digit_timeout?: float64 = 5.0; +} + +/** Speech-recognition collection settings. */ +model CollectSpeech { + /** Silence (seconds) to wait for before declaring end of speech. Default `1`. */ + @minValueExclusive(0) + end_silence_timeout?: float64 = 1.0; + + /** Maximum seconds to collect speech. Default `60`. */ + @minValueExclusive(0) + speech_timeout?: float64 = 60.0; + + /** Language to detect. Default `en-US`. */ + language?: string = "en-US"; + + /** Expected phrases to bias detection toward. Default not set. */ + hints?: string[]; + + /** Force a specific speech-recognition engine. Default unset (auto-selected). */ + engine?: CollectSpeechEngine; +} + +// ═════════════════════════════════════════════════════════════════════════════ +// Play media union (calling.play_and_collect `play[]`) +// +// Discriminated on `type`. Mirrors the Ringback shape but the `tts` variant +// additionally accepts a `voice` field. +// ═════════════════════════════════════════════════════════════════════════════ + +// The `PlayMedia` union (audio|tts|silence|ringtone) is defined in common.tsp — +// shared with `calling.play`. + +// ───────────────────────────────────────────────────────────────────────────── +// calling.collect +// ───────────────────────────────────────────────────────────────────────────── + +model CollectParams { + ...CallAddress; + + /** Identifier added to the created collect events. */ + control_id: string; + + /** + * Seconds to wait for initial input. Used only when `start_input_timers: true`. + * Default `4.0`. + */ + @minValueExclusive(0) + initial_timeout?: float64 = 4.0; + + /** Digit-collection settings. Required if `speech` is not set. */ + digits?: CollectDigits; + + /** Speech-collection settings. Required if `digits` is not set. */ + speech?: CollectSpeech; + + /** If true, partial-result events are fired. Default `false`. */ + partial_results?: boolean = false; + + /** + * If true, utterances and digits are detected continuously until the collect is + * stopped. Default `false`. + */ + continuous?: boolean = false; + + /** If true, the `start_of_input` event is fired when input is detected. Default `false`. */ + send_start_of_input?: boolean = false; + + /** If true, the `initial_timeout` timer is started immediately. Default `false`. */ + start_input_timers?: boolean = false; + + /** HTTP(s) URL to POST collect events to. */ + status_url?: url; +} + +model CollectResult { + ...RelayResult; + + /** Echoes the `control_id` from the params. */ + control_id?: string; +} + +/** Collect DTMF digits and/or speech from an active call. */ +@rpcMethod("calling.collect") +@summary("Collect digits and/or speech from a call") +op collect(...CollectParams): CollectResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.collect.stop +// ───────────────────────────────────────────────────────────────────────────── + +model CollectStopParams { + ...CallAddress; + + /** The `control_id` assigned in `calling.collect`. */ + control_id: string; +} + +model CollectStopResult { + ...RelayResult; +} + +/** Stop an active collect. */ +@rpcMethod("calling.collect.stop") +@summary("Stop an active collect") +op collectStop(...CollectStopParams): CollectStopResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.collect.start_input_timers +// ───────────────────────────────────────────────────────────────────────────── + +model CollectStartInputTimersParams { + ...CallAddress; + + /** The `control_id` assigned in `calling.collect`. */ + control_id: string; +} + +model CollectStartInputTimersResult { + ...RelayResult; +} + +/** + * Start the `initial_timeout` timer on an active collect — the companion call when + * a collect was started with `start_input_timers: false`. + */ +@rpcMethod("calling.collect.start_input_timers") +@summary("Start the initial-timeout timer on a collect") +op collectStartInputTimers(...CollectStartInputTimersParams): CollectStartInputTimersResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.play_and_collect (NOT async-safe) +// ───────────────────────────────────────────────────────────────────────────── + +/** The `collect` block of `calling.play_and_collect`. At least one of `digits`/`speech`. */ +model PlayAndCollectCollect { + /** Seconds to wait for initial input. Default `4.0`. */ + @minValueExclusive(0) + initial_timeout?: float64 = 4.0; + + /** Digit-collection settings. Required if `speech` is not set. */ + digits?: CollectDigits; + + /** Speech-collection settings. Required if `digits` is not set. */ + speech?: CollectSpeech; +} + +model PlayAndCollectParams { + ...CallAddress; + + /** Identifier added to the created play-and-collect events. */ + control_id: string; + + /** + * Playback volume in dB, from `-40` (muted) to `+40`, where `0` is the original + * audio. Follows the standard amplitude voltage gain factor: `10 ^ (value / 20)`. + */ + @minValue(-40) + @maxValue(40) + volume?: float64; + + /** Media elements to play. */ + play: PlayMedia[]; + + /** Collection settings applied while playing. */ + collect: PlayAndCollectCollect; + + /** HTTP(s) URL to POST play-and-collect events to. */ + status_url?: url; +} + +model PlayAndCollectResult { + ...RelayResult; + + /** Echoes the `control_id` from the params. */ + control_id?: string; +} + +/** + * Play media to a call and collect input. NOT async-safe. Only one execution at a + * time per call. + */ +@rpcMethod("calling.play_and_collect") +@summary("Play media and collect input") +op playAndCollect(...PlayAndCollectParams): PlayAndCollectResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.play_and_collect.stop +// ───────────────────────────────────────────────────────────────────────────── + +model PlayAndCollectStopParams { + ...CallAddress; + + /** The `control_id` assigned in `calling.play_and_collect`. */ + control_id: string; +} + +model PlayAndCollectStopResult { + ...RelayResult; +} + +/** Stop an active play-and-collect. */ +@rpcMethod("calling.play_and_collect.stop") +@summary("Stop an active play-and-collect") +op playAndCollectStop(...PlayAndCollectStopParams): PlayAndCollectStopResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.play_and_collect.volume +// ───────────────────────────────────────────────────────────────────────────── + +model PlayAndCollectVolumeParams { + ...CallAddress; + + /** The `control_id` assigned in `calling.play_and_collect`. */ + control_id: string; + + /** + * New playback volume in dB, from `-40` (muted) to `+40`, where `0` is the + * original audio. Follows the standard amplitude voltage gain factor: + * `10 ^ (value / 20)`. + */ + @minValue(-40) + @maxValue(40) + volume: float64; +} + +model PlayAndCollectVolumeResult { + ...RelayResult; +} + +/** Alter the volume of an active play-and-collect. */ +@rpcMethod("calling.play_and_collect.volume") +@summary("Change play-and-collect volume") +op playAndCollectVolume(...PlayAndCollectVolumeParams): PlayAndCollectVolumeResult; diff --git a/specs/relay/calling/methods/detect-fax-tap-stream.tsp b/specs/relay/calling/methods/detect-fax-tap-stream.tsp new file mode 100644 index 0000000000..44293dc392 --- /dev/null +++ b/specs/relay/calling/methods/detect-fax-tap-stream.tsp @@ -0,0 +1,548 @@ +import "@signalwire/typespec-asyncapi"; +import "../common.tsp"; + +using SignalWire.AsyncAPI; + +namespace Relay.Calling; + +// ═════════════════════════════════════════════════════════════════════════════ +// Detector subobject (calling.detect) +// +// `detect` carries a variant with a `type` and a per-type `params` body. +// Discriminated on `type` (machine|fax|digit), exactly like Device/Ringback. +// ═════════════════════════════════════════════════════════════════════════════ + +/** Tone the fax detector listens for (remote side only). */ +union DetectFaxTone { + "CED", + "CNG", +} + +/** `machine` detector params (answering-machine / voicemail detection). */ +model DetectMachineParams { + /** How long to wait (sec > 0) for initial voice before giving up. Default `4.5`. */ + @minValueExclusive(0) + initial_timeout?: float64; + + /** How long to wait (sec > 0) for voice to finish. Default `1.0`. */ + @minValueExclusive(0) + end_silence_timeout?: float64; + + /** + * How long to wait (sec > 0) for voice to finish before firing the READY + * event. Default is `end_silence_timeout`. + */ + @minValueExclusive(0) + machine_ready_timeout?: float64; + + /** + * How much voice (sec > 0) to decide MACHINE. Default `1.25`. (Source says + * "sec > 0" but the description says "in ms" — units to confirm.) + */ + @minValueExclusive(0) + machine_voice_threshold?: float64; + + /** How many words (count > 0) to count to decide MACHINE. Default `6`. */ + @minValueExclusive(0) + machine_words_threshold?: int32; + + /** + * If true, a NOT_READY event is fired if VAD detects speech after READY. This + * lets the application restart message delivery to the answering machine. + * Default `false`. + */ + detect_interruptions?: boolean = false; + + /** + * If false, stop detection on the machine event and don't wait on the beep / + * end of the voicemail greeting. Default `true`. + */ + detect_message_end?: boolean = true; +} + +/** `fax` detector params. */ +model DetectFaxParams { + /** Tone to detect (remote side only). Default `CED`. */ + tone?: DetectFaxTone; +} + +/** `digit` detector params. */ +model DetectDigitParams { + /** Digits to detect. Default `0123456789#*`. */ + digits?: string; +} + +/** Detector to start. Discriminated on `type` (`machine`|`fax`|`digit`). */ +@discriminator("type") +model DetectConfig { + type: string; +} + +model DetectMachine extends DetectConfig { + type: "machine"; + params?: DetectMachineParams; +} + +model DetectFax extends DetectConfig { + type: "fax"; + params?: DetectFaxParams; +} + +model DetectDigit extends DetectConfig { + type: "digit"; + params?: DetectDigitParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.detect +// ───────────────────────────────────────────────────────────────────────────── + +model DetectParams { + ...CallAddress; + + /** Identifier used to control the active detector. */ + control_id: string; + + /** Detector to run (variant keyed on `detect.type`). */ + detect: DetectConfig; + + /** Maximum time (sec >= 0) to run the detector. Default `30.0`. */ + @minValue(0) + timeout?: float64; + + /** HTTP(s) URL to POST detector events to. */ + status_url?: url; +} + +model DetectResult { + ...RelayResult; + + /** Echo of the detector `control_id`. */ + control_id?: string; + + /** The call id. */ + call_id?: string; +} + +/** Start a detector on an active call. May run multiple in parallel. */ +@rpcMethod("calling.detect") +@summary("Start a detector (machine/fax/digit)") +op detect(...DetectParams): DetectResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.detect.stop +// ───────────────────────────────────────────────────────────────────────────── + +model DetectStopParams { + ...CallAddress; + + /** The detector `control_id` assigned in `calling.detect`. */ + control_id: string; +} + +model DetectStopResult { + ...RelayResult; + + /** Echo of the detector `control_id`. */ + control_id?: string; + + /** The call id. */ + call_id?: string; +} + +/** Stop an active detector. */ +@rpcMethod("calling.detect.stop") +@summary("Stop a detector") +op detectStop(...DetectStopParams): DetectStopResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.send_fax +// ───────────────────────────────────────────────────────────────────────────── + +model SendFaxParams { + ...CallAddress; + + /** Identifier used to control the active faxing. */ + control_id: string; + + /** Location of the fax document to send. PDF format only. */ + document: url; + + /** Identity to display on the receiving fax. Default is the SignalWire DID. */ + identity?: string; + + /** + * Custom info added to the header of each fax page (alongside identity, date, + * and page number). `SignalWire` is the default. Set to empty string to + * disable sending any header. + */ + header_info?: string = "SignalWire"; + + /** HTTP(s) URL to POST fax events to. */ + status_url?: url; +} + +model SendFaxResult { + ...RelayResult; + + /** Echo of the fax `control_id`. */ + control_id?: string; + + /** The call id. */ + call_id?: string; +} + +/** + * Send a fax. Can only be executed on a one-legged call that is not executing + * anything else. + */ +@rpcMethod("calling.send_fax") +@summary("Send a PDF fax") +op sendFax(...SendFaxParams): SendFaxResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.send_fax.stop +// ───────────────────────────────────────────────────────────────────────────── + +model SendFaxStopParams { + ...CallAddress; + + /** The send-fax `control_id` assigned in `calling.send_fax`. */ + control_id: string; +} + +model SendFaxStopResult { + ...RelayResult; + + /** Echo of the fax `control_id`. */ + control_id?: string; + + /** The call id. */ + call_id?: string; +} + +/** Stop sending a fax. */ +@rpcMethod("calling.send_fax.stop") +@summary("Stop sending a fax") +op sendFaxStop(...SendFaxStopParams): SendFaxStopResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.receive_fax +// ───────────────────────────────────────────────────────────────────────────── + +model ReceiveFaxParams { + ...CallAddress; + + /** Identifier used to control the active faxing. */ + control_id: string; + + /** HTTP(s) URL to POST fax events to. */ + status_url?: url; +} + +model ReceiveFaxResult { + ...RelayResult; + + /** Echo of the fax `control_id`. */ + control_id?: string; + + /** The call id. */ + call_id?: string; +} + +/** + * Receive a fax. Can only be executed on a one-legged call that is not executing + * anything else. + */ +@rpcMethod("calling.receive_fax") +@summary("Receive a fax") +op receiveFax(...ReceiveFaxParams): ReceiveFaxResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.receive_fax.stop +// ───────────────────────────────────────────────────────────────────────────── + +model ReceiveFaxStopParams { + ...CallAddress; + + /** The fax `control_id` assigned in `calling.receive_fax`. */ + control_id: string; +} + +model ReceiveFaxStopResult { + ...RelayResult; + + /** Echo of the fax `control_id`. */ + control_id?: string; + + /** The call id. */ + call_id?: string; +} + +/** Stop receiving a fax. */ +@rpcMethod("calling.receive_fax.stop") +@summary("Stop receiving a fax") +op receiveFaxStop(...ReceiveFaxStopParams): ReceiveFaxStopResult; + +// ═════════════════════════════════════════════════════════════════════════════ +// Tap subobjects (calling.tap) +// +// `tap` carries the media-selection variant (keyed on `tap.type`); `device` +// carries the delivery-target variant (keyed on `device.type`). Both are +// discriminated bases with `extends` variants. The same TapDevice shape is +// echoed back in the result as `source_device` with all params resolved. +// ═════════════════════════════════════════════════════════════════════════════ + +/** + * Which side of the call to tap. `listen` = what the call party hears; `speak` + * = what the call party says. + */ +union TapDirection { + "listen", + "speak", + "both", +} + +/** + * Audio codec for the tapped media. Open-ended list — it will match the tapped + * audio if not set. + */ +union TapCodec { + "OPUS", + "PCMA", + "PCMU", +} + +/** `audio` tap params. */ +model TapAudioParams { + /** Side of the call to tap. Default `speak`. */ + direction?: TapDirection = "speak"; +} + +/** Media to intercept. Discriminated on `type` (documented value `audio`). */ +@discriminator("type") +model TapConfig { + type: string; +} + +model TapAudio extends TapConfig { + type: "audio"; + params: TapAudioParams; +} + +/** `rtp` device params (delivery target). */ +model TapRtpDeviceParams { + /** + * RTP IPv4 address. Must be an IP owned by the customer or expecting our + * traffic; specifying a private IP or a SignalWire-owned public IP is + * forbidden. + */ + addr: string; + + /** RTP port. */ + port: int32; + + /** Codec — matches the tapped audio if not set. */ + codec?: TapCodec; + + /** Packetization time in ms — matches the tapped audio if not set. */ + ptime?: int32; + + /** Sample rate in Hz (present in the resolved `source_device` echo). */ + rate?: int32; +} + +/** `ws` device params (delivery target). */ +model TapWsDeviceParams { + /** WebSocket URI. */ + uri: string; + + /** Codec — matches the tapped audio if not set. */ + codec?: TapCodec; + + /** Sample rate in Hz — matches the tapped audio if not set. */ + rate?: int32; +} + +/** + * Device to receive the tapped media. Discriminated on `type` (`rtp`|`ws`; + * future: `phone`|`webrtc`|`sip`). Echoed back fully-resolved as the result's + * `source_device`. + */ +@discriminator("type") +model TapDevice { + type: string; +} + +model TapRtpDevice extends TapDevice { + type: "rtp"; + params: TapRtpDeviceParams; +} + +model TapWsDevice extends TapDevice { + type: "ws"; + params: TapWsDeviceParams; +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.tap +// ───────────────────────────────────────────────────────────────────────────── + +model TapParams { + ...CallAddress; + + /** Identifier used to control the active tap. */ + control_id: string; + + /** Media to intercept (variant keyed on `tap.type`). */ + tap: TapConfig; + + /** Device to receive the tapped media (variant keyed on `device.type`). */ + device: TapDevice; + + /** HTTP(s) URL to POST tap events to. */ + status_url?: url; +} + +model TapResult { + ...RelayResult; + + /** The call id. */ + call_id?: string; + + /** Echo of the tap `control_id`. */ + control_id?: string; + + /** + * The source device with all params filled in, so the destination knows what + * is being delivered (offer/answer model). + */ + source_device?: TapDevice; +} + +/** + * Intercept call media and stream it to an external device. Transcoding and + * resampling are available on request. + */ +@rpcMethod("calling.tap") +@summary("Tap call media to an external device") +op tap(...TapParams): TapResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.tap.stop +// ───────────────────────────────────────────────────────────────────────────── + +model TapStopParams { + ...CallAddress; + + /** The tap `control_id` assigned in `calling.tap`. */ + control_id: string; +} + +model TapStopResult { + ...RelayResult; + + /** Echo of the tap `control_id`. */ + control_id?: string; + + /** The call id. */ + call_id?: string; +} + +/** Stop an active call tap. */ +@rpcMethod("calling.tap.stop") +@summary("Stop a call tap") +op tapStop(...TapStopParams): TapStopResult; + +// ═════════════════════════════════════════════════════════════════════════════ +// calling.stream +// +// Flat params (no nested tap/device union). NOTE: the result echoes `node_id`, +// unlike sibling methods that echo `call_id`. +// ═════════════════════════════════════════════════════════════════════════════ + +/** Which audio track to stream. */ +union StreamTrack { + "inbound_track", + "outbound_track", + "both_tracks", +} + +model StreamParams { + ...CallAddress; + + /** Identifier used to control the active stream. */ + control_id: string; + + /** WebSocket URI (`wss://`) to stream audio to. */ + url: url; + + /** A friendly name for the stream. */ + name?: string; + + /** Codec for the streamed audio. Default is the call's native codec. */ + codec?: string; + + /** + * Which audio track to stream. `inbound_track` (what the caller says), + * `outbound_track` (what the caller hears), or `both_tracks`. Default + * `inbound_track`. + */ + track?: StreamTrack = "inbound_track"; + + /** HTTP(s) URL to POST stream status events to. */ + status_url?: url; + + /** HTTP method for `status_url`. Default `POST`. */ + status_url_method?: "GET" | "POST" = "POST"; + + /** Bearer token to include in the WebSocket connection. */ + authorization_bearer_token?: string; + + /** + * JSON object of custom key-value pairs sent to the WebSocket endpoint on + * connect. + */ + custom_parameters?: Record; +} + +model StreamResult { + ...RelayResult; + + /** Echo of the stream `control_id`. */ + control_id?: string; + + /** Node the call is on (this method echoes `node_id`, not `call_id`). */ + node_id?: string; +} + +/** Start streaming call audio to a WebSocket endpoint. */ +@rpcMethod("calling.stream") +@summary("Stream call audio to a WebSocket endpoint") +op stream(...StreamParams): StreamResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.stream.stop +// ───────────────────────────────────────────────────────────────────────────── + +model StreamStopParams { + ...CallAddress; + + /** The stream `control_id` assigned in `calling.stream`. */ + control_id: string; +} + +model StreamStopResult { + ...RelayResult; + + /** Echo of the stream `control_id`. */ + control_id?: string; + + /** The call id. */ + call_id?: string; +} + +/** Stop an active call stream. */ +@rpcMethod("calling.stream.stop") +@summary("Stop a call stream") +op streamStop(...StreamStopParams): StreamStopResult; diff --git a/specs/relay/calling/methods/pay-play.tsp b/specs/relay/calling/methods/pay-play.tsp new file mode 100644 index 0000000000..7bc321a626 --- /dev/null +++ b/specs/relay/calling/methods/pay-play.tsp @@ -0,0 +1,390 @@ +import "@signalwire/typespec-asyncapi"; +import "../common.tsp"; + +using SignalWire.AsyncAPI; + +namespace Relay.Calling; + +// ═════════════════════════════════════════════════════════════════════════════ +// Shared enums (pay / play family) +// ═════════════════════════════════════════════════════════════════════════════ + +/** + * How the Pay IVR collects payment details. Only `dtmf` is currently supported. + * (The protocol reference documents `dtmf|voice`, but the gateway parser accepts + * `dtmf|speech` — see open questions.) + */ +union PayInput { + "dtmf", + "speech", +} + +/** + * Payment method used for the Pay request. The gateway parser accepts + * `credit-card` and `ach-debit`; only `credit-card` is currently supported. + * (The doc prose says `credit-card` and the wire example sends `creditcard`, + * but the canonical/accepted value is `credit-card`.) + */ +union PayMethod { + "credit-card", + "ach-debit", +} + +/** Bank account type (used when `payment_method` is `ach-debit`). */ +union PayBankAccountType { + "consumer-checking", + "consumer-savings", + "commercial-checking", +} + +/** Whether the tokenized payment is a one-off charge or reusable for recurring billing. */ +union PayTokenType { + "one-time", + "reusable", +} + +/** Situation a custom Pay prompt applies to. */ +union PayPromptFor { + "payment-card-number", + "expiration-date", + "security-code", + "postal-code", + "bank-routing-number", + "bank-account-number", + "payment-processing", + "payment-completed", + "payment-failed", + "payment-canceled", +} + +/** Action performed by a Pay prompt: `Say` (text-to-speech) or `Play` (audio file). */ +union PayPromptActionType { + "Say", + "Play", +} + +// ═════════════════════════════════════════════════════════════════════════════ +// calling.pay +// ═════════════════════════════════════════════════════════════════════════════ + +/** A name/value pair POSTed to the payment connector alongside payment details. */ +model PayParameter { + /** Parameter name. */ + name: string; + + /** Parameter value. */ + value: string; +} + +/** A single action (Say/Play) executed when a custom prompt is reached. */ +model PayPromptAction { + /** `Say` for text-to-speech, `Play` for playing an audio file. */ + type: PayPromptActionType; + + /** Sentence to speak (for `Say`) or audio URL to play (for `Play`). */ + phrase: string; +} + +/** + * A custom prompt overriding the Pay IVR default for a given situation. + * + * `card_type` and `error_type` are SPACE-DELIMITED token strings on the wire (not + * arrays) — e.g. `error_type: "timeout invalid-card-number invalid-card-type"`. + */ +model PayPrompt { + /** The situation this prompt applies to. */ + for: PayPromptFor; + + /** + * Space-delimited card-type tokens this prompt applies to (subset of + * `visa mastercard amex maestro discover jcb diners-club`). Applies to all + * card types if unset. + */ + card_type?: string; + + /** + * Space-delimited error-type tokens this prompt applies to. Documented tokens: + * timeout, invalid-card-number, invalid-card-type, invalid-date, + * invalid-security-code, invalid-postal-code, session-in-progress, + * card-declined. (The gateway parser additionally recognizes + * invalid-bank-routing-number, invalid-bank-account-number, and + * input-matching-failed.) + */ + error_type?: string; + + /** Actions to execute for this prompt. */ + actions: PayPromptAction[]; +} + +model PayParams { + ...CallAddress; + + /** Identifier used to control this active pay (e.g. `calling.pay.stop`). */ + control_id: string; + + /** How payment details are collected. Default `dtmf`. (Only `dtmf` supported.) */ + input?: PayInput = "dtmf"; + + /** URL to request on each status change during the payment process. */ + status_url?: url; + + /** Payment method to use. Default `credit-card`. (Only `credit-card` supported.) */ + payment_method?: PayMethod = "credit-card"; + + /** + * Bank account type (relevant only for `ach-debit`). Undocumented in the + * protocol reference but accepted by the gateway. Default `consumer-checking`. + */ + bank_account_type?: PayBankAccountType = "consumer-checking"; + + /** + * Seconds the Pay IVR waits for the next digit before validating the captured + * digits. Default `5`. (Sent as a JSON string on the wire, e.g. `"6"`.) + */ + @minValue(0) + timeout?: int32 = 5; + + /** + * Number of times the Pay IVR retries when collecting card details. Default + * `1`. (Sent as a JSON string on the wire, e.g. `"3"`.) + */ + @minValue(1) + max_attempts?: int32 = 1; + + /** + * Whether to prompt for the card security code. Default `true`. (Sent as a JSON + * string on the wire, e.g. `"false"`.) + */ + security_code?: boolean = true; + + /** + * Whether to prompt for the billing postal code. Default `true`. (Sent as a + * JSON string on the wire, e.g. `"false"`. A known postcode may instead be + * supplied so the IVR skips the prompt — see open questions.) + */ + postal_code?: boolean = true; + + /** + * Minimum number of digits a caller must enter for the postal code. Default + * `0`. (Sent as a JSON string on the wire, e.g. `"6"`.) + */ + @minValue(0) + min_postal_code_length?: int32 = 0; + + /** URL to POST collected payment details to upon completion. */ + payment_connector_url: url; + + /** Whether the payment token is one-off or reusable. Default `reusable`. */ + token_type?: PayTokenType = "reusable"; + + /** + * Amount to charge against the payment method. Decimal value with no currency + * prefix, passed as a string (e.g. `"15.00"`). Default `"0.00"`. + */ + charge_amount?: string = "0.00"; + + /** Currency of the charge amount. Default `usd`. */ + currency?: string = "usd"; + + /** Language for prompts played to the caller. Default `en-US`. */ + language?: string = "en-US"; + + /** + * Text-to-speech voice for prompts (free-form; passed through to TTS, e.g. + * `woman`, `man`, `polly.Sally`). Default `woman`. + */ + voice?: string = "woman"; + + /** Custom description of the payment. */ + description?: string; + + /** + * SPACE-DELIMITED list of card types allowed in this payment (not an array) — + * subset of `visa mastercard amex maestro discover jcb diners-club`. Default + * `"visa mastercard amex"`. + */ + valid_card_types?: string = "visa mastercard amex"; + + /** Additional name/value pairs to POST to the payment connector. */ + parameters?: PayParameter[]; + + /** Custom prompts that override the IVR defaults. */ + prompts?: PayPrompt[]; +} + +model PayResult { + ...RelayResult; + + /** Echo of the `control_id` from the request. */ + control_id?: string; +} + +/** + * Start a Pay IVR session on an active call: collect card details via DTMF and + * POST them to a payment connector. + */ +@rpcMethod("calling.pay") +@summary("Collect a payment via the Pay IVR") +op pay(...PayParams): PayResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.pay.stop +// ───────────────────────────────────────────────────────────────────────────── + +model PayStopParams { + ...CallAddress; + + /** The `control_id` assigned in `calling.pay`. */ + control_id: string; +} + +model PayStopResult { + ...RelayResult; +} + +/** Stop an active Pay IVR session. */ +@rpcMethod("calling.pay.stop") +@summary("Stop an active pay") +op payStop(...PayStopParams): PayStopResult; + +// ═════════════════════════════════════════════════════════════════════════════ +// calling.play — media elements +// +// `play[]` is a discriminated union on `type`: audio | tts | silence | ringtone. +// Distinct from common.tsp's `Ringback` (connect ringback) — kept separate so +// the emitter emits exactly these four play variants for the play method. +// ═════════════════════════════════════════════════════════════════════════════ + +// The `PlayMedia` union (audio|tts|silence|ringtone) is defined in common.tsp — +// shared with `calling.play_and_collect`. + +model PlayParams { + ...CallAddress; + + /** Identifier used to control this active play (pause/resume/stop/volume). */ + control_id: string; + + /** + * Playback volume, -40dB to +40dB (`0` = original audio, `-40` = muted; + * amplitude gain factor `10^(value/20)`). + */ + @minValue(-40) + @maxValue(40) + volume?: float64; + + /** Which side of the call hears the media. Default `listen`. */ + direction?: "listen" | "speak" | "both" = "listen"; + + /** HTTP(s) URL to POST play events to. */ + status_url?: url; + + /** Ordered list of media elements to play. */ + play: PlayMedia[]; + + /** + * Number of times to play the sequence. `0` loops until the call ends or the + * play is stopped. Default `1`. + */ + @minValue(0) + loop?: int32 = 1; +} + +model PlayResult { + ...RelayResult; + + /** Echo of the `control_id` from the request. */ + control_id?: string; +} + +/** Play a sequence of media elements (audio/TTS/silence/ringtone) to a call. */ +@rpcMethod("calling.play") +@summary("Play media to a call") +op play(...PlayParams): PlayResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.play.pause +// ───────────────────────────────────────────────────────────────────────────── + +model PlayPauseParams { + ...CallAddress; + + /** The playing `control_id` assigned in `calling.play`. */ + control_id: string; +} + +model PlayPauseResult { + ...RelayResult; +} + +/** Pause an active play. */ +@rpcMethod("calling.play.pause") +@summary("Pause an active play") +op playPause(...PlayPauseParams): PlayPauseResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.play.resume +// ───────────────────────────────────────────────────────────────────────────── + +model PlayResumeParams { + ...CallAddress; + + /** The playing `control_id` assigned in `calling.play`. */ + control_id: string; +} + +model PlayResumeResult { + ...RelayResult; +} + +/** Resume an active paused play. */ +@rpcMethod("calling.play.resume") +@summary("Resume a paused play") +op playResume(...PlayResumeParams): PlayResumeResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.play.stop +// ───────────────────────────────────────────────────────────────────────────── + +model PlayStopParams { + ...CallAddress; + + /** The `control_id` assigned in `calling.play`. */ + control_id: string; +} + +model PlayStopResult { + ...RelayResult; +} + +/** Stop an active play. */ +@rpcMethod("calling.play.stop") +@summary("Stop an active play") +op playStop(...PlayStopParams): PlayStopResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.play.volume +// ───────────────────────────────────────────────────────────────────────────── + +model PlayVolumeParams { + ...CallAddress; + + /** The `control_id` assigned in `calling.play`. */ + control_id: string; + + /** + * Playback volume, -40dB to +40dB (`0` = original audio, `-40` = muted; + * amplitude gain factor `10^(value/20)`). + */ + @minValue(-40) + @maxValue(40) + volume: float64; +} + +model PlayVolumeResult { + ...RelayResult; +} + +/** Adjust the volume of an active play. */ +@rpcMethod("calling.play.volume") +@summary("Adjust the volume of an active play") +op playVolume(...PlayVolumeParams): PlayVolumeResult; diff --git a/specs/relay/calling/methods/queue-record-refer.tsp b/specs/relay/calling/methods/queue-record-refer.tsp new file mode 100644 index 0000000000..2675f7ad18 --- /dev/null +++ b/specs/relay/calling/methods/queue-record-refer.tsp @@ -0,0 +1,317 @@ +import "@signalwire/typespec-asyncapi"; +import "../common.tsp"; + +using SignalWire.AsyncAPI; + +namespace Relay.Calling; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.queue.enter +// ───────────────────────────────────────────────────────────────────────────── + +model QueueEnterParams { + ...CallAddress; + + /** Identifier used to control this queue placement. */ + control_id: string; + + /** + * Name of the queue to place the call in. If it does not exist, a new queue is + * created and the call becomes first in it. + */ + queue_name: string; + + /** HTTP(S) URL to deliver RELAY queue event callbacks to. */ + status_url?: url; +} + +model QueueEnterResult { + ...RelayResult; + + /** Echo of the `control_id` supplied in the request. */ + control_id?: string; +} + +/** Place the active call into a named queue. */ +@rpcMethod("calling.queue.enter") +@summary("Place the call into a queue") +op queueEnter(...QueueEnterParams): QueueEnterResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.queue.leave +// ───────────────────────────────────────────────────────────────────────────── + +model QueueLeaveParams { + ...CallAddress; + + /** Identifier used to control this queue placement. */ + control_id: string; + + /** Name of the queue to remove the call from. */ + queue_name: string; + + /** ID of the queue to remove the call from. */ + queue_id?: string; + + /** HTTP(S) URL to deliver RELAY queue event callbacks to. */ + status_url?: url; +} + +model QueueLeaveResult { + ...RelayResult; + + /** Echo of the `control_id` supplied in the request. */ + control_id?: string; +} + +/** Remove the active call from a queue. */ +@rpcMethod("calling.queue.leave") +@summary("Remove the call from a queue") +op queueLeave(...QueueLeaveParams): QueueLeaveResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.record +// +// The wire shape keys the recording spec by subobject NAME (`record:{audio:{}}`) +// rather than a `type` discriminator, so `RecordSpec` is modeled faithfully as +// an object keyed by `audio` (the only documented variant). See openQuestions. +// ───────────────────────────────────────────────────────────────────────────── + +/** Audio direction to capture in a recording. */ +union RecordAudioDirection { + /** What the call party hears. */ + "listen", + + /** What the call party says. */ + "speak", + + /** Both directions. */ + "both", +} + +/** Audio-recording parameters (the `record.audio` subobject). */ +model RecordAudio { + /** Play a beep before recording starts. Default `false`. */ + beep?: boolean = false; + + /** Output file format. Default `mp3`. */ + format?: "mp3" | "wav" = "mp3"; + + /** Record the two call directions on separate channels. Default `false`. */ + stereo?: boolean = false; + + /** Which audio direction(s) to capture. Default `speak`. */ + direction?: RecordAudioDirection = "speak"; + + /** + * Seconds to wait until something is heard before giving up. Disable with `0`. + * Default `5.0`. + */ + @minValue(0) + initial_timeout?: float64 = 5.0; + + /** + * Seconds of silence to wait after the call party stops speaking before ending + * the recording. Disable with `0`. Default `1.0`. + */ + @minValue(0) + end_silence_timeout?: float64 = 1.0; + + /** DTMF digits that end the recording. Default `#*`. */ + terminators?: string = "#*"; + + /** + * Input sensitivity: `0` = hear nothing, `100` = hear everything. Default + * `44`. + */ + @minValue(0) + @maxValue(100) + input_sensitivity?: float64 = 44.0; +} + +/** + * Recording spec. Keyed by subobject name (`audio`) rather than a `type` + * discriminator; only the `audio` variant is documented. + */ +model RecordSpec { + /** Audio-recording parameters. */ + audio: RecordAudio; +} + +model RecordParams { + ...CallAddress; + + /** Identifier used to control active recordings. */ + control_id: string; + + /** The recording spec (subobject-keyed; only `audio` is documented). */ + record: RecordSpec; + + /** HTTP(S) URL to deliver RELAY recording event callbacks to. */ + status_url?: url; +} + +model RecordResult { + ...RelayResult; + + /** Echo of the `control_id` supplied in the request. */ + control_id?: string; + + /** URL of the resulting recording. */ + url?: url; +} + +/** Record a call. Async-safe — may be executed multiple times in parallel. */ +@rpcMethod("calling.record") +@summary("Record a call") +op recordCall(...RecordParams): RecordResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.record.pause +// ───────────────────────────────────────────────────────────────────────────── + +/** How an active recording behaves while paused. */ +union RecordPauseBehavior { + /** Omit the paused span from the recording. */ + "skip", + + /** Include the paused span as silence in the recording. */ + "silence", +} + +model RecordPauseParams { + ...CallAddress; + + /** The `control_id` assigned in `calling.record`. */ + control_id: string; + + /** Behavior of the recording while paused. Default `skip`. */ + behavior?: RecordPauseBehavior = "skip"; +} + +model RecordPauseResult { + ...RelayResult; + + /** Echo of the `control_id` supplied in the request. */ + control_id?: string; +} + +/** Pause an active call recording. Async-safe. */ +@rpcMethod("calling.record.pause") +@summary("Pause an active recording") +op recordPause(...RecordPauseParams): RecordPauseResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.record.resume +// ───────────────────────────────────────────────────────────────────────────── + +model RecordResumeParams { + ...CallAddress; + + /** The `control_id` assigned in `calling.record`. */ + control_id: string; +} + +model RecordResumeResult { + ...RelayResult; + + /** Echo of the `control_id` supplied in the request. */ + control_id?: string; +} + +/** Resume a previously paused call recording. Async-safe. */ +@rpcMethod("calling.record.resume") +@summary("Resume a paused recording") +op recordResume(...RecordResumeParams): RecordResumeResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.record.stop +// ───────────────────────────────────────────────────────────────────────────── + +model RecordStopParams { + ...CallAddress; + + /** The `control_id` assigned in `calling.record`. */ + control_id: string; +} + +model RecordStopResult { + ...RelayResult; + + /** Echo of the `control_id` supplied in the request. */ + control_id?: string; +} + +/** Stop an active call recording. Async-safe. */ +@rpcMethod("calling.record.stop") +@summary("Stop an active recording") +op recordStop(...RecordStopParams): RecordStopResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.refer +// +// SIP REFER transfer. `device` is a discriminated union but only the `sip` +// variant is valid, modeled as a `@discriminator("type")` base + variant. +// ───────────────────────────────────────────────────────────────────────────── + +/** `sip` REFER device params. */ +model ReferSipDeviceParams { + /** SIP URI to transfer the call to (e.g. `userb@example.com`). */ + to: string; + + /** Username used to authenticate the REFER request. */ + username?: string; + + /** Password used to authenticate the REFER request. */ + password?: string; +} + +/** Target device for a SIP REFER transfer. Discriminated on `type` (`sip` only). */ +@discriminator("type") +model ReferDevice { + type: string; +} + +model ReferSipDevice extends ReferDevice { + type: "sip"; + params: ReferSipDeviceParams; +} + +model ReferParams { + ...CallAddress; + + /** The device to transfer the call to (only `sip` is valid). */ + device: ReferDevice; + + /** HTTP(S) URL to POST refer events to. */ + status_url?: url; +} + +model ReferResult { + ...RelayResult; +} + +/** Transfer a SIP call to an external SIP endpoint via SIP REFER. Async-safe. */ +@rpcMethod("calling.refer") +@summary("Transfer a SIP call via SIP REFER") +op refer(...ReferParams): ReferResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.pass +// ───────────────────────────────────────────────────────────────────────────── + +model PassParams { + ...CallAddress; +} + +model PassResult { + ...RelayResult; +} + +/** + * Pass on a `calling.call.receive` offer so SignalWire offers the call to another + * RELAY consumer. + */ +@rpcMethod("calling.pass") +@summary("Pass the call offer to another consumer") +op pass(...PassParams): PassResult; diff --git a/specs/relay/calling/methods/transcribe-misc.tsp b/specs/relay/calling/methods/transcribe-misc.tsp new file mode 100644 index 0000000000..5f434f89c7 --- /dev/null +++ b/specs/relay/calling/methods/transcribe-misc.tsp @@ -0,0 +1,267 @@ +import "@signalwire/typespec-asyncapi"; +import "../common.tsp"; + +using SignalWire.AsyncAPI; + +namespace Relay.Calling; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.transcribe +// ───────────────────────────────────────────────────────────────────────────── + +model TranscribeParams { + ...CallAddress; + + /** Identifier used to control (e.g. stop) the active transcription. */ + control_id: string; + + /** http or https URL to deliver transcription status event callbacks to. */ + status_url?: url; +} + +model TranscribeResult { + ...RelayResult; + + /** Path/URL of the shadow recording created for the transcription (e.g. `recordings/.wav`). */ + url?: string; +} + +/** + * (async-safe) Start transcribing a call. Creates a shadow recording with + * transcription enabled. Only one active transcription per call at a time — + * starting a second while one is active returns `"409"` "Transcribe is already + * in progress". + */ +@rpcMethod("calling.transcribe") +@summary("Start transcribing a call") +op transcribe(...TranscribeParams): TranscribeResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.transcribe.stop +// ───────────────────────────────────────────────────────────────────────────── + +model TranscribeStopParams { + ...CallAddress; + + /** The `control_id` assigned in `calling.transcribe`. */ + control_id: string; +} + +model TranscribeStopResult { + ...RelayResult; +} + +/** (async-safe) Stop an active call transcription. */ +@rpcMethod("calling.transcribe.stop") +@summary("Stop an active call transcription") +op transcribeStop(...TranscribeStopParams): TranscribeStopResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.echo +// ───────────────────────────────────────────────────────────────────────────── + +model EchoParams { + ...CallAddress; + + /** Echo duration in seconds (`0` = until the call ends). */ + @minValue(0) + timeout?: int32; + + /** http or https URL to deliver echo status event callbacks to. */ + status_url?: url; +} + +model EchoResult { + ...RelayResult; +} + +/** + * Echo audio back to the caller (useful for testing). Echo ends when the + * timeout expires or the call ends. (No documented stop method — self-terminates.) + */ +@rpcMethod("calling.echo") +@summary("Echo audio back to the caller") +op echo(...EchoParams): EchoResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.bind_digit +// ───────────────────────────────────────────────────────────────────────────── + +model BindDigitParams { + ...CallAddress; + + /** DTMF digit sequence to bind (e.g. `"*1"`). */ + digits: string; + + /** Method name to invoke when the digits are pressed (e.g. `calling.play`). */ + bind_method: string; + + /** + * Parameters to pass to the bound method. Free-form: the shape matches the + * params model of `bind_method` (polymorphic by `bind_method`, no own + * discriminator). Modeled loosely. + */ + params?: Record; + + /** Namespace for this binding (used for selective clearing). */ + realm?: string; + + /** Maximum times this binding can fire (`0` = unlimited). */ + @minValue(0) + max_triggers?: int32; +} + +model BindDigitResult { + ...RelayResult; +} + +/** Bind a DTMF digit sequence to trigger a RELAY method. */ +@rpcMethod("calling.bind_digit") +@summary("Bind a DTMF digit sequence to a RELAY method") +op bindDigit(...BindDigitParams): BindDigitResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.clear_digit_bindings +// ───────────────────────────────────────────────────────────────────────────── + +model ClearDigitBindingsParams { + ...CallAddress; + + /** Only clear bindings in this realm. Clears all bindings when omitted. */ + realm?: string; +} + +model ClearDigitBindingsResult { + ...RelayResult; +} + +/** Clear all digit bindings, optionally filtered by realm. */ +@rpcMethod("calling.clear_digit_bindings") +@summary("Clear digit bindings") +op clearDigitBindings(...ClearDigitBindingsParams): ClearDigitBindingsResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.live_transcribe +// +// `action` is a KEY-DISCRIMINATED union: exactly one of `start`/`stop`/ +// `summarize` is present, keyed by the action name (not a `type` field). The +// source shows each variant only as `{}`, so the inner sub-params are +// loose-modeled as Record. See openQuestions. +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Live-transcribe action. Key-discriminated: provide exactly one of `start`, + * `stop`, or `summarize`. Inner sub-params are undocumented in the protocol + * reference (loose-modeled). + */ +model LiveTranscribeAction { + /** Begin live transcription. Sub-params undocumented. */ + start?: Record; + + /** Stop live transcription. Sub-params undocumented. */ + stop?: Record; + + /** Summarize the live transcription. Sub-params undocumented. */ + summarize?: Record; +} + +model LiveTranscribeParams { + ...CallAddress; + + /** Action to perform (provide exactly one of `start`/`stop`/`summarize`). */ + action: LiveTranscribeAction; +} + +model LiveTranscribeResult { + ...RelayResult; +} + +/** Start or stop live transcription on a call. */ +@rpcMethod("calling.live_transcribe") +@summary("Start or stop live transcription on a call") +op liveTranscribe(...LiveTranscribeParams): LiveTranscribeResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.live_translate +// +// Superset of live_transcribe's action: adds `inject`. Same key-discriminated +// modeling; inner sub-params loose-modeled. See openQuestions. +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Live-translate action. Key-discriminated: provide exactly one of `start`, + * `stop`, `summarize`, or `inject`. Inner sub-params are undocumented in the + * protocol reference (loose-modeled). + */ +model LiveTranslateAction { + /** Begin live translation. Sub-params undocumented. */ + start?: Record; + + /** Stop live translation. Sub-params undocumented. */ + stop?: Record; + + /** Summarize the live translation. Sub-params undocumented. */ + summarize?: Record; + + /** Inject content into the live translation. Sub-params undocumented. */ + inject?: Record; +} + +model LiveTranslateParams { + ...CallAddress; + + /** Action to perform (provide exactly one of `start`/`stop`/`summarize`/`inject`). */ + action: LiveTranslateAction; + + /** http or https URL to deliver translation status event callbacks to. */ + status_url?: url; +} + +model LiveTranslateResult { + ...RelayResult; +} + +/** Start or stop live translation on a call. */ +@rpcMethod("calling.live_translate") +@summary("Start or stop live translation on a call") +op liveTranslate(...LiveTranslateParams): LiveTranslateResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.join_room +// ───────────────────────────────────────────────────────────────────────────── + +model JoinRoomParams { + ...CallAddress; + + /** Room name to join. */ + name: string; + + /** http or https URL to deliver room status event callbacks to. */ + status_url?: url; +} + +model JoinRoomResult { + ...RelayResult; +} + +/** Join a video/audio room by name. */ +@rpcMethod("calling.join_room") +@summary("Join a video/audio room") +op joinRoom(...JoinRoomParams): JoinRoomResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.leave_room +// ───────────────────────────────────────────────────────────────────────────── + +model LeaveRoomParams { + ...CallAddress; +} + +model LeaveRoomResult { + ...RelayResult; +} + +/** Leave the current room (operates on the call's current room; no room param). */ +@rpcMethod("calling.leave_room") +@summary("Leave the current room") +op leaveRoom(...LeaveRoomParams): LeaveRoomResult; diff --git a/specs/relay/calling/methods/transfer-conf-hold-digits.tsp b/specs/relay/calling/methods/transfer-conf-hold-digits.tsp new file mode 100644 index 0000000000..313c652382 --- /dev/null +++ b/specs/relay/calling/methods/transfer-conf-hold-digits.tsp @@ -0,0 +1,300 @@ +import "@signalwire/typespec-asyncapi"; +import "../common.tsp"; + +using SignalWire.AsyncAPI; + +namespace Relay.Calling; + +// ═════════════════════════════════════════════════════════════════════════════ +// Shared enums for this family (conference status-callback knobs) +// ═════════════════════════════════════════════════════════════════════════════ + +/** Beep behaviour when a participant enters/leaves a conference. String-typed on the wire (`"true"`/`"false"` are strings, not booleans). */ +union ConferenceBeep { + "true", + "false", + "onEnter", + "onExit", +} + +/** Whether (and when) a conference is recorded. */ +union ConferenceRecord { + "do-not-record", + "record-from-start", +} + +/** Geographic region the conference media is anchored in. */ +union ConferenceRegion { + "global", + "us", + "eu", +} + +/** Whether leading/trailing silence is trimmed from a conference recording. */ +union ConferenceTrim { + "trim-silence", + "do-not-trim", +} + +/** Encoding of a conference status callback payload. */ +union ConferenceCallbackEventType { + "relay", + "cxml", +} + +/** HTTP method used to deliver a conference status callback. */ +union ConferenceCallbackMethod { + "GET", + "POST", +} + +/** Lifecycle of a conference recording reported via `recording_status_callback`. */ +union ConferenceRecordingCallbackEvent { + "in-progress", + "completed", + "absent", +} + +// ───────────────────────────────────────────────────────────────────────────── +// calling.transfer +// ───────────────────────────────────────────────────────────────────────────── + +model TransferParams { + ...CallAddress; + + /** + * Where to transfer call control. One of: an `https://` script URL to POST, an + * inline SWML script, or a relay application prefixed with `context:`. A single + * wire string — polymorphic by prefix/scheme. + */ + dest: string; +} + +model TransferResult { + ...RelayResult; + + /** The transferred call id (echoed). */ + call_id?: string; +} + +/** (async-safe) Transfer call control to another RELAY application or to a SWML script. */ +@rpcMethod("calling.transfer") +@summary("Transfer call control to a RELAY app or SWML script") +op transfer(...TransferParams): TransferResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.join_conference +// ───────────────────────────────────────────────────────────────────────────── + +model JoinConferenceParams { + ...CallAddress; + + /** Name of the conference to join. */ + name: string; + + /** Join muted. */ + muted?: boolean = false; + + /** Beep behaviour on enter/exit. Default `true`. */ + beep?: ConferenceBeep; + + /** Start the conference when this participant enters. */ + start_on_enter?: boolean = true; + + /** End the conference when this participant exits. */ + end_on_exit?: boolean = false; + + /** URL to CXML or an mp3/wav to play while waiting. Default: hold music. */ + wait_url?: url; + + /** Maximum number of participants (positive, `<= 250`). Default `250`. */ + @minValueExclusive(0) + @maxValue(250) + max_participants?: int32; + + /** Whether/when to record the conference. Default `do-not-record`. */ + record?: ConferenceRecord; + + /** Region the conference media is anchored in. Default `global`. */ + region?: ConferenceRegion; + + /** Trim silence from the recording. Default `trim-silence`. */ + trim?: ConferenceTrim; + + /** A SWML Call ID or CXML CallSid to coach. Default: not set. */ + coach?: string; + + /** URL to POST conference status callbacks to. Default: not set. */ + status_callback?: url; + + /** + * Space-separated list of conference events to deliver to `status_callback`. + * Tokens: `start end join leave mute hold modify speaker announcement`. + * Default: not set. + */ + status_callback_event?: string; + + /** Encoding of the status callback payload. Default `relay`. */ + status_callback_event_type?: ConferenceCallbackEventType; + + /** HTTP method for `status_callback`. Default `POST`. Ignored when `status_callback_event_type` is `relay`. */ + status_callback_method?: ConferenceCallbackMethod; + + /** URL to POST recording status callbacks to. Default: not set. */ + recording_status_callback?: url; + + /** + * Recording lifecycle events to deliver to `recording_status_callback`. + * Default `completed`. (Example uses a space-separated token list, e.g. + * `"in-progress completed"` — see openQuestions.) + */ + recording_status_callback_event?: ConferenceRecordingCallbackEvent; + + /** Encoding of the recording status callback payload. Default `relay`. */ + recording_status_callback_event_type?: ConferenceCallbackEventType; + + /** HTTP method for `recording_status_callback`. Default `POST`. Ignored when `recording_status_callback_event_type` is `relay`. */ + recording_status_callback_method?: ConferenceCallbackMethod; + + /** + * Attach a bidirectional WebSocket stream to the conference. Reuses the same + * `call_device_stream` schema as `calling.connect`'s stream device. + */ + stream?: StreamDeviceParams; +} + +model JoinConferenceResult { + ...RelayResult; +} + +/** Join an ad-hoc audio conference with RELAY and CXML calls. */ +@rpcMethod("calling.join_conference") +@summary("Join an ad-hoc audio conference") +op joinConference(...JoinConferenceParams): JoinConferenceResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.leave_conference +// ───────────────────────────────────────────────────────────────────────────── + +model LeaveConferenceParams { + ...CallAddress; + + /** The conference identifier. Comes from `calling.conference` events. */ + conference_id: string; +} + +model LeaveConferenceResult { + ...RelayResult; +} + +/** Leave an audio conference. */ +@rpcMethod("calling.leave_conference") +@summary("Leave an audio conference") +op leaveConference(...LeaveConferenceParams): LeaveConferenceResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.hold (NOT IMPLEMENTED) +// ───────────────────────────────────────────────────────────────────────────── + +model HoldParams { + ...CallAddress; +} + +model HoldResult { + ...RelayResult; + + /** Resulting hold state (`"hold"`). */ + state?: string; +} + +/** (NOT IMPLEMENTED) (async-safe) Put a call into a hold state. */ +@rpcMethod("calling.hold") +@summary("(Not implemented) Put a call on hold") +op hold(...HoldParams): HoldResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.unhold (NOT IMPLEMENTED) +// ───────────────────────────────────────────────────────────────────────────── + +model UnholdParams { + ...CallAddress; +} + +model UnholdResult { + ...RelayResult; + + /** Resulting hold state (`"unhold"`). */ + state?: string; +} + +/** (NOT IMPLEMENTED) (async-safe) Release a call from a hold state. */ +@rpcMethod("calling.unhold") +@summary("(Not implemented) Release a call from hold") +op unhold(...UnholdParams): UnholdResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.denoise +// ───────────────────────────────────────────────────────────────────────────── + +model DenoiseParams { + ...CallAddress; +} + +model DenoiseResult { + ...RelayResult; +} + +/** (async-safe) Start call noise reduction. */ +@rpcMethod("calling.denoise") +@summary("Start call noise reduction") +op denoise(...DenoiseParams): DenoiseResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.denoise.stop +// ───────────────────────────────────────────────────────────────────────────── + +model DenoiseStopParams { + ...CallAddress; +} + +model DenoiseStopResult { + ...RelayResult; +} + +/** (async-safe) Stop call noise reduction. */ +@rpcMethod("calling.denoise.stop") +@summary("Stop call noise reduction") +op denoiseStop(...DenoiseStopParams): DenoiseStopResult; + +// ───────────────────────────────────────────────────────────────────────────── +// calling.send_digits +// ───────────────────────────────────────────────────────────────────────────── + +model SendDigitsParams { + ...CallAddress; + + /** An identifier used to control the active send-digits operation. */ + control_id: string; + + /** + * The string of digits to play. Allowed: `1234567890*#ABCD`, plus `w` (0.5s + * wait) and `W` (1s wait), repeated for longer waits. Any invalid character + * rejects the entire operation. + */ + digits: string; +} + +model SendDigitsResult { + ...RelayResult; + + /** The send-digits control id (echoed). */ + control_id?: string; + + /** The call id (echoed). */ + call_id?: string; +} + +/** Send DTMF digit tones to a call. */ +@rpcMethod("calling.send_digits") +@summary("Send DTMF digit tones to a call") +op sendDigits(...SendDigitsParams): SendDigitsResult; diff --git a/specs/relay/messaging/main.tsp b/specs/relay/messaging/main.tsp new file mode 100644 index 0000000000..dd60a97b51 --- /dev/null +++ b/specs/relay/messaging/main.tsp @@ -0,0 +1,199 @@ +import "@signalwire/typespec-asyncapi"; + +using SignalWire.AsyncAPI; + +/** + * The Relay **Messaging** service sends outbound SMS/MMS to PSTN numbers and + * delivers inbound-message and delivery-state events. It rides on a connection + * established by `signalwire.connect`; messages are routed by **context**. + */ +@service(#{ title: "SignalWire Relay — Messaging" }) +@server("production", #{ + host: "relay.signalwire.com", + protocol: "wss", + pathname: "/api/relay/wss", + description: "SignalWire Relay WebSocket endpoint.", +}) +@channel("messaging") +@bearerAuth("JWT") +namespace Relay.Messaging; + +// ═════════════════════════════════════════════════════════════════════════════ +// Shared result envelope & enums +// ═════════════════════════════════════════════════════════════════════════════ + +/** + * The common Relay result envelope. `code` is a STRING (`"200"` on success); + * errors are carried in-band via a non-`"200"` `code` plus `message` — there is + * no JSON-RPC `error` object. + */ +model Result { + /** Result code (string), e.g. `"200"`, `"400"`, `"404"`. */ + code: string; + + /** Human-readable result message. */ + message: string; +} + +/** The direction of a message relative to the SignalWire platform. */ +union MessageDirection { + /** Message received from the carrier network (toward the client). */ + "inbound", + + /** Message sent toward the carrier network (away from the client). */ + "outbound", +} + +/** Delivery-lifecycle state of a message (`messaging.state`). */ +union MessageState { + /** Message accepted and waiting to be processed. */ + "queued", + + /** Message processing has started. */ + "initiated", + + /** Message handed off to the carrier. */ + "sent", + + /** Carrier confirmed delivery to the handset. */ + "delivered", + + /** Carrier reported the message could not be delivered. */ + "undelivered", + + /** Message failed before/at the carrier. */ + "failed", +} + +// ═════════════════════════════════════════════════════════════════════════════ +// messaging.send +// ═════════════════════════════════════════════════════════════════════════════ + +model SendParams { + /** The context to receive inbound events for this message. */ + context: string; + + /** Optional client-defined tags, surfaced for searching in the UI. */ + tags?: string[]; + + /** + * Region of the world to originate the message from. Defaults to a value + * picked from account preferences or device location. + */ + region?: string; + + /** Destination phone number, in E.164 format. */ + to_number: string; + + /** Origin phone number, in E.164 format. */ + from_number: string; + + /** + * Body of the message. Required if `media` is absent; at least one of `body` + * or `media` must be present (both may be supplied). + */ + body?: string; + + /** + * An array of media URLs to send (MMS). Required if `body` is absent; at + * least one of `body` or `media` must be present (both may be supplied). + */ + media?: string[]; +} + +model SendResult { + ...Result; + + /** The UUID of the accepted message (present on success). */ + message_id: string; +} + +/** + * Send an outbound SMS/MMS to a PSTN phone number. At least one of `body` or + * `media` must be supplied. + */ +@rpcMethod("messaging.send") +@summary("Send an outbound message") +op send(...SendParams): SendResult; + +// ═════════════════════════════════════════════════════════════════════════════ +// Events +// +// The shared event envelope (context/timestamp/space_id/project_id) is folded +// into the carrier by the emitter; only the inner event params are authored. +// ═════════════════════════════════════════════════════════════════════════════ + +/** + * An inbound message has been received. + */ +@event("messaging.receive") +model ReceiveEvent { + /** The UUID of the message. */ + message_id: string; + + /** The context the message was set on. */ + context: string; + + /** The message's direction. Always `inbound` for this event. */ + direction: MessageDirection; + + /** Optional client data this message is tagged with. */ + tags?: string[]; + + /** Origin phone number, in E.164 format. */ + from_number: string; + + /** Destination phone number, in E.164 format. */ + to_number: string; + + /** Body of the message. */ + body: string; + + /** An array of media URLs included with the message. */ + media: string[]; + + /** Number of segments the message was split into. */ + segments: int32; + + /** The message state. Always `received` for an inbound message. */ + message_state: "received"; +} + +/** + * A change in the delivery state of a message. + */ +@event("messaging.state") +model StateEvent { + /** The UUID of the message. */ + message_id: string; + + /** The context the message was set on. */ + context: string; + + /** The message's direction. */ + direction: MessageDirection; + + /** Optional client data this message is tagged with. */ + tags?: string[]; + + /** Origin phone number, in E.164 format. */ + from_number: string; + + /** Destination phone number, in E.164 format. */ + to_number: string; + + /** Body of the message. */ + body: string; + + /** An array of media URLs included with the message. */ + media: string[]; + + /** Number of segments the message was split into. */ + segments: int32; + + /** The new delivery-lifecycle state of the message. */ + message_state: MessageState; + + /** Explanation of the state. Present only on `undelivered`/`failed`. */ + reason?: string; +} diff --git a/specs/relay/messaging/tspconfig.yaml b/specs/relay/messaging/tspconfig.yaml new file mode 100644 index 0000000000..94cbb698ff --- /dev/null +++ b/specs/relay/messaging/tspconfig.yaml @@ -0,0 +1,7 @@ +emit: + - "@signalwire/typespec-asyncapi" + +options: + "@signalwire/typespec-asyncapi": + emitter-output-dir: "{cwd}/../../../fern/apis/relay" + output-file: "messaging.yaml" diff --git a/specs/relay/provisioning/main.tsp b/specs/relay/provisioning/main.tsp new file mode 100644 index 0000000000..4d9d077ae9 --- /dev/null +++ b/specs/relay/provisioning/main.tsp @@ -0,0 +1,94 @@ +import "@signalwire/typespec-asyncapi"; + +using SignalWire.AsyncAPI; + +/** + * The `provisioning` protocol lets a Relay **connector** request its runtime + * configuration from SignalWire. The connector reports its identity and network + * endpoints, and SignalWire returns the rendered connector configuration (for a + * FreeSWITCH connector, a SIP profile as XML). This service is + * connector-internal: a single method, no server-pushed events, and currently + * only the `freeswitch` connector target is supported. + */ +@service(#{ title: "SignalWire Relay — Connector Provisioning" }) +@server("production", #{ + host: "relay.signalwire.com", + protocol: "wss", + pathname: "/api/relay/wss", + description: "SignalWire Relay WebSocket endpoint.", +}) +@channel("provisioning") +@bearerAuth("JWT") +namespace Relay.Provisioning; + +// ───────────────────────────────────────────────────────────────────────────── +// Shared result base +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Standard `{code, message}` result fields shared by every Relay response. + * `code` is a STRING (e.g. `"200"`); any value other than `"200"` is an error. + */ +model Result { + /** Result code (string). `"200"` on success; e.g. `"400"`/`"404"` on error. */ + code: string; + + /** Human-readable result message. */ + message: string; +} + +// ───────────────────────────────────────────────────────────────────────────── +// provisioning.configure +// ───────────────────────────────────────────────────────────────────────────── + +/** The connector type being provisioned. Currently only `freeswitch` is supported. */ +union ConnectorTarget { + string, + + /** A FreeSWITCH connector. */ + freeswitch: "freeswitch", +} + +model ConfigureParams { + /** The connector type to provision. Currently only `freeswitch` is supported. */ + target: ConnectorTarget; + + /** The connector's local (internal) endpoint as an IPv4 address, e.g. `10.10.0.2`. */ + local_endpoint: string; + + /** The connector's external (public) endpoint as an IPv4 address, e.g. `8.8.8.8`. */ + external_endpoint: string; + + /** UUID of the Relay connector being configured. */ + relay_connector_id: string; +} + +/** + * The rendered connector configuration returned to the connector. + * + * Note: `profile` is the raw FreeSWITCH SIP profile **rendered as XML**, carried + * as a single string. The precise shape (raw-XML string vs. a structured object) + * is not specified by the source and is modeled here as an opaque string. Other + * keys under `configuration` for non-`freeswitch` targets are unconfirmed. + */ +model Configuration { + /** The FreeSWITCH SIP profile, rendered as an XML document. */ + profile: string; +} + +model ConfigureResult { + ...Result; + + /** The rendered connector configuration. */ + configuration: Configuration; +} + +/** + * Request a connector's SignalWire configuration. The connector supplies its + * identity (`relay_connector_id`) and network endpoints (`local_endpoint`, + * `external_endpoint`) and receives its configuration payload — for a + * `freeswitch` target, a FreeSWITCH SIP profile rendered as XML. + */ +@rpcMethod("provisioning.configure") +@summary("Request SignalWire connector configuration") +op configure(...ConfigureParams): ConfigureResult; diff --git a/specs/relay/provisioning/tspconfig.yaml b/specs/relay/provisioning/tspconfig.yaml new file mode 100644 index 0000000000..4173a5628c --- /dev/null +++ b/specs/relay/provisioning/tspconfig.yaml @@ -0,0 +1,7 @@ +emit: + - "@signalwire/typespec-asyncapi" + +options: + "@signalwire/typespec-asyncapi": + emitter-output-dir: "{cwd}/../../../fern/apis/relay" + output-file: "provisioning.yaml" diff --git a/specs/relay/tasking/main.tsp b/specs/relay/tasking/main.tsp new file mode 100644 index 0000000000..1e53316742 --- /dev/null +++ b/specs/relay/tasking/main.tsp @@ -0,0 +1,90 @@ +import "@signalwire/typespec-asyncapi"; + +using SignalWire.AsyncAPI; + +/** + * The `tasking` service delivers arbitrary, caller-defined JSON messages to + * Relay consumers subscribed to a context. A client calls `tasking.deliver` + * with a `context` and an opaque `message`; SignalWire queues the task and + * pushes a `queuing.relay.tasks` event to every consumer listening on that + * context. The `message` payload is never inspected — it is echoed verbatim + * from the deliver request into the task event. + */ +@service(#{ title: "SignalWire Relay — Tasking" }) +@server("production", #{ + host: "relay.signalwire.com", + protocol: "wss", + pathname: "/api/relay/wss", + description: "SignalWire Relay WebSocket endpoint.", +}) +@channel("tasking") +@bearerAuth("JWT") +namespace Relay.Tasking; + +// ───────────────────────────────────────────────────────────────────────────── +// tasking.deliver +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Standard Relay `{code, message}` result. `code` is a STRING (`"200"` on + * success). The Tasking source does not enumerate result fields or non-200 + * codes; per the Relay convention every response carries at least these two. + */ +model Result { + /** Result code (string). `"200"` on success. */ + code: string; + /** Human-readable result message. */ + message: string; +} + +model DeliverParams { + /** The context to deliver the task to. Consumers subscribed to this context receive the `queuing.relay.tasks` event. */ + context: string; + + /** + * The message to send. Opaque, caller-defined JSON (e.g. `{ "foo": 123 }`); + * SignalWire imposes no schema and echoes it verbatim into the task event. + */ + message: Record; +} + +/** Acknowledgement of a `tasking.deliver` request. */ +model DeliverResult { + ...Result; +} + +/** + * Send an arbitrary JSON message to a context for delivery to subscribed Relay + * consumers. SignalWire queues the task and pushes it as a + * `queuing.relay.tasks` event. + */ +@rpcMethod("tasking.deliver") +@summary("Deliver a task message to a context") +op deliver(...DeliverParams): DeliverResult; + +// ───────────────────────────────────────────────────────────────────────────── +// Events +// ───────────────────────────────────────────────────────────────────────────── + +/** + * A task has been received. Pushed to consumers subscribed to the task's + * `context`, carrying the opaque `message` echoed verbatim from the + * originating `tasking.deliver` call. + */ +@event("queuing.relay.tasks") +model TasksEvent { + /** The context that received the event. */ + context: string; + + /** Seconds since the epoch, with up to microsecond resolution. The time the task was received. */ + timestamp: float64; + + /** The SignalWire space the task belongs to. */ + space_id: string; + + /** The SignalWire project the task belongs to. */ + project_id: string; + + /** The opaque message passed to the task, echoed verbatim from `tasking.deliver`. */ + message: Record; +} diff --git a/specs/relay/tasking/tspconfig.yaml b/specs/relay/tasking/tspconfig.yaml new file mode 100644 index 0000000000..7c6b4f4c0c --- /dev/null +++ b/specs/relay/tasking/tspconfig.yaml @@ -0,0 +1,7 @@ +emit: + - "@signalwire/typespec-asyncapi" + +options: + "@signalwire/typespec-asyncapi": + emitter-output-dir: "{cwd}/../../../fern/apis/relay" + output-file: "tasking.yaml" diff --git a/specs/relay/webrtc/main.tsp b/specs/relay/webrtc/main.tsp new file mode 100644 index 0000000000..b4ecfc10b5 --- /dev/null +++ b/specs/relay/webrtc/main.tsp @@ -0,0 +1,157 @@ +import "@signalwire/typespec-asyncapi"; + +using SignalWire.AsyncAPI; + +/** + * The Relay **WebRTC** service is a thin transport wrapper around the Verto + * signaling sub-protocol. Methods are dispatched as `blade.execute`; the + * `message` method tunnels an opaque inner Verto JSON-RPC frame to FreeSWITCH, + * and `conference.list` enumerates joinable conferences. It rides on a + * connection established by `signalwire.connect`. + * + * The inner Verto message protocol itself (`verto.invite`/`verto.answer`/ + * `verto.bye`/`verto.modify`, `dialogParams`, conference control) is OUT OF + * SCOPE here and is modeled as a loose pass-through. Its full union is defined + * separately in `verto_messages.md`. + */ +@service(#{ title: "SignalWire Relay — WebRTC" }) +@server("production", #{ + host: "relay.signalwire.com", + protocol: "wss", + pathname: "/api/relay/wss", + description: "SignalWire Relay WebSocket endpoint.", +}) +@channel("webrtc") +@bearerAuth("JWT") +namespace Relay.WebRTC; + +// ═════════════════════════════════════════════════════════════════════════════ +// Shared result envelope +// ═════════════════════════════════════════════════════════════════════════════ + +/** + * The common Relay result envelope. `code` is a STRING (`"200"` on success); + * errors are carried in-band via a non-`"200"` `code` plus `message` — there is + * no JSON-RPC `error` object. + */ +model Result { + /** Result code (string), e.g. `"200"`, `"400"`, `"404"`. */ + code: string; + + /** Human-readable result message. */ + message: string; +} + +// ═════════════════════════════════════════════════════════════════════════════ +// webrtc.message +// ═════════════════════════════════════════════════════════════════════════════ + +model MessageParams { + /** + * The FreeSWITCH node id this message targets. Set by the client to the FS + * nodeid once a call exists (sourced from prior events/responses); absent on + * the very first message before a call is established. + */ + node_id?: string; + + /** + * The inner Verto JSON-RPC 2.0 frame to transport to FreeSWITCH (e.g. a + * `verto.invite` with `dialogParams`/`sdp`/`layout`/`positions`). Modeled as + * a loose pass-through: the full Verto method/`params` union is out of scope + * here (see `verto_messages.md`). + */ + message: Record; + + /** + * "Event channel" subscriptions to apply alongside this request — intended + * for the case of joining a conference and wanting its event feed. Values are + * conference/room event channels (e.g. `member.joined`, `member.left`, + * `room.ended`, `room.updated`, `layout.changed`, `member.updated`); + * illustrative, not exhaustive. + */ + subscribe?: string[]; +} + +/** + * Transport a Verto JSON-RPC message from the client to FreeSWITCH. Carries the + * opaque inner Verto frame plus optional event-channel subscriptions. + * + * The result only acknowledges receipt/forwarding (`"Received"`); the actual + * Verto outcome arrives asynchronously via the `webrtc.message` event. + */ +@rpcMethod("message") +@summary("Transport a Verto message to FreeSWITCH") +op message(...MessageParams): MessageResult; + +/** Acknowledgement that the Verto message was received and forwarded. */ +model MessageResult { + ...Result; +} + +// ═════════════════════════════════════════════════════════════════════════════ +// conference.list +// ═════════════════════════════════════════════════════════════════════════════ + +/** Empty parameters — `conference.list` takes no arguments. */ +model ConferenceListParams {} + +/** A single active conference the client may join. */ +model Conference { + /** The FreeSWITCH node id hosting the conference. */ + node_id: string; + + /** The conference's UUID. */ + conference_id: string; + + /** Human-readable conference name (e.g. `Awesome Room!`). */ + name: string; + + /** Extension to dial to reach the conference. */ + extension: string; + + /** Creation/last-activity time, in seconds since epoch (microsecond resolution). */ + timestamp: float64; +} + +model ConferenceListResult { + ...Result; + + /** The active conferences the current client can join. */ + data: Conference[]; +} + +/** + * List the active conferences the current client can join. Async-safe. + */ +@rpcMethod("conference.list") +@summary("List joinable conferences") +op conferenceList(...ConferenceListParams): ConferenceListResult; + +// ═════════════════════════════════════════════════════════════════════════════ +// Events +// +// The shared event envelope (event_channel/timestamp/project_id) is folded into +// the carrier by the emitter; only the inner event params are authored here. +// ═════════════════════════════════════════════════════════════════════════════ + +/** + * Transport event delivering a Verto JSON-RPC message from FreeSWITCH back to + * the client — both Verto responses and subscribed conference/room events. + * + * The inner `params` is the opaque Verto frame; its full union is out of scope + * here (see `verto_messages.md`). + */ +@event("webrtc.message") +model MessageEvent { + /** + * The FreeSWITCH node id sending the event. Sent by FS so the client can + * capture the specific nodeid once a call is started. + */ + node_id?: string; + + /** + * The Verto JSON-RPC frame being transported (a Verto response or a + * conference/room event). Loose pass-through — see `verto_messages.md`. + */ + params: Record; +} diff --git a/specs/relay/webrtc/tspconfig.yaml b/specs/relay/webrtc/tspconfig.yaml new file mode 100644 index 0000000000..1fafe11a4a --- /dev/null +++ b/specs/relay/webrtc/tspconfig.yaml @@ -0,0 +1,7 @@ +emit: + - "@signalwire/typespec-asyncapi" + +options: + "@signalwire/typespec-asyncapi": + emitter-output-dir: "{cwd}/../../../fern/apis/relay" + output-file: "webrtc.yaml" From e21dda568363db4e5209fcdf87da7a92027376ca Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 19:50:14 -0400 Subject: [PATCH 21/25] feat(apis): add Relay AsyncAPI reference tab (api-name: relay) to API nav --- fern/products/apis/apis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fern/products/apis/apis.yml b/fern/products/apis/apis.yml index 49bcb087d0..96795df452 100644 --- a/fern/products/apis/apis.yml +++ b/fern/products/apis/apis.yml @@ -251,3 +251,6 @@ navigation: contents: - subpackage_swmlWebhook.inbound_call_webhook - subpackage_swmlWebhook.inbound_message_webhook + - api: SignalWire Relay + api-name: relay + slug: relay From ee71b0959117c194a0a982168be7eaa91b8546af Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 21:45:08 -0400 Subject: [PATCH 22/25] fix(asyncapi): Relay server is static wss://relay.signalwire.com (drop invented /api/relay/wss path) Verified against the TS + Python SDK clients: both connect to `wss://${host}` with DEFAULT_RELAY_HOST = relay.signalwire.com and reject a host containing a path. The service is selected by the JSON-RPC method in the payload, not a URL path. --- fern/apis/relay/calling.yaml | 1 - fern/apis/relay/messaging.yaml | 1 - fern/apis/relay/provisioning.yaml | 1 - fern/apis/relay/signalwire.yaml | 1 - fern/apis/relay/tasking.yaml | 1 - fern/apis/relay/webrtc.yaml | 1 - specs/relay/calling/main.tsp | 1 - specs/relay/messaging/main.tsp | 1 - specs/relay/provisioning/main.tsp | 1 - specs/relay/signalwire/main.tsp | 1 - specs/relay/tasking/main.tsp | 1 - specs/relay/webrtc/main.tsp | 1 - 12 files changed, 12 deletions(-) diff --git a/fern/apis/relay/calling.yaml b/fern/apis/relay/calling.yaml index b9e00d57b3..3269158c63 100644 --- a/fern/apis/relay/calling.yaml +++ b/fern/apis/relay/calling.yaml @@ -11,7 +11,6 @@ servers: production: host: relay.signalwire.com protocol: wss - pathname: /api/relay/wss description: SignalWire Relay WebSocket endpoint. security: - $ref: "#/components/securitySchemes/httpBearer" diff --git a/fern/apis/relay/messaging.yaml b/fern/apis/relay/messaging.yaml index 6844d3ca05..7aa3481199 100644 --- a/fern/apis/relay/messaging.yaml +++ b/fern/apis/relay/messaging.yaml @@ -11,7 +11,6 @@ servers: production: host: relay.signalwire.com protocol: wss - pathname: /api/relay/wss description: SignalWire Relay WebSocket endpoint. security: - $ref: "#/components/securitySchemes/httpBearer" diff --git a/fern/apis/relay/provisioning.yaml b/fern/apis/relay/provisioning.yaml index 0192298b9e..34d5c6e3f2 100644 --- a/fern/apis/relay/provisioning.yaml +++ b/fern/apis/relay/provisioning.yaml @@ -14,7 +14,6 @@ servers: production: host: relay.signalwire.com protocol: wss - pathname: /api/relay/wss description: SignalWire Relay WebSocket endpoint. security: - $ref: "#/components/securitySchemes/httpBearer" diff --git a/fern/apis/relay/signalwire.yaml b/fern/apis/relay/signalwire.yaml index 7ef34db51f..425518384d 100644 --- a/fern/apis/relay/signalwire.yaml +++ b/fern/apis/relay/signalwire.yaml @@ -13,7 +13,6 @@ servers: production: host: relay.signalwire.com protocol: wss - pathname: /api/relay/wss description: SignalWire Relay WebSocket endpoint. security: - $ref: "#/components/securitySchemes/httpBearer" diff --git a/fern/apis/relay/tasking.yaml b/fern/apis/relay/tasking.yaml index b5401ec3d8..b7a49d9e1b 100644 --- a/fern/apis/relay/tasking.yaml +++ b/fern/apis/relay/tasking.yaml @@ -14,7 +14,6 @@ servers: production: host: relay.signalwire.com protocol: wss - pathname: /api/relay/wss description: SignalWire Relay WebSocket endpoint. security: - $ref: "#/components/securitySchemes/httpBearer" diff --git a/fern/apis/relay/webrtc.yaml b/fern/apis/relay/webrtc.yaml index d75c797fa7..d1afb16b74 100644 --- a/fern/apis/relay/webrtc.yaml +++ b/fern/apis/relay/webrtc.yaml @@ -18,7 +18,6 @@ servers: production: host: relay.signalwire.com protocol: wss - pathname: /api/relay/wss description: SignalWire Relay WebSocket endpoint. security: - $ref: "#/components/securitySchemes/httpBearer" diff --git a/specs/relay/calling/main.tsp b/specs/relay/calling/main.tsp index 28f930946e..3ccdb708a4 100644 --- a/specs/relay/calling/main.tsp +++ b/specs/relay/calling/main.tsp @@ -25,7 +25,6 @@ using SignalWire.AsyncAPI; @server("production", #{ host: "relay.signalwire.com", protocol: "wss", - pathname: "/api/relay/wss", description: "SignalWire Relay WebSocket endpoint.", }) @channel("calling") diff --git a/specs/relay/messaging/main.tsp b/specs/relay/messaging/main.tsp index dd60a97b51..6e9b981c8f 100644 --- a/specs/relay/messaging/main.tsp +++ b/specs/relay/messaging/main.tsp @@ -11,7 +11,6 @@ using SignalWire.AsyncAPI; @server("production", #{ host: "relay.signalwire.com", protocol: "wss", - pathname: "/api/relay/wss", description: "SignalWire Relay WebSocket endpoint.", }) @channel("messaging") diff --git a/specs/relay/provisioning/main.tsp b/specs/relay/provisioning/main.tsp index 4d9d077ae9..cd1836e0db 100644 --- a/specs/relay/provisioning/main.tsp +++ b/specs/relay/provisioning/main.tsp @@ -14,7 +14,6 @@ using SignalWire.AsyncAPI; @server("production", #{ host: "relay.signalwire.com", protocol: "wss", - pathname: "/api/relay/wss", description: "SignalWire Relay WebSocket endpoint.", }) @channel("provisioning") diff --git a/specs/relay/signalwire/main.tsp b/specs/relay/signalwire/main.tsp index c161cbca61..a7f4fea29e 100644 --- a/specs/relay/signalwire/main.tsp +++ b/specs/relay/signalwire/main.tsp @@ -13,7 +13,6 @@ using SignalWire.AsyncAPI; @server("production", #{ host: "relay.signalwire.com", protocol: "wss", - pathname: "/api/relay/wss", description: "SignalWire Relay WebSocket endpoint.", }) @channel("signalwire") diff --git a/specs/relay/tasking/main.tsp b/specs/relay/tasking/main.tsp index 1e53316742..a7127c6e77 100644 --- a/specs/relay/tasking/main.tsp +++ b/specs/relay/tasking/main.tsp @@ -14,7 +14,6 @@ using SignalWire.AsyncAPI; @server("production", #{ host: "relay.signalwire.com", protocol: "wss", - pathname: "/api/relay/wss", description: "SignalWire Relay WebSocket endpoint.", }) @channel("tasking") diff --git a/specs/relay/webrtc/main.tsp b/specs/relay/webrtc/main.tsp index b4ecfc10b5..94231bd869 100644 --- a/specs/relay/webrtc/main.tsp +++ b/specs/relay/webrtc/main.tsp @@ -18,7 +18,6 @@ using SignalWire.AsyncAPI; @server("production", #{ host: "relay.signalwire.com", protocol: "wss", - pathname: "/api/relay/wss", description: "SignalWire Relay WebSocket endpoint.", }) @channel("webrtc") From 15f25b9aefc4874dcf67ccec12b5dd71bb90b895 Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 21:52:22 -0400 Subject: [PATCH 23/25] fix(asyncapi): emit channel address "/" so Fern renders the bare WS root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With address null, Fern composes the channel URL as wss:///, showing a misleading per-service path (e.g. wss://relay.signalwire.com/messaging). Relay multiplexes every service over one root connection and routes by the JSON-RPC method, so emit the root address "/" — Fern then renders wss://relay.signalwire.com/. Verified live for the messaging and calling channels. --- fern/apis/relay/calling.yaml | 2 +- fern/apis/relay/messaging.yaml | 2 +- fern/apis/relay/provisioning.yaml | 2 +- fern/apis/relay/signalwire.yaml | 2 +- fern/apis/relay/tasking.yaml | 2 +- fern/apis/relay/webrtc.yaml | 2 +- specs/emitters/typespec-asyncapi/src/emitter.ts | 6 +++++- .../typespec-asyncapi/test/__snapshots__/calling.yaml | 2 +- specs/emitters/typespec-asyncapi/test/rpc-method.test.ts | 4 ++-- 9 files changed, 14 insertions(+), 10 deletions(-) diff --git a/fern/apis/relay/calling.yaml b/fern/apis/relay/calling.yaml index 3269158c63..18012dbca8 100644 --- a/fern/apis/relay/calling.yaml +++ b/fern/apis/relay/calling.yaml @@ -18,7 +18,7 @@ servers: ws: {} channels: calling: - address: null + address: / title: SignalWire Relay — Calling servers: - $ref: "#/servers/production" diff --git a/fern/apis/relay/messaging.yaml b/fern/apis/relay/messaging.yaml index 7aa3481199..b58d2eee79 100644 --- a/fern/apis/relay/messaging.yaml +++ b/fern/apis/relay/messaging.yaml @@ -18,7 +18,7 @@ servers: ws: {} channels: messaging: - address: null + address: / title: SignalWire Relay — Messaging servers: - $ref: "#/servers/production" diff --git a/fern/apis/relay/provisioning.yaml b/fern/apis/relay/provisioning.yaml index 34d5c6e3f2..bef3cdd4c7 100644 --- a/fern/apis/relay/provisioning.yaml +++ b/fern/apis/relay/provisioning.yaml @@ -21,7 +21,7 @@ servers: ws: {} channels: provisioning: - address: null + address: / title: SignalWire Relay — Connector Provisioning servers: - $ref: "#/servers/production" diff --git a/fern/apis/relay/signalwire.yaml b/fern/apis/relay/signalwire.yaml index 425518384d..5f6aad2e04 100644 --- a/fern/apis/relay/signalwire.yaml +++ b/fern/apis/relay/signalwire.yaml @@ -20,7 +20,7 @@ servers: ws: {} channels: signalwire: - address: null + address: / title: SignalWire Relay — Signalwire (handshake & control) servers: - $ref: "#/servers/production" diff --git a/fern/apis/relay/tasking.yaml b/fern/apis/relay/tasking.yaml index b7a49d9e1b..0dc55b3221 100644 --- a/fern/apis/relay/tasking.yaml +++ b/fern/apis/relay/tasking.yaml @@ -21,7 +21,7 @@ servers: ws: {} channels: tasking: - address: null + address: / title: SignalWire Relay — Tasking servers: - $ref: "#/servers/production" diff --git a/fern/apis/relay/webrtc.yaml b/fern/apis/relay/webrtc.yaml index d1afb16b74..6083549d97 100644 --- a/fern/apis/relay/webrtc.yaml +++ b/fern/apis/relay/webrtc.yaml @@ -25,7 +25,7 @@ servers: ws: {} channels: webrtc: - address: null + address: / title: SignalWire Relay — WebRTC servers: - $ref: "#/servers/production" diff --git a/specs/emitters/typespec-asyncapi/src/emitter.ts b/specs/emitters/typespec-asyncapi/src/emitter.ts index 19b809ba84..632c56d95a 100644 --- a/specs/emitters/typespec-asyncapi/src/emitter.ts +++ b/specs/emitters/typespec-asyncapi/src/emitter.ts @@ -265,7 +265,11 @@ export async function $onEmit(context: EmitContext): Pro servers: { [serverCfg.name]: server }, channels: { [channelId]: { - address: null, + // The Relay WS endpoint is a single root connection (`wss://`); every + // service multiplexes over it and routes by the JSON-RPC `method` in the + // payload, not by a URL path. Emit the root address `"/"` so renderers show + // the bare endpoint instead of treating the channel id as a path segment. + address: "/", title, servers: [{ $ref: `#/servers/${serverCfg.name}` }], messages: target.channelMessages, diff --git a/specs/emitters/typespec-asyncapi/test/__snapshots__/calling.yaml b/specs/emitters/typespec-asyncapi/test/__snapshots__/calling.yaml index 4efa0f69bd..ef342dc8d9 100644 --- a/specs/emitters/typespec-asyncapi/test/__snapshots__/calling.yaml +++ b/specs/emitters/typespec-asyncapi/test/__snapshots__/calling.yaml @@ -14,7 +14,7 @@ servers: ws: {} channels: calling: - address: null + address: / title: Relay Kitchen Sink servers: - $ref: "#/servers/production" diff --git a/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts b/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts index 290040bec2..ce12823247 100644 --- a/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts +++ b/specs/emitters/typespec-asyncapi/test/rpc-method.test.ts @@ -14,9 +14,9 @@ export const SVC = ` `; describe("@channel", () => { - it("emits a single channel with address null", async () => { + it("emits a single channel addressed at the WS root", async () => { const { doc } = await asyncApiFor(SVC); - strictEqual(doc.channels.calling.address, null); + strictEqual(doc.channels.calling.address, "/"); deepStrictEqual(doc.channels.calling.servers, [{ $ref: "#/servers/production" }]); }); }); From 7bfa4601182f32480bdf75bded8f6305f9d175cb Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 22:04:29 -0400 Subject: [PATCH 24/25] fix(ci): build the typespec-asyncapi emitter before compiling relay specs The emitter's dist/ is gitignored and nothing built it in CI, so build:relay failed with import-not-found on ../dist/src/index.js. build:relay now runs the emitter's tsc build first (verified against a clean dist/). --- specs/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/package.json b/specs/package.json index f65e7bd254..ab574a5a2f 100644 --- a/specs/package.json +++ b/specs/package.json @@ -7,7 +7,8 @@ "build:all": "yarn build:api && yarn build:schema && yarn build:relay", "build:api": "yarn build:signalwire-rest && yarn build:compatibility-api", "build:schema": "yarn build:swml-calling && yarn build:swml-messaging", - "build:relay": "yarn build:relay-signalwire && yarn build:relay-calling && yarn build:relay-messaging && yarn build:relay-tasking && yarn build:relay-provisioning && yarn build:relay-webrtc", + "build:relay": "yarn build:relay-emitter && yarn build:relay-signalwire && yarn build:relay-calling && yarn build:relay-messaging && yarn build:relay-tasking && yarn build:relay-provisioning && yarn build:relay-webrtc", + "build:relay-emitter": "cd ./emitters/typespec-asyncapi && yarn build && cd ../..", "build:relay-signalwire": "cd ./relay/signalwire && tsp compile . && cd ../..", "build:relay-calling": "cd ./relay/calling && tsp compile . && cd ../..", "build:relay-messaging": "cd ./relay/messaging && tsp compile . && cd ../..", From a713ec2b0701ac57ef58e87fc3bffb11bcdbe13a Mon Sep 17 00:00:00 2001 From: Devon-White Date: Tue, 16 Jun 2026 22:21:32 -0400 Subject: [PATCH 25/25] feat(apis): split API reference into REST and Relay tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Relay API was rendering in the same flat sidebar as REST. Convert the apis product nav to tabs (matching the compatibility-api pattern): a REST tab and a Relay tab. URLs are preserved — Core stays at /docs/apis/, REST at /docs/apis/rest/..., Relay at /docs/apis/relay/... (tab carries the slug, the API uses skip-slug). Verified live: both tabs render and all existing URLs 200. --- fern/products/apis/apis.yml | 424 +++++++++++++++++------------------- 1 file changed, 197 insertions(+), 227 deletions(-) diff --git a/fern/products/apis/apis.yml b/fern/products/apis/apis.yml index 96795df452..26d62a3668 100644 --- a/fern/products/apis/apis.yml +++ b/fern/products/apis/apis.yml @@ -3,254 +3,224 @@ tabs: display-name: REST icon: "fa-regular fa-server" slug: rest + relay: + display-name: Relay + icon: "fa-regular fa-tower-broadcast" + slug: relay jsonrpc: display-name: JSON-RPC icon: "fa-regular fa-bolt" slug: json-rpc navigation: - # TODO: Restore tabbed navigation when SW APIs can support OpenRPC Format or we introduce async API specs. - # - tab: rest - # layout: - # - api: Calling - # api-name: calling - # - api: Voice - # api-name: voice - # - api: Message - # api-name: message - # - api: Fax - # api-name: fax - # - api: Chat - # api-name: chat - # - api: Video - # api-name: video - # - api: Fabric - # api-name: fabric - # - api: Space - # api-name: space - # - api: Project - # api-name: project - # - api: Datasphere - # api-name: datasphere - # - api: Pubsub - # api-name: pubsub - # - api: Logs - # api-name: logs - - # Legacy/Compatibility - # - api: Compatibility - # api-name: compatibility - - # - tab: jsonrpc - # layout: - # - api: Calling RPC - # api-name: calling-rpc - # slug: calling-rpc - - - section: Core - contents: - - page: Overview - path: ./pages/core/overview.mdx - - page: Authorization - path: ./pages/core/authorization.mdx - - page: Base URL - path: ./pages/core/base-url.mdx - - page: Data formats - path: ./pages/core/data-formats.mdx - - page: Paging - path: ./pages/core/paging.mdx - - page: Error codes - path: ./pages/core/error-codes.mdx - - page: Permissions - path: ./pages/core/permissions.mdx - - api: SignalWire REST API - api-name: signalwire-rest - alphabetized: true - slug: rest - flattened: true + - tab: rest layout: - - section: Calling - skip-slug: true + - section: Core contents: - - calls: - - section: Webhooks - slug: webhooks + - page: Overview + path: ./pages/core/overview.mdx + - page: Authorization + path: ./pages/core/authorization.mdx + - page: Base URL + path: ./pages/core/base-url.mdx + - page: Data formats + path: ./pages/core/data-formats.mdx + - page: Paging + path: ./pages/core/paging.mdx + - page: Error codes + path: ./pages/core/error-codes.mdx + - page: Permissions + path: ./pages/core/permissions.mdx + - api: SignalWire REST API + api-name: signalwire-rest + alphabetized: true + skip-slug: true + flattened: true + layout: + - section: Calling + skip-slug: true + contents: + - calls: + - section: Webhooks + slug: webhooks + contents: + - subpackage_calls.transcribe_status_callback + - subpackage_calls.stream_status_callback + - subpackage_calls.ai_sidecar_callback + - subpackage_calls.ai_sidecar_swaig_tool_webhook + - subpackage_calls.ai_swaig_tool_webhook + - queues + - queueMembers + - recordings + - section: Video contents: - - subpackage_calls.transcribe_status_callback - - subpackage_calls.stream_status_callback - - subpackage_calls.ai_sidecar_callback - - subpackage_calls.ai_sidecar_swaig_tool_webhook - - subpackage_calls.ai_swaig_tool_webhook - - queues - - queueMembers - - recordings - - section: Video + - conferenceTokens + - rooms + - roomRecordings + - roomSessions + - roomTokens + - streams + - videoConferences + - section: Datasphere + skip-slug: true contents: - - conferenceTokens - - rooms - - roomRecordings - - roomSessions - - roomTokens - - streams - - videoConferences - - section: Datasphere - skip-slug: true - contents: - - chunks - - documents - - section: Logs - skip-slug: true - contents: - - conferenceLogs - - faxLogs - - messageLogs - - videoLogs - - voiceLogs - - section: Messaging - skip-slug: true - contents: - - messages: - - section: Webhooks - slug: webhooks + - chunks + - documents + - section: Logs + skip-slug: true + contents: + - conferenceLogs + - faxLogs + - messageLogs + - videoLogs + - voiceLogs + - section: Messaging + skip-slug: true + contents: + - messages: + - section: Webhooks + slug: webhooks + contents: + - subpackage_messages.message_status_callback + - section: Campaign Registry contents: - - subpackage_messages.message_status_callback - - section: Campaign Registry + - section: Brands + referenced-packages: + - campaignRegistryBrands + contents: [] + - section: Campaigns + referenced-packages: + - campaignRegistryCampaigns + contents: [] + - section: Phone Number Assignments + referenced-packages: + - campaignRegistryPhoneNumberAssignments + contents: [] + - section: Webhooks + slug: webhooks + contents: + - subpackage_campaignRegistry.ten_dlc_status_callback + - shortCodes + - section: Phone Number Management + skip-slug: true contents: - - section: Brands + - section: Phone Numbers referenced-packages: - - campaignRegistryBrands + - phoneNumbers + - importedPhoneNumbers + - phoneNumberLookup + - phoneRoutes contents: [] - - section: Campaigns + - section: E911 Addresses referenced-packages: - - campaignRegistryCampaigns + - e911Addresses contents: [] - - section: Phone Number Assignments + - numberGroups + - numberGroupMembership + - verifiedCallerId + - section: Platform + skip-slug: true + contents: + - chatTokens + - section: Domain Applications referenced-packages: - - campaignRegistryPhoneNumberAssignments + - domainApplications + - spaceDomainApplications contents: [] - - section: Webhooks - slug: webhooks - contents: - - subpackage_campaignRegistry.ten_dlc_status_callback - - shortCodes - - section: Phone Number Management - skip-slug: true - contents: - - section: Phone Numbers - referenced-packages: - - phoneNumbers - - importedPhoneNumbers - - phoneNumberLookup - - phoneRoutes - contents: [] - - section: E911 Addresses - referenced-packages: - - e911Addresses - contents: [] - - numberGroups - - numberGroupMembership - - verifiedCallerId - - section: Platform - skip-slug: true - contents: - - chatTokens - - section: Domain Applications - referenced-packages: - - domainApplications - - spaceDomainApplications - contents: [] - - multiFactorAuthentication - - projectTokens - - section: PubSub Tokens - slug: pubsub - referenced-packages: - - pubSubTokens - contents: [] - - section: SIP Profile - referenced-packages: - - sipProfile - contents: [] - - section: Resource Management - skip-slug: true - contents: - - section: Resources - referenced-packages: - - resources - contents: [] - - section: Addresses - referenced-packages: - - addresses - contents: [] - - section: AI Agents - contents: - - aiAgentsCustom - - aiAgentsDialogflow - - callFlows - - conferenceRooms - - section: cXML Applications - slug: cxml-applications - referenced-packages: - - cXmlApplications - contents: [] - - section: cXML Scripts - slug: cxml-scripts - referenced-packages: - - cXmlScripts - contents: [] - - section: cXML Webhook - slug: cxml-webhook - referenced-packages: - - cXmlWebhook - contents: [] - - section: FreeSWITCH Connector - slug: freeswitch-connector - referenced-packages: - - freeSwitchConnector - contents: [] - - relayApplication - - section: SIP Credentials - referenced-packages: - - sipCredentials - contents: [] - - section: SIP Endpoints (Legacy) - slug: sip-endpoints - availability: deprecated - hidden: true - referenced-packages: - - sipEndpointsLegacy - contents: [] - - section: SIP Gateway - referenced-packages: - - sipGateway - contents: [] - - section: Subscribers - referenced-packages: - - subscribers - contents: - - section: Subscriber SIP Credentials - slug: sip-credentials + - multiFactorAuthentication + - projectTokens + - section: PubSub Tokens + slug: pubsub referenced-packages: - - subscriberSipCredentials + - pubSubTokens contents: [] - - section: Subscriber Tokens - slug: tokens + - section: SIP Profile referenced-packages: - - subscriberTokens + - sipProfile contents: [] - - section: SWML Scripts - referenced-packages: - - swmlScripts - contents: [] - - section: SWML Webhook - slug: swml-webhook - referenced-packages: - - swmlWebhook + - section: Resource Management + skip-slug: true contents: - - section: Webhooks - slug: webhooks + - section: Resources + referenced-packages: + - resources + contents: [] + - section: Addresses + referenced-packages: + - addresses + contents: [] + - section: AI Agents contents: - - subpackage_swmlWebhook.inbound_call_webhook - - subpackage_swmlWebhook.inbound_message_webhook - - api: SignalWire Relay - api-name: relay - slug: relay + - aiAgentsCustom + - aiAgentsDialogflow + - callFlows + - conferenceRooms + - section: cXML Applications + slug: cxml-applications + referenced-packages: + - cXmlApplications + contents: [] + - section: cXML Scripts + slug: cxml-scripts + referenced-packages: + - cXmlScripts + contents: [] + - section: cXML Webhook + slug: cxml-webhook + referenced-packages: + - cXmlWebhook + contents: [] + - section: FreeSWITCH Connector + slug: freeswitch-connector + referenced-packages: + - freeSwitchConnector + contents: [] + - relayApplication + - section: SIP Credentials + referenced-packages: + - sipCredentials + contents: [] + - section: SIP Endpoints (Legacy) + slug: sip-endpoints + availability: deprecated + hidden: true + referenced-packages: + - sipEndpointsLegacy + contents: [] + - section: SIP Gateway + referenced-packages: + - sipGateway + contents: [] + - section: Subscribers + referenced-packages: + - subscribers + contents: + - section: Subscriber SIP Credentials + slug: sip-credentials + referenced-packages: + - subscriberSipCredentials + contents: [] + - section: Subscriber Tokens + slug: tokens + referenced-packages: + - subscriberTokens + contents: [] + - section: SWML Scripts + referenced-packages: + - swmlScripts + contents: [] + - section: SWML Webhook + slug: swml-webhook + referenced-packages: + - swmlWebhook + contents: + - section: Webhooks + slug: webhooks + contents: + - subpackage_swmlWebhook.inbound_call_webhook + - subpackage_swmlWebhook.inbound_message_webhook + - tab: relay + layout: + - api: SignalWire Relay + api-name: relay + skip-slug: true