diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..12ff1e64 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,59 @@ +# Dependencies +node_modules/ +**/node_modules/ + +# Build outputs +dist/ +**/dist/ + +# Git +.git/ +.gitignore + +# CI/CD +.github/ +.gitlab-ci.yml +.travis.yml + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Testing +test/ +tests/ +__tests__/ +*.test.js +*.spec.js +coverage/ +.nyc_output/ + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Environment +.env +.env.* + +# Temporary files +*.tmp +*.temp +.cache/ + +# Examples and scripts +examples/ +bin/ + +# Other packages (we only need mcp-server) +packages/*/ +!packages/mcp-server/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 476c94fd..af17f338 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,14 @@ name: CI on: push: - branches-ignore: - - 'generated' - - 'codegen/**' - - 'integrated/**' - - 'stl-preview-head/**' - - 'stl-preview-base/**' + branches: + - '**' + - '!integrated/**' + - '!stl-preview-head/**' + - '!stl-preview-base/**' + - '!generated' + - '!codegen/**' + - 'codegen/stl/**' pull_request: branches-ignore: - 'stl-preview-head/**' @@ -17,9 +19,9 @@ jobs: timeout-minutes: 10 name: lint runs-on: ${{ github.repository == 'stainless-sdks/writer-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Node uses: actions/setup-node@v4 @@ -36,12 +38,12 @@ jobs: timeout-minutes: 5 name: build runs-on: ${{ github.repository == 'stainless-sdks/writer-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') permissions: contents: read id-token: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Node uses: actions/setup-node@v4 @@ -55,14 +57,18 @@ jobs: run: ./scripts/build - name: Get GitHub OIDC Token - if: github.repository == 'stainless-sdks/writer-typescript' + if: |- + github.repository == 'stainless-sdks/writer-typescript' && + !startsWith(github.ref, 'refs/heads/stl/') id: github-oidc - uses: actions/github-script@v6 + uses: actions/github-script@v8 with: script: core.setOutput('github_token', await core.getIDToken()); - name: Upload tarball - if: github.repository == 'stainless-sdks/writer-typescript' + if: |- + github.repository == 'stainless-sdks/writer-typescript' && + !startsWith(github.ref, 'refs/heads/stl/') env: URL: https://pkg.stainless.com/s AUTH: ${{ steps.github-oidc.outputs.github_token }} @@ -70,7 +76,9 @@ jobs: run: ./scripts/utils/upload-artifact.sh - name: Upload MCP Server tarball - if: github.repository == 'stainless-sdks/writer-typescript' + if: |- + github.repository == 'stainless-sdks/writer-typescript' && + !startsWith(github.ref, 'refs/heads/stl/') env: URL: https://pkg.stainless.com/s?subpackage=mcp-server AUTH: ${{ steps.github-oidc.outputs.github_token }} @@ -83,7 +91,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/writer-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Node uses: actions/setup-node@v4 diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 27dab6ba..e977e568 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -20,7 +20,7 @@ jobs: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Node uses: actions/setup-node@v3 @@ -33,13 +33,14 @@ jobs: - name: Publish to NPM run: | - if [ -n "${{ github.event.inputs.path }}" ]; then - PATHS_RELEASED='[\"${{ github.event.inputs.path }}\"]' + if [ -n "$INPUT_PATH" ]; then + PATHS_RELEASED="[\"$INPUT_PATH\"]" else PATHS_RELEASED='[\".\", \"packages/mcp-server\"]' fi yarn tsn scripts/publish-packages.ts "{ \"paths_released\": \"$PATHS_RELEASED\" }" env: + INPUT_PATH: ${{ github.event.inputs.path }} NPM_TOKEN: ${{ secrets.WRITER_NPM_TOKEN || secrets.NPM_TOKEN }} - name: Upload MCP Server DXT GitHub release asset diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index ec1f9592..897f1dab 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'writer/writer-node' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Check release environment run: | diff --git a/.gitignore b/.gitignore index d62bea50..b7d4f6b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .prism.log +.stdy.log node_modules yarn-error.log codegen.log diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f42cb631..6cb00e07 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.3.3-rc.1" + ".": "2.4.0-rc.1" } diff --git a/.stats.yml b/.stats.yml index 33ea12a4..a774d25b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 33 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-ea6ec4b34f6b7fdecc564f59b2e31482eee05830bf8dc1f389461b158de1548e.yml openapi_spec_hash: ea89c1faed473908be2740efe6da255f -config_hash: 886645f89dc98f04b8931eaf02854e5f +config_hash: 247c2ce23a36ef7446d356308329c87b diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ac567af..9908edcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,135 @@ # Changelog +## 2.4.0-rc.1 (2026-03-31) + +Full Changelog: [v2.3.3-rc.1...v2.4.0-rc.1](https://github.com/writer/writer-node/compare/v2.3.3-rc.1...v2.4.0-rc.1) + +### ⚠ BREAKING CHANGES + +* **mcp:** remove deprecated tool schemes +* **mcp:** **Migration:** To migrate, simply modify the command used to invoke the MCP server. Currently, the only supported tool scheme is code mode. Now, starting the server with just `node /path/to/mcp/server` or `npx package-name` will invoke code tools: changing your command to one of these is likely all you will need to do. + +### Features + +* **api:** manual updates ([0012f4c](https://github.com/writer/writer-node/commit/0012f4ccc4acda716ef0559edc28f14af57911fa)) +* **api:** manual updates ([d9dd552](https://github.com/writer/writer-node/commit/d9dd55266323cb77cb8d4b88b6cc3ff3c7a91a00)) +* **mcp:** add an option to disable code tool ([fa96ea5](https://github.com/writer/writer-node/commit/fa96ea5f1c6c863ee94918f58f532ecf7d4ec8c3)) +* **mcp:** add initial server instructions ([d52d098](https://github.com/writer/writer-node/commit/d52d098bfb5163f020f6bf7a0e45e1ea911ca962)) +* **mcp:** add typescript check to code execution tool ([7ecde82](https://github.com/writer/writer-node/commit/7ecde8204a6475dc8106c4c90d1e90030260e465)) +* **mcp:** handle code mode calls in the Stainless API ([a705a1b](https://github.com/writer/writer-node/commit/a705a1b8ed30c17464d0a3700c828796550ed8d6)) +* **mcp:** return logs on code tool errors ([76551d3](https://github.com/writer/writer-node/commit/76551d345f33dbe6f6ea1bc437b691bcf3690664)) + + +### Bug Fixes + +* **client:** avoid memory leak with abort signals ([325c9ca](https://github.com/writer/writer-node/commit/325c9ca844c64083fb0e09f83c291cc17ef2c61d)) +* **client:** avoid removing abort listener too early ([8a3f29d](https://github.com/writer/writer-node/commit/8a3f29dbe0af0d126d7227b500c551204c916773)) +* **client:** preserve URL params already embedded in path ([c650393](https://github.com/writer/writer-node/commit/c650393f8ea5e1ceebb4c903f69b78d85572279c)) +* **docs/contributing:** correct pnpm link command ([edcceda](https://github.com/writer/writer-node/commit/edcceda07df649e2a6dfd80d1b220b8e17a1b3ea)) +* **docs:** fix mcp installation instructions for remote servers ([5248a83](https://github.com/writer/writer-node/commit/5248a83066782fedd9c97b4b6f78f476c2535d59)) +* fix request delays for retrying to be more respectful of high requested delays ([b897aab](https://github.com/writer/writer-node/commit/b897aab23f199ac46e4fe6092fb3c1cbc0cbdb7d)) +* **mcp:** add client instantiation options to code tool ([761c3b1](https://github.com/writer/writer-node/commit/761c3b1ccc0eacc6b7ed7782fd1abddefc9020d7)) +* **mcp:** allow falling back for required env variables ([7e8d2fc](https://github.com/writer/writer-node/commit/7e8d2fc3c1012493584dab21e31c36e0f458b3fd)) +* **mcp:** correct code tool API endpoint ([6cd6857](https://github.com/writer/writer-node/commit/6cd6857db708dc98d8ee76b3b324c1a123de222c)) +* **mcp:** correct code tool api output types ([9b088bc](https://github.com/writer/writer-node/commit/9b088bc1c7705c55bfb6f448f85520171a0ae820)) +* **mcp:** fix env parsing ([a8328e7](https://github.com/writer/writer-node/commit/a8328e7d7dc8abae15fe7c8bb653e206a14c51cd)) +* **mcp:** fix options parsing ([6690d22](https://github.com/writer/writer-node/commit/6690d22af5aac3cdc2ffd883010b06989e6a4f92)) +* **mcp:** initialize SDK lazily to avoid failing the connection on init errors ([49df35f](https://github.com/writer/writer-node/commit/49df35fc62598b6ccc91e4e45cf5afa530c4f19d)) +* **mcp:** pass base url to code tool ([59063c0](https://github.com/writer/writer-node/commit/59063c0bea7c292d3bc080b32b8bfd92316446e5)) +* **mcp:** return correct lines on typescript errors ([c7e0c9b](https://github.com/writer/writer-node/commit/c7e0c9bffca2893dd36715811ba3b6001fa5544d)) +* **mcp:** return tool execution error on api error ([b62c6ba](https://github.com/writer/writer-node/commit/b62c6ba071ea931ae3af7b996b084403be6b0777)) +* **mcp:** update code tool prompt ([382ffb1](https://github.com/writer/writer-node/commit/382ffb1f2535c072fc1def53a0eb1e1456b609b9)) +* **mcp:** update prompt ([dddad93](https://github.com/writer/writer-node/commit/dddad93a9b1c3ab92f1d52a5bdbe31af46939d8e)) + + +### Chores + +* break long lines in snippets into multiline ([58a37e3](https://github.com/writer/writer-node/commit/58a37e30f60599207bdc756ba723d25686770d4a)) +* **ci:** escape input path in publish-npm workflow ([5a5bf6b](https://github.com/writer/writer-node/commit/5a5bf6b7c85abbc390ed796347b77855d8f5a461)) +* **ci:** skip lint on metadata-only changes ([01c3e15](https://github.com/writer/writer-node/commit/01c3e15869a06fd6247270c6d6f2bc77ebbefd26)) +* **ci:** skip uploading artifacts on stainless-internal branches ([51bf9fc](https://github.com/writer/writer-node/commit/51bf9fc60afb36994efbac0e9bc04e39a2b5b163)) +* **ci:** upgrade `actions/github-script` ([c84c7b9](https://github.com/writer/writer-node/commit/c84c7b9ebc03fcee7411719aa5fd783cbdcdc38c)) +* **client:** do not parse responses with empty content-length ([6802838](https://github.com/writer/writer-node/commit/6802838d8eab0322f78ba8f2612aa282687715c9)) +* **client:** restructure abort controller binding ([3204f51](https://github.com/writer/writer-node/commit/3204f51e093c5f1390cef993d2a377b778edbe1d)) +* fix typo in descriptions ([2f6c30f](https://github.com/writer/writer-node/commit/2f6c30f7e0a9f56641eeb077fdda5abf79c9e11c)) +* **internal/client:** fix form-urlencoded requests ([69d4765](https://github.com/writer/writer-node/commit/69d476512384faa4ba6f068921c0aacb2fcd8c54)) +* **internal:** add health check to MCP server when running in HTTP mode ([cbb4461](https://github.com/writer/writer-node/commit/cbb44616abd6e800f622b8e2e1a406f5da72c85b)) +* **internal:** allow basic filtering of methods allowed for MCP code mode ([a07d4aa](https://github.com/writer/writer-node/commit/a07d4aaa8dcf25a61de3721f0203b26fac082ea0)) +* **internal:** allow setting x-stainless-api-key header on mcp server requests ([ec7acc0](https://github.com/writer/writer-node/commit/ec7acc08393eb40ab704817b28ac441a25747ff0)) +* **internal:** always generate MCP server dockerfiles and upgrade associated dependencies ([dc3398b](https://github.com/writer/writer-node/commit/dc3398b32e6139cb085ec0b1291e068d71cd35cf)) +* **internal:** avoid type checking errors with ts-reset ([d32477d](https://github.com/writer/writer-node/commit/d32477dbfdf57bcd09bc40722698fad93b31fbde)) +* **internal:** bump @modelcontextprotocol/sdk, @hono/node-server, and minimatch ([85c4851](https://github.com/writer/writer-node/commit/85c4851f99e0cf1c918e6ffaec248dddffcecb77)) +* **internal:** cache fetch instruction calls in MCP server ([6745b11](https://github.com/writer/writer-node/commit/6745b119635f4fb1adc214136afb028beaf617e7)) +* **internal:** codegen related update ([757eeae](https://github.com/writer/writer-node/commit/757eeae33e139f642cdb432b600c7c6887e65f25)) +* **internal:** codegen related update ([56fc7c7](https://github.com/writer/writer-node/commit/56fc7c7870a26529558d4ae70d238d201e2015a0)) +* **internal:** codegen related update ([e12403b](https://github.com/writer/writer-node/commit/e12403bc7c73a4ae453c9bfbab7a5423a91497e7)) +* **internal:** codegen related update ([1be7281](https://github.com/writer/writer-node/commit/1be72815af2ad70591cfc3a0c63bbf318d5dc405)) +* **internal:** codegen related update ([4807744](https://github.com/writer/writer-node/commit/4807744a4ec789fa0e283d33fa77b50a9f9880fc)) +* **internal:** codegen related update ([e56fb21](https://github.com/writer/writer-node/commit/e56fb21f84394e9f58fadcfaa86b1e52ab8f4673)) +* **internal:** codegen related update ([c549df2](https://github.com/writer/writer-node/commit/c549df28775dfec7ec1367b854068d9d845b851d)) +* **internal:** codegen related update ([220705b](https://github.com/writer/writer-node/commit/220705bccf50e9c1a2c0c087a697e274a64e090c)) +* **internal:** codegen related update ([cf8a0ac](https://github.com/writer/writer-node/commit/cf8a0ac27f7a4d3c2f09435d537a0e9b6d5f7a95)) +* **internal:** codegen related update ([5958ab9](https://github.com/writer/writer-node/commit/5958ab937e37a09fe325dfc509537080825573e0)) +* **internal:** codegen related update ([30d1025](https://github.com/writer/writer-node/commit/30d10259e89332dd11656dbefbee651eeb506706)) +* **internal:** fix MCP Dockerfiles so they can be built without buildkit ([2e39b4f](https://github.com/writer/writer-node/commit/2e39b4fa5f4ee2554f2185a0466933e7414eccec)) +* **internal:** fix MCP Dockerfiles so they can be built without buildkit ([2aa7960](https://github.com/writer/writer-node/commit/2aa7960f102438b8b9a312bd32a1ddb1cb67b790)) +* **internal:** fix MCP server TS errors that occur with required client options ([64520aa](https://github.com/writer/writer-node/commit/64520aa474771c608f1a575d0cd9ae36bdca033c)) +* **internal:** fix pagination internals not accepting option promises ([302c9ab](https://github.com/writer/writer-node/commit/302c9ab7a966a1cc9e3f46a376057ab0d479ee83)) +* **internal:** improve layout of generated MCP server files ([da6f525](https://github.com/writer/writer-node/commit/da6f525158745577a74376c352bb967515e0f902)) +* **internal:** improve local docs search for MCP servers ([bf2441f](https://github.com/writer/writer-node/commit/bf2441f89a2d2642dcfb5978938518a3dba7ad15)) +* **internal:** improve local docs search for MCP servers ([d1710b0](https://github.com/writer/writer-node/commit/d1710b03fc4b42bdc8cab7dc288bea9eb084af71)) +* **internal:** make generated MCP servers compatible with Cloudflare worker environments ([d8af505](https://github.com/writer/writer-node/commit/d8af505573509dfa5541cb49021e7279850cd457)) +* **internal:** make MCP code execution location configurable via a flag ([8b197f0](https://github.com/writer/writer-node/commit/8b197f0e9406fc38ac7b318836484982b6b895a7)) +* **internal:** move stringifyQuery implementation to internal function ([08e2c09](https://github.com/writer/writer-node/commit/08e2c09ebe73f6c62b9d9b531662ff7827c7aa64)) +* **internal:** refactor flag parsing for MCP servers and add debug flag ([5291a9f](https://github.com/writer/writer-node/commit/5291a9f086f73589f86a3a6bd604e6686e961f13)) +* **internal:** support custom-instructions-path flag in MCP servers ([8d2f3d0](https://github.com/writer/writer-node/commit/8d2f3d0fae360c5d3ae00fe48376e570e1904731)) +* **internal:** support local docs search in MCP servers ([f479ec6](https://github.com/writer/writer-node/commit/f479ec6f4141d7c59cf0dd44dff9fdf38debd2f4)) +* **internal:** support oauth authorization code flow for MCP servers ([96ba350](https://github.com/writer/writer-node/commit/96ba350f38776d9622a9cc4124ebca788a5fa9e1)) +* **internal:** support x-stainless-mcp-client-envs header in MCP servers ([29ec150](https://github.com/writer/writer-node/commit/29ec150c6869179932be3ac434fc3596a2883387)) +* **internal:** support x-stainless-mcp-client-permissions headers in MCP servers ([5cacf5f](https://github.com/writer/writer-node/commit/5cacf5ff4671398607b6dcb56414c43a20bd77b8)) +* **internal:** switch MCP servers to use pino for logging ([a2693a7](https://github.com/writer/writer-node/commit/a2693a734be31a23d6f3156fd93c33a0a272a55c)) +* **internal:** tweak CI branches ([700bb90](https://github.com/writer/writer-node/commit/700bb904c404df16bd4e6c7635c7ab4de1ced843)) +* **internal:** update `actions/checkout` version ([ac62b27](https://github.com/writer/writer-node/commit/ac62b278b1aa8635c85e4b7f80dfd461c76bbde9)) +* **internal:** update dependencies to address dependabot vulnerabilities ([96b9468](https://github.com/writer/writer-node/commit/96b9468dddad4693959b59bd4dfd0c64e6948def)) +* **internal:** update gitignore ([a8a8b44](https://github.com/writer/writer-node/commit/a8a8b44016b44b27e31cd36b7d4c3d726c0994f5)) +* **internal:** update lock file ([8872b80](https://github.com/writer/writer-node/commit/8872b80dddc9edf1d45de5387acce00b13e6010c)) +* **internal:** update multipart form array serialization ([5be2c2b](https://github.com/writer/writer-node/commit/5be2c2beea67491dcf0fe54ec757a049daa5e05b)) +* **internal:** upgrade @modelcontextprotocol/sdk and hono ([e62fb6c](https://github.com/writer/writer-node/commit/e62fb6c36372a4ece5574a2075248213641f9a50)) +* **internal:** upgrade babel, qs, js-yaml ([5717a02](https://github.com/writer/writer-node/commit/5717a02433ce869ff16eddce1a30b892667b37d5)) +* **internal:** upgrade eslint ([4ae73b1](https://github.com/writer/writer-node/commit/4ae73b119027a678183ab79ddef25703584f63b7)) +* **internal:** use x-stainless-mcp-client-envs header for MCP remote code tool calls ([368815e](https://github.com/writer/writer-node/commit/368815effa9872d7cfb68b6b59fc2a3c292d3642)) +* **mcp-server:** add support for session id, forward client info ([9fa5781](https://github.com/writer/writer-node/commit/9fa5781db92bbc1d576f0a6e2224368e9c526137)) +* **mcp-server:** improve instructions ([50343c5](https://github.com/writer/writer-node/commit/50343c59ca40bf64eb6d0fc315af953bced30f97)) +* **mcp-server:** return access instructions for 404 without API key ([30ceb2d](https://github.com/writer/writer-node/commit/30ceb2d5135fd4d2a17ba9ab6041d32573405b60)) +* **mcp:** add intent param to execute tool ([c988c65](https://github.com/writer/writer-node/commit/c988c65c5ac29240540265630b0b2c9a565caa94)) +* **mcp:** correctly update version in sync with sdk ([b93d491](https://github.com/writer/writer-node/commit/b93d491022f90f2f6b92bab112676e5df18bf4c2)) +* **mcp:** forward STAINLESS_API_KEY to docs search endpoint ([dde1184](https://github.com/writer/writer-node/commit/dde11849b78e009db7128c811b0f511f442801ae)) +* **mcp:** pass intent param to execute handler ([58fa431](https://github.com/writer/writer-node/commit/58fa4314badfa5ba2cd2cf148ffb46227d965389)) +* **mcp:** remove deprecated tool schemes ([e376d65](https://github.com/writer/writer-node/commit/e376d65638ff29ccef3339c53b61c6207a3c08d2)) +* **mcp:** up tsconfig lib version to es2022 ([cb421df](https://github.com/writer/writer-node/commit/cb421dfbb23c198f7bd3b39d6555c6e22ec0a200)) +* **mcp:** update lockfile ([8d1106e](https://github.com/writer/writer-node/commit/8d1106e94062b4610f8e0333761f82749c0bbc9f)) +* **mcp:** upgrade dependencies ([5766d6c](https://github.com/writer/writer-node/commit/5766d6c58f52a74fcfc718fa92e46a4a0e0103d7)) +* **test:** do not count install time for mock server timeout ([a345373](https://github.com/writer/writer-node/commit/a3453738634b2bcccde87fd0e9736eb1356ee45f)) +* **tests:** bump steady to v0.19.4 ([dad22a4](https://github.com/writer/writer-node/commit/dad22a4cd08a9aaedc076023093519f9b4813d87)) +* **tests:** bump steady to v0.19.5 ([b51c69d](https://github.com/writer/writer-node/commit/b51c69df9ec75a75a86560ddfa23ecb908dd5ff7)) +* **tests:** bump steady to v0.19.6 ([241c42d](https://github.com/writer/writer-node/commit/241c42d876331b92dee01b704d1eae1c0587f8f4)) +* **tests:** bump steady to v0.19.7 ([518baf9](https://github.com/writer/writer-node/commit/518baf98c314c1fe39c2d44e95863212f881f8b2)) +* **tests:** bump steady to v0.20.1 ([32f3fb9](https://github.com/writer/writer-node/commit/32f3fb9a4eebc6658ac077164f2eee9e21ddad00)) +* **tests:** bump steady to v0.20.2 ([b54c180](https://github.com/writer/writer-node/commit/b54c18011c29d415e55f7ccad2bfb0be46e3ba87)) +* update mock server docs ([b21d0a5](https://github.com/writer/writer-node/commit/b21d0a594a1ce7a8bf9db8f67af853f1dbefc16f)) +* update placeholder string ([95ae007](https://github.com/writer/writer-node/commit/95ae007e055d87d31ae11cbb2163ab5e882c20d5)) +* use latest @modelcontextprotocol/sdk ([08d8625](https://github.com/writer/writer-node/commit/08d8625b8b8a46b8b0f8bb92c6ee57be5b90a780)) + + +### Documentation + +* prominently feature MCP server setup in root SDK readmes ([723e616](https://github.com/writer/writer-node/commit/723e616cc53d9a26740f5de5b72a1ec9e0313949)) + + +### Refactors + +* **tests:** switch from prism to steady ([5d62789](https://github.com/writer/writer-node/commit/5d6278939d566e78b574a04a11fc53659b29f5e1)) + ## 2.3.3-rc.1 (2025-12-01) Full Changelog: [v2.3.2...v2.3.3-rc.1](https://github.com/writer/writer-node/compare/v2.3.2...v2.3.3-rc.1) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2f4b9b0..cfb8ff7b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,15 +60,15 @@ $ yarn link writer-sdk # With pnpm $ pnpm link --global $ cd ../my-package -$ pnpm link -—global writer-sdk +$ pnpm link --global writer-sdk ``` ## Running tests -Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. +Most tests require you to [set up a mock server](https://github.com/dgellow/steady) against the OpenAPI spec to run the tests. ```sh -$ npx prism mock path/to/your/openapi.yml +$ ./scripts/mock ``` ```sh diff --git a/LICENSE b/LICENSE index 38b16626..a1e82cd6 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Writer + Copyright 2026 Writer Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index e1f246dc..a4d28040 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,15 @@ The REST API documentation can be found on [dev.writer.com](https://dev.writer.c It is generated with [Stainless](https://www.stainless.com/). +## MCP Server + +Use the Writer MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. + +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=writer-sdk-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIndyaXRlci1zZGstbWNwIl0sImVudiI6eyJXUklURVJfQVBJX0tFWSI6Ik15IEFQSSBLZXkifX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22writer-sdk-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22writer-sdk-mcp%22%5D%2C%22env%22%3A%7B%22WRITER_API_KEY%22%3A%22My%20API%20Key%22%7D%7D) + +> Note: You may need to set environment variables in your MCP client. + ## Installation ```sh @@ -211,7 +220,10 @@ a subclass of `APIError` will be thrown. ```ts const chatCompletion = await client.chat - .chat({ messages: [{ content: 'Write a haiku about programming', role: 'user' }], model: 'palmyra-x5' }) + .chat({ + messages: [{ content: 'Write a haiku about programming', role: 'user' }], + model: 'palmyra-x5', + }) .catch(async (err) => { if (err instanceof Writer.APIError) { console.log(err.status); // 400 @@ -238,7 +250,9 @@ Error codes are as follows: ### Retries -The library automatically retries certain errors two times by default, with a short exponential backoff. Connection errors, `408 Request Timeout`, `409 Conflict`, `429 Rate Limit`, and `>=500 Internal errors` are all retried by default. +Certain errors will be automatically retried 7 times by default, with a short exponential backoff. +Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, +429 Rate Limit, and >=500 Internal errors will all be retried by default. You can use the `maxRetries` option to configure or disable this: @@ -323,13 +337,19 @@ Unlike `.asResponse()` this method consumes the body, returning once it is parse const client = new Writer(); const response = await client.chat - .chat({ messages: [{ content: 'Write a haiku about programming', role: 'user' }], model: 'palmyra-x5' }) + .chat({ + messages: [{ content: 'Write a haiku about programming', role: 'user' }], + model: 'palmyra-x5', + }) .asResponse(); console.log(response.headers.get('X-My-Header')); console.log(response.statusText); // access the underlying Response object const { data: chatCompletion, response: raw } = await client.chat - .chat({ messages: [{ content: 'Write a haiku about programming', role: 'user' }], model: 'palmyra-x5' }) + .chat({ + messages: [{ content: 'Write a haiku about programming', role: 'user' }], + model: 'palmyra-x5', + }) .withResponse(); console.log(raw.headers.get('X-My-Header')); console.log(chatCompletion.id); diff --git a/package.json b/package.json index 33f406b8..2f21e3bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "writer-sdk", - "version": "2.3.3-rc.1", + "version": "2.4.0-rc.1", "description": "The official TypeScript library for the Writer API", "author": "Writer ", "types": "dist/index.d.ts", @@ -34,7 +34,7 @@ "@types/node": "^20.17.24", "@typescript-eslint/eslint-plugin": "8.31.1", "@typescript-eslint/parser": "8.31.1", - "eslint": "^9.20.1", + "eslint": "^9.39.1", "eslint-plugin-prettier": "^5.4.1", "eslint-plugin-unused-imports": "^4.1.4", "fast-check": "^3.23.1", @@ -55,6 +55,17 @@ "bin": { "writer-sdk": "bin/cli" }, + "overrides": { + "minimatch": "^9.0.5" + }, + "pnpm": { + "overrides": { + "minimatch": "^9.0.5" + } + }, + "resolutions": { + "minimatch": "^9.0.5" + }, "exports": { ".": { "import": "./dist/index.mjs", diff --git a/packages/mcp-server/Dockerfile b/packages/mcp-server/Dockerfile new file mode 100644 index 00000000..956f14da --- /dev/null +++ b/packages/mcp-server/Dockerfile @@ -0,0 +1,76 @@ +# Dockerfile for Writer MCP Server +# +# This Dockerfile builds a Docker image for the MCP Server. +# +# To build the image locally: +# docker build -f packages/mcp-server/Dockerfile -t writer-sdk-mcp:local . +# +# To run the image: +# docker run -i writer-sdk-mcp:local [OPTIONS] +# +# Common options: +# --tool= Include specific tools +# --resource= Include tools for specific resources +# --operation=read|write Filter by operation type +# --client= Set client compatibility (e.g., claude, cursor) +# --transport= Set transport type (stdio or http) +# +# For a full list of options: +# docker run -i writer-sdk-mcp:local --help +# +# Note: The MCP server uses stdio transport by default. Docker's -i flag +# enables interactive mode, allowing the container to communicate over stdin/stdout. + +# Build stage +FROM node:24-alpine AS builder + +# Install bash for build script +RUN apk add --no-cache bash openssl + +# Set working directory +WORKDIR /build + +# Copy entire repository +COPY . . + +# Install all dependencies and build everything +RUN yarn install --frozen-lockfile && \ + yarn build + +FROM denoland/deno:alpine-2.7.1 + +# Install node and npm +RUN apk add --no-cache nodejs npm + +ENV LD_LIBRARY_PATH=/usr/lib:/usr/local/lib + +# Add non-root user +RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001 + +# Set working directory +WORKDIR /app + +# Copy the built mcp-server dist directory +COPY --from=builder /build/packages/mcp-server/dist ./ + +# Copy node_modules from mcp-server (includes all production deps) +COPY --from=builder /build/packages/mcp-server/node_modules ./node_modules + +# Copy the built writer-sdk into node_modules +COPY --from=builder /build/dist ./node_modules/writer-sdk + +# Change ownership to nodejs user +RUN chown -R nodejs:nodejs /app +RUN chown -R nodejs:nodejs /deno-dir + +# Switch to non-root user +USER nodejs + +# The MCP server uses stdio transport by default +# No exposed ports needed for stdio communication + +# Set the entrypoint to the MCP server +ENTRYPOINT ["node", "index.js"] + +# Allow passing arguments to the MCP server +CMD [] diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index b212d1a2..974b8282 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -25,7 +25,7 @@ For clients with a configuration JSON, it might look something like this: "mcpServers": { "writer_sdk_api": { "command": "npx", - "args": ["-y", "writer-sdk-mcp", "--client=claude", "--tools=dynamic"], + "args": ["-y", "writer-sdk-mcp"], "env": { "WRITER_API_KEY": "My API Key" } @@ -39,14 +39,14 @@ For clients with a configuration JSON, it might look something like this: If you use Cursor, you can install the MCP server by using the button below. You will need to set your environment variables in Cursor's `mcp.json`, which can be found in Cursor Settings > Tools & MCP > New MCP Server. -[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=writer-sdk-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIndyaXRlci1zZGstbWNwIl0sImVudiI6eyJXUklURVJfQVBJX0tFWSI6IlNldCB5b3VyIFdSSVRFUl9BUElfS0VZIGhlcmUuIn19) +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=writer-sdk-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIndyaXRlci1zZGstbWNwIl0sImVudiI6eyJXUklURVJfQVBJX0tFWSI6Ik15IEFQSSBLZXkifX0) ### VS Code If you use MCP, you can install the MCP server by clicking the link below. You will need to set your environment variables in VS Code's `mcp.json`, which can be found via Command Palette > MCP: Open User Configuration. -[Open VS Code](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22writer-sdk-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22writer-sdk-mcp%22%5D%2C%22env%22%3A%7B%22WRITER_API_KEY%22%3A%22Set%20your%20WRITER_API_KEY%20here.%22%7D%7D) +[Open VS Code](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22writer-sdk-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22writer-sdk-mcp%22%5D%2C%22env%22%3A%7B%22WRITER_API_KEY%22%3A%22My%20API%20Key%22%7D%7D) ### Claude Code @@ -54,113 +54,25 @@ If you use Claude Code, you can install the MCP server by running the command be environment variables in Claude Code's `.claude.json`, which can be found in your home directory. ``` -claude mcp add --transport stdio writer_sdk_api --env WRITER_API_KEY="Your WRITER_API_KEY here." -- npx -y writer-sdk-mcp +claude mcp add writer_sdk_mcp_api --env WRITER_API_KEY="My API Key" -- npx -y writer-sdk-mcp ``` -## Exposing endpoints to your MCP Client +## Code Mode -There are three ways to expose endpoints as tools in the MCP server: +This MCP server is built on the "Code Mode" tool scheme. In this MCP Server, +your agent will write code against the TypeScript SDK, which will then be executed in an +isolated sandbox. To accomplish this, the server will expose two tools to your agent: -1. Exposing one tool per endpoint, and filtering as necessary -2. Exposing a set of tools to dynamically discover and invoke endpoints from the API -3. Exposing a docs search tool and a code execution tool, allowing the client to write code to be executed against the TypeScript client +- The first tool is a docs search tool, which can be used to generically query for + documentation about your API/SDK. -### Filtering endpoints and tools +- The second tool is a code tool, where the agent can write code against the TypeScript SDK. + The code will be executed in a sandbox environment without web or filesystem access. Then, + anything the code returns or prints will be returned to the agent as the result of the + tool call. -You can run the package on the command line to discover and filter the set of tools that are exposed by the -MCP Server. This can be helpful for large APIs where including all endpoints at once is too much for your AI's -context window. - -You can filter by multiple aspects: - -- `--tool` includes a specific tool by name -- `--resource` includes all tools under a specific resource, and can have wildcards, e.g. `my.resource*` -- `--operation` includes just read (get/list) or just write operations - -### Dynamic tools - -If you specify `--tools=dynamic` to the MCP server, instead of exposing one tool per endpoint in the API, it will -expose the following tools: - -1. `list_api_endpoints` - Discovers available endpoints, with optional filtering by search query -2. `get_api_endpoint_schema` - Gets detailed schema information for a specific endpoint -3. `invoke_api_endpoint` - Executes any endpoint with the appropriate parameters - -This allows you to have the full set of API endpoints available to your MCP Client, while not requiring that all -of their schemas be loaded into context at once. Instead, the LLM will automatically use these tools together to -search for, look up, and invoke endpoints dynamically. However, due to the indirect nature of the schemas, it -can struggle to provide the correct properties a bit more than when tools are imported explicitly. Therefore, -you can opt-in to explicit tools, the dynamic tools, or both. - -See more information with `--help`. - -All of these command-line options can be repeated, combined together, and have corresponding exclusion versions (e.g. `--no-tool`). - -Use `--list` to see the list of available tools, or see below. - -### Code execution - -If you specify `--tools=code` to the MCP server, it will expose just two tools: - -- `search_docs` - Searches the API documentation and returns a list of markdown results -- `execute` - Runs code against the TypeScript client - -This allows the LLM to implement more complex logic by chaining together many API calls without loading -intermediary results into its context window. - -The code execution itself happens in a Deno sandbox that has network access only to the base URL for the API. - -### Specifying the MCP Client - -Different clients have varying abilities to handle arbitrary tools and schemas. - -You can specify the client you are using with the `--client` argument, and the MCP server will automatically -serve tools and schemas that are more compatible with that client. - -- `--client=`: Set all capabilities based on a known MCP client - - - Valid values: `openai-agents`, `claude`, `claude-code`, `cursor` - - Example: `--client=cursor` - -Additionally, if you have a client not on the above list, or the client has gotten better -over time, you can manually enable or disable certain capabilities: - -- `--capability=`: Specify individual client capabilities - - Available capabilities: - - `top-level-unions`: Enable support for top-level unions in tool schemas - - `valid-json`: Enable JSON string parsing for arguments - - `refs`: Enable support for $ref pointers in schemas - - `unions`: Enable support for union types (anyOf) in schemas - - `formats`: Enable support for format validations in schemas (e.g. date-time, email) - - `tool-name-length=N`: Set maximum tool name length to N characters - - Example: `--capability=top-level-unions --capability=tool-name-length=40` - - Example: `--capability=top-level-unions,tool-name-length=40` - -### Examples - -1. Filter for read operations on cards: - -```bash ---resource=cards --operation=read -``` - -2. Exclude specific tools while including others: - -```bash ---resource=cards --no-tool=create_cards -``` - -3. Configure for Cursor client with custom max tool name length: - -```bash ---client=cursor --capability=tool-name-length=40 -``` - -4. Complex filtering with multiple criteria: - -```bash ---resource=cards,accounts --operation=read --tag=kyc --no-tool=create_cards -``` +Using this scheme, agents are capable of performing very complex tasks deterministically +and repeatably. ## Running remotely @@ -187,124 +99,3 @@ A configuration JSON for this server might look like this, assuming the server i } } ``` - -The command-line arguments for filtering tools and specifying clients can also be used as query parameters in the URL. -For example, to exclude specific tools while including others, use the URL: - -``` -http://localhost:3000?resource=cards&resource=accounts&no_tool=create_cards -``` - -Or, to configure for the Cursor client, with a custom max tool name length, use the URL: - -``` -http://localhost:3000?client=cursor&capability=tool-name-length%3D40 -``` - -## Importing the tools and server individually - -```js -// Import the server, generated endpoints, or the init function -import { server, endpoints, init } from "writer-sdk-mcp/server"; - -// import a specific tool -import retrieveApplications from "writer-sdk-mcp/tools/applications/retrieve-applications"; - -// initialize the server and all endpoints -init({ server, endpoints }); - -// manually start server -const transport = new StdioServerTransport(); -await server.connect(transport); - -// or initialize your own server with specific tools -const myServer = new McpServer(...); - -// define your own endpoint -const myCustomEndpoint = { - tool: { - name: 'my_custom_tool', - description: 'My custom tool', - inputSchema: zodToJsonSchema(z.object({ a_property: z.string() })), - }, - handler: async (client: client, args: any) => { - return { myResponse: 'Hello world!' }; - }) -}; - -// initialize the server with your custom endpoints -init({ server: myServer, endpoints: [retrieveApplications, myCustomEndpoint] }); -``` - -## Available Tools - -The following tools are available in this MCP server. - -### Resource `applications`: - -- `retrieve_applications` (`read`): Retrieves detailed information for a specific no-code agent (formerly called no-code applications), including its configuration and current status. -- `list_applications` (`read`): Get all available no-code agents (applications) in your account. No-code agents are pre-configured AI workflows built in Writer's AI Studio. Use this to discover which agents are available before generating content from them. -- `generate_content_applications` (`write`): Generate content using a pre-configured no-code agent. No-code agents are custom AI workflows you've built in AI Studio with specific prompts, models, and settings. Provide the application ID and required inputs to get tailored content. Useful for consistent, repeatable AI tasks like content generation, data extraction, or custom workflows. - -### Resource `applications.jobs`: - -- `create_applications_jobs` (`write`): Generate content asynchronously from an existing no-code agent (formerly called no-code applications) with inputs. -- `retrieve_applications_jobs` (`read`): Retrieves a single job created via the Async API. -- `list_applications_jobs` (`read`): Retrieve all jobs created via the async API, linked to the provided application ID (or alias). -- `retry_applications_jobs` (`write`): Re-triggers the async execution of a single job previously created via the Async api and terminated in error. - -### Resource `applications.graphs`: - -- `update_applications_graphs` (`write`): Updates the list of Knowledge Graphs associated with a no-code chat agent. -- `list_applications_graphs` (`read`): Retrieve Knowledge Graphs associated with a no-code agent that has chat capabilities. - -### Resource `chat`: - -- `chat_chat` (`write`): Generate AI responses for conversational interactions. Use this for chat-based tasks, Q&A, content generation, and any natural language processing. Supports tools like Knowledge Graphs, web search, translation, and vision. Choose from models like palmyra-x5, palmyra-x4, palmyra-creative, palmyra-med, or palmyra-fin depending on the task. - -### Resource `completions`: - -- `create_completions` (`write`): Generate text completions from a single prompt without conversational context. Best for straightforward text generation tasks like article writing, summaries, or creative content. For interactive conversations or multi-turn dialogues, use generate-chat-completion instead. - -### Resource `models`: - -- `list_models` (`read`): Retrieve a list of available models that can be used for text generation, chat completions, and other AI tasks. - -### Resource `graphs`: - -- `create_graphs` (`write`): Create a new Knowledge Graph to organize and query documents. Knowledge Graphs are containers for files that enable AI-powered search and question answering. After creation, add files to the graph using add-file-to-graph, then query it using query-knowledge-graph. -- `retrieve_graphs` (`read`): Get detailed information about a specific Knowledge Graph by its ID. Returns the graph name, description, creation date, file processing status, and associated URLs (for web-based graphs). Use this to check processing status or get graph metadata. -- `update_graphs` (`write`): Update the name and description of a Knowledge Graph. -- `list_graphs` (`read`): Get all available Knowledge Graphs in your account. Knowledge Graphs are collections of documents and files that can be queried using AI. Use this to discover which knowledge bases are available before querying them. -- `delete_graphs` (`write`): Delete a Knowledge Graph. -- `add_file_to_graph_graphs` (`write`): Add an uploaded file to a Knowledge Graph to make it queryable. The file must already be uploaded using upload-file. Once added, the file's content becomes searchable when querying the Knowledge Graph. Files are processed asynchronously - check status using get-file-info. -- `question_graphs` (`write`): Ask questions and get AI-generated answers based on your Knowledge Graph content. Queries your uploaded documents, PDFs, and files to retrieve accurate, source-cited information. Returns answers with supporting snippets and file references. Ideal for RAG (Retrieval-Augmented Generation) applications and knowledge base queries. -- `remove_file_from_graph_graphs` (`write`): Remove a file from a Knowledge Graph. - -### Resource `files`: - -- `retrieve_files` (`read`): Get metadata and status information for a specific file by its ID. Returns file name, creation date, processing status, and associated Knowledge Graph IDs. Use this to check if a file has finished processing or to find which Knowledge Graphs contain a specific file. -- `list_files` (`read`): Get a paginated list of all uploaded files. Filter by processing status (in_progress, completed, failed), Knowledge Graph association, or file type. Use this to discover available files, monitor processing status, or find files to add to Knowledge Graphs. -- `delete_files` (`write`): Permanently delete a file from the system. This action cannot be undone. -- `download_files` (`read`): Download the binary content of a file. The response will contain the file data in the appropriate MIME type. -- `retry_files` (`write`): Retry processing of files that previously failed to process. This will re-attempt the processing of the specified files. -- `upload_files` (`write`): Upload documents and files to Writer. Supports PDF, DOC, DOCX, PPT, PPTX, JPG, PNG, EML, HTML, SRT, CSV, XLS, XLSX, MP3, and MP4 formats. Once uploaded, files can be added to Knowledge Graphs for querying or used with Vision API for image analysis. Returns a file ID for subsequent operations. - -### Resource `tools`: - -- `ai_detect_tools` (`write`): Detects if content is AI- or human-generated, with a confidence score. Content must have at least 350 characters -- `context_aware_splitting_tools` (`write`): Splits a long block of text (maximum 4000 words) into smaller chunks while preserving the semantic meaning of the text and context between the chunks. -- `parse_pdf_tools` (`write`): Parse PDF to other formats. -- `web_search_tools` (`write`): Search the web for information about a given query and return relevant results with source URLs. - -### Resource `tools.comprehend`: - -- `medical_tools_comprehend` (`write`): Analyze unstructured medical text to extract entities labeled with standardized medical codes and confidence scores. - -### Resource `translation`: - -- `translate_translation` (`write`): Translate text from one language to another. - -### Resource `vision`: - -- `analyze_vision` (`write`): Submit images and documents with a prompt to generate an analysis. Supports JPG, PNG, PDF, and TXT files up to 7MB each. diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json index 9a3b54bb..66156fba 100644 --- a/packages/mcp-server/manifest.json +++ b/packages/mcp-server/manifest.json @@ -1,7 +1,7 @@ { "dxt_version": "0.2", "name": "writer-sdk-mcp", - "version": "2.3.2-rc.1", + "version": "2.4.0-rc.1", "description": "The official MCP Server for the Writer API", "author": { "name": "Writer", @@ -18,7 +18,9 @@ "entry_point": "index.js", "mcp_config": { "command": "node", - "args": ["${__dirname}/index.js"], + "args": [ + "${__dirname}/index.js" + ], "env": { "WRITER_API_KEY": "${user_config.WRITER_API_KEY}" } @@ -39,5 +41,7 @@ "node": ">=18.0.0" } }, - "keywords": ["api"] + "keywords": [ + "api" + ] } diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index f081e3c6..d83bb457 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "writer-sdk-mcp", - "version": "2.3.3-rc.1", + "version": "2.4.0-rc.1", "description": "The official MCP Server for the Writer API", "author": "Writer ", "types": "dist/index.d.ts", @@ -26,19 +26,27 @@ "format": "prettier --write --cache --cache-strategy metadata . !dist", "prepare": "npm run build", "tsn": "ts-node -r tsconfig-paths/register", - "lint": "eslint --ext ts,js .", - "fix": "eslint --fix --ext ts,js ." + "lint": "eslint .", + "fix": "eslint --fix ." }, "dependencies": { "writer-sdk": "file:../../dist/", + "ajv": "^8.18.0", "@cloudflare/cabidela": "^0.2.4", - "@modelcontextprotocol/sdk": "^1.11.5", + "@hono/node-server": "^1.19.10", + "@modelcontextprotocol/sdk": "^1.27.1", + "hono": "^4.12.4", "@valtown/deno-http-worker": "^0.0.21", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^5.1.0", "fuse.js": "^7.1.0", + "minisearch": "^7.2.0", "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz", - "qs": "^6.14.0", + "pino": "^10.3.1", + "pino-http": "^11.0.0", + "pino-pretty": "^13.1.3", + "qs": "^6.14.1", "typescript": "5.8.3", "yargs": "^17.7.2", "zod": "^3.25.20", @@ -49,7 +57,8 @@ "mcp-server": "dist/index.js" }, "devDependencies": { - "@anthropic-ai/mcpb": "1.1.0", + "@anthropic-ai/mcpb": "^2.1.2", + "@types/cookie-parser": "^1.4.10", "@types/cors": "^2.8.19", "@types/express": "^5.0.3", "@types/jest": "^29.4.0", @@ -57,9 +66,9 @@ "@types/yargs": "^17.0.8", "@typescript-eslint/eslint-plugin": "8.31.1", "@typescript-eslint/parser": "8.31.1", - "eslint": "^8.49.0", - "eslint-plugin-prettier": "^5.0.1", - "eslint-plugin-unused-imports": "^3.0.0", + "eslint": "^9.39.1", + "eslint-plugin-prettier": "^5.4.1", + "eslint-plugin-unused-imports": "^4.1.4", "jest": "^29.4.0", "prettier": "^3.0.0", "ts-jest": "^29.1.0", diff --git a/packages/mcp-server/src/headers.ts b/packages/mcp-server/src/auth.ts similarity index 50% rename from packages/mcp-server/src/headers.ts rename to packages/mcp-server/src/auth.ts index 555715b7..495e9823 100644 --- a/packages/mcp-server/src/headers.ts +++ b/packages/mcp-server/src/auth.ts @@ -2,8 +2,9 @@ import { IncomingMessage } from 'node:http'; import { ClientOptions } from 'writer-sdk'; +import { McpOptions } from './options'; -export const parseAuthHeaders = (req: IncomingMessage): Partial => { +export const parseClientAuthHeaders = (req: IncomingMessage, required?: boolean): Partial => { if (req.headers.authorization) { const scheme = req.headers.authorization.split(' ')[0]!; const value = req.headers.authorization.slice(scheme.length + 1); @@ -15,6 +16,8 @@ export const parseAuthHeaders = (req: IncomingMessage): Partial = 'Unsupported authorization scheme. Expected the "Authorization" header to be a supported scheme (Bearer).', ); } + } else if (required) { + throw new Error('Missing required Authorization header; see WWW-Authenticate header for details.'); } const apiKey = @@ -23,3 +26,17 @@ export const parseAuthHeaders = (req: IncomingMessage): Partial = : req.headers['x-writer-api-key']; return { apiKey }; }; + +export const getStainlessApiKey = (req: IncomingMessage, mcpOptions: McpOptions): string | undefined => { + // Try to get the key from the x-stainless-api-key header + const headerKey = + Array.isArray(req.headers['x-stainless-api-key']) ? + req.headers['x-stainless-api-key'][0] + : req.headers['x-stainless-api-key']; + if (headerKey && typeof headerKey === 'string') { + return headerKey; + } + + // Fall back to value set in the mcpOptions (e.g. from environment variable), if provided + return mcpOptions.stainlessApiKey; +}; diff --git a/packages/mcp-server/src/code-tool-paths.cts b/packages/mcp-server/src/code-tool-paths.cts index 15ce7f55..78263e45 100644 --- a/packages/mcp-server/src/code-tool-paths.cts +++ b/packages/mcp-server/src/code-tool-paths.cts @@ -1,3 +1,5 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -export const workerPath = require.resolve('./code-tool-worker.mjs'); +export function getWorkerPath(): string { + return require.resolve('./code-tool-worker.mjs'); +} diff --git a/packages/mcp-server/src/code-tool-types.ts b/packages/mcp-server/src/code-tool-types.ts index 48c677a2..ace4d54e 100644 --- a/packages/mcp-server/src/code-tool-types.ts +++ b/packages/mcp-server/src/code-tool-types.ts @@ -3,12 +3,15 @@ import { ClientOptions } from 'writer-sdk'; export type WorkerInput = { - opts: ClientOptions; + project_name: string; code: string; + client_opts: ClientOptions; + intent?: string | undefined; }; -export type WorkerSuccess = { + +export type WorkerOutput = { + is_error: boolean; result: unknown | null; - logLines: string[]; - errLines: string[]; + log_lines: string[]; + err_lines: string[]; }; -export type WorkerError = { message: string | undefined }; diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index 1c458202..b6c1a243 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -1,37 +1,47 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import path from 'node:path'; import util from 'node:util'; - import Fuse from 'fuse.js'; import ts from 'typescript'; +import { WorkerOutput } from './code-tool-types'; +import { Writer, ClientOptions } from 'writer-sdk'; -import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; -import { Writer } from 'writer-sdk'; - -function getRunFunctionNode( - code: string, -): ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction | null { +function getRunFunctionSource(code: string): { + type: 'declaration' | 'expression'; + client: string | undefined; + code: string; +} | null { const sourceFile = ts.createSourceFile('code.ts', code, ts.ScriptTarget.Latest, true); + const printer = ts.createPrinter(); for (const statement of sourceFile.statements) { // Check for top-level function declarations if (ts.isFunctionDeclaration(statement)) { if (statement.name?.text === 'run') { - return statement; + return { + type: 'declaration', + client: statement.parameters[0]?.name.getText(), + code: printer.printNode(ts.EmitHint.Unspecified, statement.body!, sourceFile), + }; } } // Check for variable declarations: const run = () => {} or const run = function() {} if (ts.isVariableStatement(statement)) { for (const declaration of statement.declarationList.declarations) { - if (ts.isIdentifier(declaration.name) && declaration.name.text === 'run') { + if ( + ts.isIdentifier(declaration.name) && + declaration.name.text === 'run' && // Check if it's initialized with a function - if ( - declaration.initializer && - (ts.isFunctionExpression(declaration.initializer) || ts.isArrowFunction(declaration.initializer)) - ) { - return declaration.initializer; - } + declaration.initializer && + (ts.isFunctionExpression(declaration.initializer) || ts.isArrowFunction(declaration.initializer)) + ) { + return { + type: 'expression', + client: declaration.initializer.parameters[0]?.name.getText(), + code: printer.printNode(ts.EmitHint.Unspecified, declaration.initializer, sourceFile), + }; } } } @@ -40,6 +50,58 @@ function getRunFunctionNode( return null; } +function getTSDiagnostics(code: string): string[] { + const functionSource = getRunFunctionSource(code)!; + const codeWithImport = [ + 'import { Writer } from "writer-sdk";', + functionSource.type === 'declaration' ? + `async function run(${functionSource.client}: Writer)` + : `const run: (${functionSource.client}: Writer) => Promise =`, + functionSource.code, + ].join('\n'); + const sourcePath = path.resolve('code.ts'); + const ast = ts.createSourceFile(sourcePath, codeWithImport, ts.ScriptTarget.Latest, true); + const options = ts.getDefaultCompilerOptions(); + options.target = ts.ScriptTarget.Latest; + options.module = ts.ModuleKind.NodeNext; + options.moduleResolution = ts.ModuleResolutionKind.NodeNext; + const host = ts.createCompilerHost(options, true); + const newHost: typeof host = { + ...host, + getSourceFile: (...args) => { + if (path.resolve(args[0]) === sourcePath) { + return ast; + } + return host.getSourceFile(...args); + }, + readFile: (...args) => { + if (path.resolve(args[0]) === sourcePath) { + return codeWithImport; + } + return host.readFile(...args); + }, + fileExists: (...args) => { + if (path.resolve(args[0]) === sourcePath) { + return true; + } + return host.fileExists(...args); + }, + }; + const program = ts.createProgram({ + options, + rootNames: [sourcePath], + host: newHost, + }); + const diagnostics = ts.getPreEmitDiagnostics(program, ast); + return diagnostics.map((d) => { + const message = ts.flattenDiagnosticMessageText(d.messageText, '\n'); + if (!d.file || !d.start) return `- ${message}`; + const { line: lineNumber } = ts.getLineAndCharacterOfPosition(d.file, d.start); + const line = codeWithImport.split('\n').at(lineNumber)?.trim(); + return line ? `- ${message}\n ${line}` : `- ${message}`; + }); +} + const fuse = new Fuse( [ 'client.applications.generateContent', @@ -169,24 +231,34 @@ function parseError(code: string, error: unknown): string | undefined { } const fetch = async (req: Request): Promise => { - const { opts, code } = (await req.json()) as WorkerInput; - if (code == null) { + const { opts, code } = (await req.json()) as { opts: ClientOptions; code: string }; + + const runFunctionSource = code ? getRunFunctionSource(code) : null; + if (!runFunctionSource) { + const message = + code ? + 'The code is missing a top-level `run` function.' + : 'The code argument is missing. Provide one containing a top-level `run` function.'; return Response.json( { - message: - 'The code param is missing. Provide one containing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', - } satisfies WorkerError, + is_error: true, + result: `${message} Write code within this template:\n\n\`\`\`\nasync function run(client) {\n // Fill this out\n}\n\`\`\``, + log_lines: [], + err_lines: [], + } satisfies WorkerOutput, { status: 400, statusText: 'Code execution error' }, ); } - const runFunctionNode = getRunFunctionNode(code); - if (!runFunctionNode) { + const diagnostics = getTSDiagnostics(code); + if (diagnostics.length > 0) { return Response.json( { - message: - 'The code is missing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', - } satisfies WorkerError, + is_error: true, + result: `The code contains TypeScript diagnostics:\n${diagnostics.join('\n')}`, + log_lines: [], + err_lines: [], + } satisfies WorkerOutput, { status: 400, statusText: 'Code execution error' }, ); } @@ -195,14 +267,14 @@ const fetch = async (req: Request): Promise => { ...opts, }); - const logLines: string[] = []; - const errLines: string[] = []; + const log_lines: string[] = []; + const err_lines: string[] = []; const console = { log: (...args: unknown[]) => { - logLines.push(util.format(...args)); + log_lines.push(util.format(...args)); }, error: (...args: unknown[]) => { - errLines.push(util.format(...args)); + err_lines.push(util.format(...args)); }, }; try { @@ -210,15 +282,19 @@ const fetch = async (req: Request): Promise => { eval(`${code}\nrun_ = run;`); const result = await run_(makeSdkProxy(client, { path: ['client'] })); return Response.json({ + is_error: false, result, - logLines, - errLines, - } satisfies WorkerSuccess); + log_lines, + err_lines, + } satisfies WorkerOutput); } catch (e) { return Response.json( { - message: parseError(code, e), - } satisfies WorkerError, + is_error: true, + result: parseError(code, e), + log_lines, + err_lines, + } satisfies WorkerOutput, { status: 400, statusText: 'Code execution error' }, ); } diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index d7cd783d..df578364 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -1,13 +1,43 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { dirname } from 'node:path'; -import { pathToFileURL } from 'node:url'; -import Writer, { ClientOptions } from 'writer-sdk'; -import { ContentBlock, Endpoint, Metadata, ToolCallResult } from './tools/types'; - +import { + ContentBlock, + McpRequestContext, + McpTool, + Metadata, + ToolCallResult, + asErrorResult, + asTextContentResult, +} from './types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { readEnv, requireValue } from './util'; +import { WorkerInput, WorkerOutput } from './code-tool-types'; +import { getLogger } from './logger'; +import { SdkMethod } from './methods'; +import { McpCodeExecutionMode } from './options'; +import { ClientOptions } from 'writer-sdk'; + +const prompt = `Runs JavaScript code to interact with the Writer API. + +You are a skilled TypeScript programmer writing code to interface with the service. +Define an async function named "run" that takes a single parameter of an initialized SDK client and it will be run. +For example: + +\`\`\` +async function run(client) { + const chatCompletion = await client.chat.chat({ messages: [{ content: 'Write a haiku about programming', role: 'user' }], model: 'palmyra-x5' }); -import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types'; + console.log(chatCompletion.id); +} +\`\`\` + +You will be returned anything that your function returns, plus the results of any console.log statements. +Do not add try-catch blocks for single API calls. The tool will handle errors for you. +Do not add comments unless necessary for generating better code. +Code will run in a container, and cannot interact with the network outside of the given SDK client. +Variables will not persist between calls, so make sure to return or log any data you might need later. +Remember that you are writing TypeScript code, so you need to be careful with your types. +Always type dynamic key-value stores explicitly as Record instead of {}.`; /** * A tool that runs code against a copy of the SDK. @@ -16,132 +46,349 @@ import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types'; * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then * a generic endpoint that can be used to invoke any endpoint with the provided arguments. * - * @param endpoints - The endpoints to include in the list. + * @param blockedMethods - The methods to block for code execution. Blocking is done by simple string + * matching, so it is not secure against obfuscation. For stronger security, block in the downstream API + * with limited API keys. + * @param codeExecutionMode - Whether to execute code in a local Deno environment or in a remote + * sandbox environment hosted by Stainless. */ -export async function codeTool(): Promise { +export function codeTool({ + blockedMethods, + codeExecutionMode, +}: { + blockedMethods: SdkMethod[] | undefined; + codeExecutionMode: McpCodeExecutionMode; +}): McpTool { const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] }; const tool: Tool = { name: 'execute', - description: - 'Runs JavaScript code to interact with the API.\n\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client named "client", and it will be run.\nWrite code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```\n\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', - inputSchema: { type: 'object', properties: { code: { type: 'string' } } }, + description: prompt, + inputSchema: { + type: 'object', + properties: { + code: { + type: 'string', + description: 'Code to execute.', + }, + intent: { + type: 'string', + description: 'Task you are trying to perform. Used for improving the service.', + }, + }, + required: ['code'], + }, }; - // Import dynamically to avoid failing at import time in cases where the environment is not well-supported. - const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker'); - const { workerPath } = await import('./code-tool-paths.cjs'); - - const handler = async (client: Writer, args: unknown): Promise => { - const baseURLHostname = new URL(client.baseURL).hostname; - const { code } = args as { code: string }; - - const worker = await newDenoHTTPWorker(pathToFileURL(workerPath), { - runFlags: [ - `--node-modules-dir=manual`, - `--allow-read=code-tool-worker.mjs,${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`, - `--allow-net=${baseURLHostname}`, - // Allow environment variables because instantiating the client will try to read from them, - // even though they are not set. - '--allow-env', - ], - printOutput: true, - spawnOptions: { - cwd: dirname(workerPath), + const logger = getLogger(); + + const handler = async ({ + reqContext, + args, + }: { + reqContext: McpRequestContext; + args: any; + }): Promise => { + const code = args.code as string; + // Do very basic blocking of code that includes forbidden method names. + // + // WARNING: This is not secure against obfuscation and other evasion methods. If + // stronger security blocks are required, then these should be enforced in the downstream + // API (e.g., by having users call the MCP server with API keys with limited permissions). + if (blockedMethods) { + const blockedMatches = blockedMethods.filter((method) => code.includes(method.fullyQualifiedName)); + if (blockedMatches.length > 0) { + return asErrorResult( + `The following methods have been blocked by the MCP server and cannot be used in code execution: ${blockedMatches + .map((m) => m.fullyQualifiedName) + .join(', ')}`, + ); + } + } + + let result: ToolCallResult; + const startTime = Date.now(); + + if (codeExecutionMode === 'local') { + logger.debug('Executing code in local Deno environment'); + result = await localDenoHandler({ reqContext, args }); + } else { + logger.debug('Executing code in remote Stainless environment'); + result = await remoteStainlessHandler({ reqContext, args }); + } + + logger.info( + { + codeExecutionMode, + durationMs: Date.now() - startTime, + isError: result.isError, + contentRows: result.content?.length ?? 0, }, - }); + 'Got code tool execution result', + ); + return result; + }; + + return { metadata, tool, handler }; +} + +const remoteStainlessHandler = async ({ + reqContext, + args, +}: { + reqContext: McpRequestContext; + args: any; +}): Promise => { + const code = args.code as string; + const intent = args.intent as string | undefined; + const client = reqContext.client; + + const codeModeEndpoint = readEnv('CODE_MODE_ENDPOINT_URL') ?? 'https://api.stainless.com/api/ai/code-tool'; + + const localClientEnvs = { + WRITER_API_KEY: requireValue( + readEnv('WRITER_API_KEY') ?? client.apiKey, + 'set WRITER_API_KEY environment variable or provide apiKey client option', + ), + WRITER_BASE_URL: readEnv('WRITER_BASE_URL') ?? client.baseURL ?? undefined, + }; + // Merge any upstream client envs from the request header, with upstream values taking precedence. + const mergedClientEnvs = { ...localClientEnvs, ...reqContext.upstreamClientEnvs }; + + // Setting a Stainless API key authenticates requests to the code tool endpoint. + const res = await fetch(codeModeEndpoint, { + method: 'POST', + headers: { + ...(reqContext.stainlessApiKey && { Authorization: reqContext.stainlessApiKey }), + 'Content-Type': 'application/json', + 'x-stainless-mcp-client-envs': JSON.stringify(mergedClientEnvs), + }, + body: JSON.stringify({ + project_name: 'writer', + code, + intent, + client_opts: {}, + } satisfies WorkerInput), + }); + + if (!res.ok) { + if (res.status === 404 && !reqContext.stainlessApiKey) { + throw new Error( + 'Could not access code tool for this project. You may need to provide a Stainless API key via the STAINLESS_API_KEY environment variable, the --stainless-api-key flag, or the x-stainless-api-key HTTP header.', + ); + } + throw new Error( + `${res.status}: ${ + res.statusText + } error when trying to contact Code Tool server. Details: ${await res.text()}`, + ); + } + + const { is_error, result, log_lines, err_lines } = (await res.json()) as WorkerOutput; + const hasLogs = log_lines.length > 0 || err_lines.length > 0; + const output = { + result, + ...(log_lines.length > 0 && { log_lines }), + ...(err_lines.length > 0 && { err_lines }), + }; + if (is_error) { + return asErrorResult(typeof result === 'string' && !hasLogs ? result : JSON.stringify(output, null, 2)); + } + return asTextContentResult(output); +}; + +const localDenoHandler = async ({ + reqContext, + args, +}: { + reqContext: McpRequestContext; + args: unknown; +}): Promise => { + const fs = await import('node:fs'); + const path = await import('node:path'); + const url = await import('node:url'); + const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker'); + const { getWorkerPath } = await import('./code-tool-paths.cjs'); + const workerPath = getWorkerPath(); + const client = reqContext.client; + const baseURLHostname = new URL(client.baseURL).hostname; + const { code } = args as { code: string }; + + let denoPath: string; + + const packageRoot = path.resolve(path.dirname(workerPath), '..'); + const packageNodeModulesPath = path.resolve(packageRoot, 'node_modules'); + + // Check if deno is in PATH + const { execSync } = await import('node:child_process'); + try { + execSync('command -v deno', { stdio: 'ignore' }); + denoPath = 'deno'; + } catch { try { - const resp = await new Promise((resolve, reject) => { - worker.addEventListener('exit', (exitCode) => { - reject(new Error(`Worker exited with code ${exitCode}`)); - }); - - const opts: ClientOptions = { - baseURL: client.baseURL, - apiKey: client.apiKey, - defaultHeaders: { - 'X-Stainless-MCP': 'true', - }, - }; + // Use deno binary in node_modules if it's found + const denoNodeModulesPath = path.resolve(packageNodeModulesPath, 'deno', 'bin.cjs'); + await fs.promises.access(denoNodeModulesPath, fs.constants.X_OK); + denoPath = denoNodeModulesPath; + } catch { + return asErrorResult( + 'Deno is required for code execution but was not found. ' + + 'Install it from https://deno.land or run: npm install deno', + ); + } + } - const req = worker.request( - 'http://localhost', - { - headers: { - 'content-type': 'application/json', - }, - method: 'POST', - }, - (resp) => { - const body: Uint8Array[] = []; - resp.on('error', (err) => { - reject(err); - }); - resp.on('data', (chunk) => { - body.push(chunk); - }); - resp.on('end', () => { - resolve( - new Response(Buffer.concat(body).toString(), { - status: resp.statusCode ?? 200, - headers: resp.headers as any, - }), - ); - }); - }, - ); + const allowReadPaths = [ + 'code-tool-worker.mjs', + `${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`, + packageRoot, + ]; + + // Follow symlinks in node_modules to allow read access to workspace-linked packages + try { + const sdkPkgName = 'writer-sdk'; + const sdkDir = path.resolve(packageNodeModulesPath, sdkPkgName); + const realSdkDir = fs.realpathSync(sdkDir); + if (realSdkDir !== sdkDir) { + allowReadPaths.push(realSdkDir); + } + } catch { + // Ignore if symlink resolution fails + } + + const allowRead = allowReadPaths.join(','); - const body = JSON.stringify({ - opts, - code, - } satisfies WorkerInput); + const worker = await newDenoHTTPWorker(url.pathToFileURL(workerPath), { + denoExecutable: denoPath, + runFlags: [ + `--node-modules-dir=manual`, + `--allow-read=${allowRead}`, + `--allow-net=${baseURLHostname}`, + // Allow environment variables because instantiating the client will try to read from them, + // even though they are not set. + '--allow-env', + ], + printOutput: true, + spawnOptions: { + cwd: path.dirname(workerPath), + // Merge any upstream client envs into the Deno subprocess environment, + // with the upstream env vars taking precedence. + env: { ...process.env, ...reqContext.upstreamClientEnvs }, + }, + }); - req.write(body, (err) => { - if (err != null) { + try { + const resp = await new Promise((resolve, reject) => { + worker.addEventListener('exit', (exitCode) => { + reject(new Error(`Worker exited with code ${exitCode}`)); + }); + + // Strip null/undefined values so that the worker SDK client can fall back to + // reading from environment variables (including any upstreamClientEnvs). + const opts = { + ...(client.baseURL != null ? { baseURL: client.baseURL } : undefined), + ...(client.apiKey != null ? { apiKey: client.apiKey } : undefined), + defaultHeaders: { + 'X-Stainless-MCP': 'true', + }, + } satisfies Partial as ClientOptions; + + const req = worker.request( + 'http://localhost', + { + headers: { + 'content-type': 'application/json', + }, + method: 'POST', + }, + (resp) => { + const body: Uint8Array[] = []; + resp.on('error', (err) => { reject(err); - } - }); + }); + resp.on('data', (chunk) => { + body.push(chunk); + }); + resp.on('end', () => { + resolve( + new Response(Buffer.concat(body).toString(), { + status: resp.statusCode ?? 200, + headers: resp.headers as any, + }), + ); + }); + }, + ); - req.end(); + const body = JSON.stringify({ + opts, + code, }); - if (resp.status === 200) { - const { result, logLines, errLines } = (await resp.json()) as WorkerSuccess; - const returnOutput: ContentBlock | null = - result == null ? null : ( - { - type: 'text', - text: typeof result === 'string' ? result : JSON.stringify(result), - } - ); - const logOutput: ContentBlock | null = - logLines.length === 0 ? - null - : { - type: 'text', - text: logLines.join('\n'), - }; - const errOutput: ContentBlock | null = - errLines.length === 0 ? - null - : { - type: 'text', - text: 'Error output:\n' + errLines.join('\n'), - }; - return { - content: [returnOutput, logOutput, errOutput].filter((block) => block !== null), - }; - } else { - const { message } = (await resp.json()) as WorkerError; - return { - content: message == null ? [] : [{ type: 'text', text: message }], - isError: true, - }; - } - } finally { - worker.terminate(); - } - }; + req.write(body, (err) => { + if (err != null) { + reject(err); + } + }); - return { metadata, tool, handler }; -} + req.end(); + }); + + if (resp.status === 200) { + const { result, log_lines, err_lines } = (await resp.json()) as WorkerOutput; + const returnOutput: ContentBlock | null = + result == null ? null : ( + { + type: 'text', + text: typeof result === 'string' ? result : JSON.stringify(result), + } + ); + const logOutput: ContentBlock | null = + log_lines.length === 0 ? + null + : { + type: 'text', + text: log_lines.join('\n'), + }; + const errOutput: ContentBlock | null = + err_lines.length === 0 ? + null + : { + type: 'text', + text: 'Error output:\n' + err_lines.join('\n'), + }; + return { + content: [returnOutput, logOutput, errOutput].filter((block) => block !== null), + }; + } else { + const { result, log_lines, err_lines } = (await resp.json()) as WorkerOutput; + const messageOutput: ContentBlock | null = + result == null ? null : ( + { + type: 'text', + text: typeof result === 'string' ? result : JSON.stringify(result), + } + ); + const logOutput: ContentBlock | null = + log_lines.length === 0 ? + null + : { + type: 'text', + text: log_lines.join('\n'), + }; + const errOutput: ContentBlock | null = + err_lines.length === 0 ? + null + : { + type: 'text', + text: 'Error output:\n' + err_lines.join('\n'), + }; + return { + content: [messageOutput, logOutput, errOutput].filter((block) => block !== null), + isError: true, + }; + } + } finally { + worker.terminate(); + } +}; diff --git a/packages/mcp-server/src/compat.ts b/packages/mcp-server/src/compat.ts deleted file mode 100644 index f84053c7..00000000 --- a/packages/mcp-server/src/compat.ts +++ /dev/null @@ -1,483 +0,0 @@ -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import { z } from 'zod'; -import { Endpoint } from './tools'; - -export interface ClientCapabilities { - topLevelUnions: boolean; - validJson: boolean; - refs: boolean; - unions: boolean; - formats: boolean; - toolNameLength: number | undefined; -} - -export const defaultClientCapabilities: ClientCapabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, -}; - -export const ClientType = z.enum(['openai-agents', 'claude', 'claude-code', 'cursor', 'infer']); -export type ClientType = z.infer; - -// Client presets for compatibility -// Note that these could change over time as models get better, so this is -// a best effort. -export const knownClients: Record, ClientCapabilities> = { - 'openai-agents': { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - claude: { - topLevelUnions: true, - validJson: false, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - 'claude-code': { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - cursor: { - topLevelUnions: false, - validJson: true, - refs: false, - unions: false, - formats: false, - toolNameLength: 50, - }, -}; - -/** - * Attempts to parse strings into JSON objects - */ -export function parseEmbeddedJSON(args: Record, schema: Record) { - let updated = false; - const newArgs: Record = Object.assign({}, args); - - for (const [key, value] of Object.entries(newArgs)) { - if (typeof value === 'string') { - try { - const parsed = JSON.parse(value); - // Only parse if result is a plain object (not array, null, or primitive) - if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { - newArgs[key] = parsed; - updated = true; - } - } catch (e) { - // Not valid JSON, leave as is - } - } - } - - if (updated) { - return newArgs; - } - - return args; -} - -export type JSONSchema = { - type?: string; - properties?: Record; - required?: string[]; - anyOf?: JSONSchema[]; - $ref?: string; - $defs?: Record; - [key: string]: any; -}; - -/** - * Truncates tool names to the specified length while ensuring uniqueness. - * If truncation would cause duplicate names, appends a number to make them unique. - */ -export function truncateToolNames(names: string[], maxLength: number): Map { - if (maxLength <= 0) { - return new Map(); - } - - const renameMap = new Map(); - const usedNames = new Set(); - - const toTruncate = names.filter((name) => name.length > maxLength); - - if (toTruncate.length === 0) { - return renameMap; - } - - const willCollide = - new Set(toTruncate.map((name) => name.slice(0, maxLength - 1))).size < toTruncate.length; - - if (!willCollide) { - for (const name of toTruncate) { - const truncatedName = name.slice(0, maxLength); - renameMap.set(name, truncatedName); - } - } else { - const baseLength = maxLength - 1; - - for (const name of toTruncate) { - const baseName = name.slice(0, baseLength); - let counter = 1; - - while (usedNames.has(baseName + counter)) { - counter++; - } - - const finalName = baseName + counter; - renameMap.set(name, finalName); - usedNames.add(finalName); - } - } - - return renameMap; -} - -/** - * Removes top-level unions from a tool by splitting it into multiple tools, - * one for each variant in the union. - */ -export function removeTopLevelUnions(tool: Tool): Tool[] { - const inputSchema = tool.inputSchema as JSONSchema; - const variants = inputSchema.anyOf; - - if (!variants || !Array.isArray(variants) || variants.length === 0) { - return [tool]; - } - - const defs = inputSchema.$defs || {}; - - return variants.map((variant, index) => { - const variantSchema: JSONSchema = { - ...inputSchema, - ...variant, - type: 'object', - properties: { - ...(inputSchema.properties || {}), - ...(variant.properties || {}), - }, - }; - - delete variantSchema.anyOf; - - if (!variantSchema['description']) { - variantSchema['description'] = tool.description; - } - - const usedDefs = findUsedDefs(variant, defs); - if (Object.keys(usedDefs).length > 0) { - variantSchema.$defs = usedDefs; - } else { - delete variantSchema.$defs; - } - - return { - ...tool, - name: `${tool.name}_${toSnakeCase(variant['title'] || `variant${index + 1}`)}`, - description: variant['description'] || tool.description, - inputSchema: variantSchema, - } as Tool; - }); -} - -function findUsedDefs( - schema: JSONSchema, - defs: Record, - visited: Set = new Set(), -): Record { - const usedDefs: Record = {}; - - if (typeof schema !== 'object' || schema === null) { - return usedDefs; - } - - if (schema.$ref) { - const refParts = schema.$ref.split('/'); - if (refParts[0] === '#' && refParts[1] === '$defs' && refParts[2]) { - const defName = refParts[2]; - const def = defs[defName]; - if (def && !visited.has(schema.$ref)) { - usedDefs[defName] = def; - visited.add(schema.$ref); - Object.assign(usedDefs, findUsedDefs(def, defs, visited)); - visited.delete(schema.$ref); - } - } - return usedDefs; - } - - for (const key in schema) { - if (key !== '$defs' && typeof schema[key] === 'object' && schema[key] !== null) { - Object.assign(usedDefs, findUsedDefs(schema[key] as JSONSchema, defs, visited)); - } - } - - return usedDefs; -} - -// Export for testing -export { findUsedDefs }; - -/** - * Inlines all $refs in a schema, eliminating $defs. - * If a circular reference is detected, the circular property is removed. - */ -export function inlineRefs(schema: JSONSchema): JSONSchema { - if (!schema || typeof schema !== 'object') { - return schema; - } - - const clonedSchema = { ...schema }; - const defs: Record = schema.$defs || {}; - - delete clonedSchema.$defs; - - const result = inlineRefsRecursive(clonedSchema, defs, new Set()); - // The top level can never be null - return result === null ? {} : result; -} - -function inlineRefsRecursive( - schema: JSONSchema, - defs: Record, - refPath: Set, -): JSONSchema | null { - if (!schema || typeof schema !== 'object') { - return schema; - } - - if (Array.isArray(schema)) { - return schema.map((item) => { - const processed = inlineRefsRecursive(item, defs, refPath); - return processed === null ? {} : processed; - }) as JSONSchema; - } - - const result = { ...schema }; - - if ('$ref' in result && typeof result.$ref === 'string') { - if (result.$ref.startsWith('#/$defs/')) { - const refName = result.$ref.split('/').pop() as string; - const def = defs[refName]; - - // If we've already seen this ref in our path, we have a circular reference - if (refPath.has(result.$ref)) { - // For circular references, we completely remove the property - // by returning null. The parent will remove it. - return null; - } - - if (def) { - const newRefPath = new Set(refPath); - newRefPath.add(result.$ref); - - const inlinedDef = inlineRefsRecursive({ ...def }, defs, newRefPath); - - if (inlinedDef === null) { - return { ...result }; - } - - // Merge the inlined definition with the original schema's properties - // but preserve things like description, etc. - const { $ref, ...rest } = result; - return { ...inlinedDef, ...rest }; - } - } - - // Keep external refs as-is - return result; - } - - for (const key in result) { - if (result[key] && typeof result[key] === 'object') { - const processed = inlineRefsRecursive(result[key] as JSONSchema, defs, refPath); - if (processed === null) { - // Remove properties that would cause circular references - delete result[key]; - } else { - result[key] = processed; - } - } - } - - return result; -} - -/** - * Removes anyOf fields from a schema, using only the first variant. - */ -export function removeAnyOf(schema: JSONSchema): JSONSchema { - if (!schema || typeof schema !== 'object') { - return schema; - } - - if (Array.isArray(schema)) { - return schema.map((item) => removeAnyOf(item)) as JSONSchema; - } - - const result = { ...schema }; - - if ('anyOf' in result && Array.isArray(result.anyOf) && result.anyOf.length > 0) { - const firstVariant = result.anyOf[0]; - - if (firstVariant && typeof firstVariant === 'object') { - // Special handling for properties to ensure deep merge - if (firstVariant.properties && result.properties) { - result.properties = { - ...result.properties, - ...(firstVariant.properties as Record), - }; - } else if (firstVariant.properties) { - result.properties = { ...firstVariant.properties }; - } - - for (const key in firstVariant) { - if (key !== 'properties') { - result[key] = firstVariant[key]; - } - } - } - - delete result.anyOf; - } - - for (const key in result) { - if (result[key] && typeof result[key] === 'object') { - result[key] = removeAnyOf(result[key] as JSONSchema); - } - } - - return result; -} - -/** - * Removes format fields from a schema and appends them to the description. - */ -export function removeFormats(schema: JSONSchema, formatsCapability: boolean): JSONSchema { - if (formatsCapability) { - return schema; - } - - if (!schema || typeof schema !== 'object') { - return schema; - } - - if (Array.isArray(schema)) { - return schema.map((item) => removeFormats(item, formatsCapability)) as JSONSchema; - } - - const result = { ...schema }; - - if ('format' in result && typeof result['format'] === 'string') { - const formatStr = `(format: "${result['format']}")`; - - if ('description' in result && typeof result['description'] === 'string') { - result['description'] = `${result['description']} ${formatStr}`; - } else { - result['description'] = formatStr; - } - - delete result['format']; - } - - for (const key in result) { - if (result[key] && typeof result[key] === 'object') { - result[key] = removeFormats(result[key] as JSONSchema, formatsCapability); - } - } - - return result; -} - -/** - * Applies all compatibility transformations to the endpoints based on the provided capabilities. - */ -export function applyCompatibilityTransformations( - endpoints: Endpoint[], - capabilities: ClientCapabilities, -): Endpoint[] { - let transformedEndpoints = [...endpoints]; - - // Handle top-level unions first as this changes tool names - if (!capabilities.topLevelUnions) { - const newEndpoints: Endpoint[] = []; - - for (const endpoint of transformedEndpoints) { - const variantTools = removeTopLevelUnions(endpoint.tool); - - if (variantTools.length === 1) { - newEndpoints.push(endpoint); - } else { - for (const variantTool of variantTools) { - newEndpoints.push({ - ...endpoint, - tool: variantTool, - }); - } - } - } - - transformedEndpoints = newEndpoints; - } - - if (capabilities.toolNameLength) { - const toolNames = transformedEndpoints.map((endpoint) => endpoint.tool.name); - const renameMap = truncateToolNames(toolNames, capabilities.toolNameLength); - - transformedEndpoints = transformedEndpoints.map((endpoint) => ({ - ...endpoint, - tool: { - ...endpoint.tool, - name: renameMap.get(endpoint.tool.name) ?? endpoint.tool.name, - }, - })); - } - - if (!capabilities.refs || !capabilities.unions || !capabilities.formats) { - transformedEndpoints = transformedEndpoints.map((endpoint) => { - let schema = endpoint.tool.inputSchema as JSONSchema; - - if (!capabilities.refs) { - schema = inlineRefs(schema); - } - - if (!capabilities.unions) { - schema = removeAnyOf(schema); - } - - if (!capabilities.formats) { - schema = removeFormats(schema, capabilities.formats); - } - - return { - ...endpoint, - tool: { - ...endpoint.tool, - inputSchema: schema as typeof endpoint.tool.inputSchema, - }, - }; - }); - } - - return transformedEndpoints; -} - -function toSnakeCase(str: string): string { - return str - .replace(/\s+/g, '_') - .replace(/([a-z])([A-Z])/g, '$1_$2') - .toLowerCase(); -} diff --git a/packages/mcp-server/src/docs-search-tool.ts b/packages/mcp-server/src/docs-search-tool.ts index 5bded3fb..43e482ec 100644 --- a/packages/mcp-server/src/docs-search-tool.ts +++ b/packages/mcp-server/src/docs-search-tool.ts @@ -1,8 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, asTextContentResult } from './tools/types'; - import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { Metadata, McpRequestContext, asTextContentResult } from './types'; +import { getLogger } from './logger'; +import type { LocalDocsSearch } from './local-docs-search'; export const metadata: Metadata = { resource: 'all', @@ -13,7 +14,8 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'search_docs', - description: 'Search for documentation for how to use the client to interact with the API.', + description: + 'Search SDK documentation to find methods, parameters, and usage examples for interacting with the API. Use this before writing code when you need to discover the right approach.', inputSchema: { type: 'object', properties: { @@ -42,11 +44,95 @@ export const tool: Tool = { const docsSearchURL = process.env['DOCS_SEARCH_URL'] || 'https://api.stainless.com/api/projects/writer/docs/search'; -export const handler = async (_: unknown, args: Record | undefined) => { +let _localSearch: LocalDocsSearch | undefined; + +export function setLocalSearch(search: LocalDocsSearch): void { + _localSearch = search; +} + +async function searchLocal(args: Record): Promise { + if (!_localSearch) { + throw new Error('Local search not initialized'); + } + + const query = (args['query'] as string) ?? ''; + const language = (args['language'] as string) ?? 'typescript'; + const detail = (args['detail'] as string) ?? 'default'; + + return _localSearch.search({ + query, + language, + detail, + maxResults: 5, + }).results; +} + +async function searchRemote(args: Record, reqContext: McpRequestContext): Promise { const body = args as any; const query = new URLSearchParams(body).toString(); - const result = await fetch(`${docsSearchURL}?${query}`); - return asTextContentResult(await result.json()); + + const startTime = Date.now(); + const result = await fetch(`${docsSearchURL}?${query}`, { + headers: { + ...(reqContext.stainlessApiKey && { Authorization: reqContext.stainlessApiKey }), + ...(reqContext.mcpSessionId && { 'x-stainless-mcp-session-id': reqContext.mcpSessionId }), + ...(reqContext.mcpClientInfo && { + 'x-stainless-mcp-client-info': JSON.stringify(reqContext.mcpClientInfo), + }), + }, + }); + + const logger = getLogger(); + + if (!result.ok) { + const errorText = await result.text(); + logger.warn( + { + durationMs: Date.now() - startTime, + query: body.query, + status: result.status, + statusText: result.statusText, + errorText, + }, + 'Got error response from docs search tool', + ); + + if (result.status === 404 && !reqContext.stainlessApiKey) { + throw new Error( + 'Could not find docs for this project. You may need to provide a Stainless API key via the STAINLESS_API_KEY environment variable, the --stainless-api-key flag, or the x-stainless-api-key HTTP header.', + ); + } + + throw new Error( + `${result.status}: ${result.statusText} when using doc search tool. Details: ${errorText}`, + ); + } + + const resultBody = await result.json(); + logger.info( + { + durationMs: Date.now() - startTime, + query: body.query, + }, + 'Got docs search result', + ); + return resultBody; +} + +export const handler = async ({ + reqContext, + args, +}: { + reqContext: McpRequestContext; + args: Record | undefined; +}) => { + const body = args ?? {}; + + if (_localSearch) { + return asTextContentResult(await searchLocal(body)); + } + + return asTextContentResult(await searchRemote(body, reqContext)); }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/dynamic-tools.ts b/packages/mcp-server/src/dynamic-tools.ts deleted file mode 100644 index a35da358..00000000 --- a/packages/mcp-server/src/dynamic-tools.ts +++ /dev/null @@ -1,153 +0,0 @@ -import Writer from 'writer-sdk'; -import { Endpoint, asTextContentResult, ToolCallResult } from './tools/types'; -import { zodToJsonSchema } from 'zod-to-json-schema'; -import { z } from 'zod'; -import { Cabidela } from '@cloudflare/cabidela'; - -function zodToInputSchema(schema: z.ZodSchema) { - return { - type: 'object' as const, - ...(zodToJsonSchema(schema) as any), - }; -} - -/** - * A list of tools that expose all the endpoints in the API dynamically. - * - * Instead of exposing every endpoint as its own tool, which uses up too many tokens for LLMs to use at once, - * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then - * a generic endpoint that can be used to invoke any endpoint with the provided arguments. - * - * @param endpoints - The endpoints to include in the list. - */ -export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { - const listEndpointsSchema = z.object({ - search_query: z - .string() - .optional() - .describe( - 'An optional search query to filter the endpoints by. Provide a partial name, resource, operation, or tag to filter the endpoints returned.', - ), - }); - - const listEndpointsTool = { - metadata: { - resource: 'dynamic_tools', - operation: 'read' as const, - tags: [], - }, - tool: { - name: 'list_api_endpoints', - description: 'List or search for all endpoints in the Writer TypeScript API', - inputSchema: zodToInputSchema(listEndpointsSchema), - }, - handler: async (client: Writer, args: Record | undefined): Promise => { - const query = args && listEndpointsSchema.parse(args).search_query?.trim(); - - const filteredEndpoints = - query && query.length > 0 ? - endpoints.filter((endpoint) => { - const fieldsToMatch = [ - endpoint.tool.name, - endpoint.tool.description, - endpoint.metadata.resource, - endpoint.metadata.operation, - ...endpoint.metadata.tags, - ]; - return fieldsToMatch.some((field) => field && field.toLowerCase().includes(query.toLowerCase())); - }) - : endpoints; - - return asTextContentResult({ - tools: filteredEndpoints.map(({ tool, metadata }) => ({ - name: tool.name, - description: tool.description, - resource: metadata.resource, - operation: metadata.operation, - tags: metadata.tags, - })), - }); - }, - }; - - const getEndpointSchema = z.object({ - endpoint: z.string().describe('The name of the endpoint to get the schema for.'), - }); - const getEndpointTool = { - metadata: { - resource: 'dynamic_tools', - operation: 'read' as const, - tags: [], - }, - tool: { - name: 'get_api_endpoint_schema', - description: - 'Get the schema for an endpoint in the Writer TypeScript API. You can use the schema returned by this tool to invoke an endpoint with the `invoke_api_endpoint` tool.', - inputSchema: zodToInputSchema(getEndpointSchema), - }, - handler: async (client: Writer, args: Record | undefined) => { - if (!args) { - throw new Error('No endpoint provided'); - } - const endpointName = getEndpointSchema.parse(args).endpoint; - - const endpoint = endpoints.find((e) => e.tool.name === endpointName); - if (!endpoint) { - throw new Error(`Endpoint ${endpointName} not found`); - } - return asTextContentResult(endpoint.tool); - }, - }; - - const invokeEndpointSchema = z.object({ - endpoint_name: z.string().describe('The name of the endpoint to invoke.'), - args: z - .record(z.string(), z.any()) - .describe( - 'The arguments to pass to the endpoint. This must match the schema returned by the `get_api_endpoint_schema` tool.', - ), - }); - - const invokeEndpointTool = { - metadata: { - resource: 'dynamic_tools', - operation: 'write' as const, - tags: [], - }, - tool: { - name: 'invoke_api_endpoint', - description: - 'Invoke an endpoint in the Writer TypeScript API. Note: use the `list_api_endpoints` tool to get the list of endpoints and `get_api_endpoint_schema` tool to get the schema for an endpoint.', - inputSchema: zodToInputSchema(invokeEndpointSchema), - }, - handler: async (client: Writer, args: Record | undefined): Promise => { - if (!args) { - throw new Error('No endpoint provided'); - } - const { success, data, error } = invokeEndpointSchema.safeParse(args); - if (!success) { - throw new Error(`Invalid arguments for endpoint. ${error?.format()}`); - } - const { endpoint_name, args: endpointArgs } = data; - - const endpoint = endpoints.find((e) => e.tool.name === endpoint_name); - if (!endpoint) { - throw new Error( - `Endpoint ${endpoint_name} not found. Use the \`list_api_endpoints\` tool to get the list of available endpoints.`, - ); - } - - try { - // Try to validate the arguments for a better error message - const cabidela = new Cabidela(endpoint.tool.inputSchema, { fullErrors: true }); - cabidela.validate(endpointArgs); - } catch (error) { - throw new Error(`Invalid arguments for endpoint ${endpoint_name}:\n${error}`); - } - - return await endpoint.handler(client, endpointArgs); - }, - }; - - return [getEndpointTool, listEndpointsTool, invokeEndpointTool]; -} diff --git a/packages/mcp-server/src/filtering.ts b/packages/mcp-server/src/filtering.ts deleted file mode 100644 index eaae0fcf..00000000 --- a/packages/mcp-server/src/filtering.ts +++ /dev/null @@ -1,18 +0,0 @@ -// @ts-nocheck -import initJq from 'jq-web'; - -export async function maybeFilter(jqFilter: unknown | undefined, response: any): Promise { - if (jqFilter && typeof jqFilter === 'string') { - return await jq(response, jqFilter); - } else { - return response; - } -} - -async function jq(json: any, jqFilter: string) { - return (await initJq).json(json, jqFilter); -} - -export function isJqError(error: any): error is Error { - return error instanceof Error && 'stderr' in error; -} diff --git a/packages/mcp-server/src/http.ts b/packages/mcp-server/src/http.ts index 84517003..0c39b6a1 100644 --- a/packages/mcp-server/src/http.ts +++ b/packages/mcp-server/src/http.ts @@ -2,16 +2,18 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; - +import { ClientOptions } from 'writer-sdk'; import express from 'express'; -import { fromError } from 'zod-validation-error/v3'; -import { McpOptions, parseQueryOptions } from './options'; -import { ClientOptions, initMcpServer, newMcpServer } from './server'; -import { parseAuthHeaders } from './headers'; +import pino from 'pino'; +import pinoHttp from 'pino-http'; +import { getStainlessApiKey, parseClientAuthHeaders } from './auth'; +import { getLogger } from './logger'; +import { McpOptions } from './options'; +import { initMcpServer, newMcpServer } from './server'; -const newServer = ({ +const newServer = async ({ clientOptions, - mcpOptions: defaultMcpOptions, + mcpOptions, req, res, }: { @@ -19,58 +21,81 @@ const newServer = ({ mcpOptions: McpOptions; req: express.Request; res: express.Response; -}): McpServer | null => { - const server = newMcpServer(); - - let mcpOptions: McpOptions; - try { - mcpOptions = parseQueryOptions(defaultMcpOptions, req.query); - } catch (error) { - res.status(400).json({ - jsonrpc: '2.0', - error: { - code: -32000, - message: `Invalid request: ${fromError(error)}`, - }, - }); - return null; +}): Promise => { + const stainlessApiKey = getStainlessApiKey(req, mcpOptions); + const customInstructionsPath = mcpOptions.customInstructionsPath; + const server = await newMcpServer({ stainlessApiKey, customInstructionsPath }); + + const authOptions = parseClientAuthHeaders(req, false); + + let upstreamClientEnvs: Record | undefined; + const clientEnvsHeader = req.headers['x-stainless-mcp-client-envs']; + if (typeof clientEnvsHeader === 'string') { + try { + const parsed = JSON.parse(clientEnvsHeader); + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + upstreamClientEnvs = parsed; + } + } catch { + // Ignore malformed header + } } - try { - const authOptions = parseAuthHeaders(req); - initMcpServer({ - server: server, - clientOptions: { - ...clientOptions, - ...authOptions, - }, - mcpOptions, - }); - } catch (error) { - res.status(401).json({ - jsonrpc: '2.0', - error: { - code: -32000, - message: `Unauthorized: ${error instanceof Error ? error.message : error}`, - }, - }); - return null; + // Parse x-stainless-mcp-client-permissions header to override permission options + // + // Note: Permissions are best-effort and intended to prevent clients from doing unexpected things; + // they're not a hard security boundary, so we allow arbitrary, client-driven overrides. + // + // See the Stainless MCP documentation for more details. + let effectiveMcpOptions = mcpOptions; + const clientPermissionsHeader = req.headers['x-stainless-mcp-client-permissions']; + if (typeof clientPermissionsHeader === 'string') { + try { + const parsed = JSON.parse(clientPermissionsHeader); + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + effectiveMcpOptions = { + ...mcpOptions, + ...(typeof parsed.allow_http_gets === 'boolean' && { codeAllowHttpGets: parsed.allow_http_gets }), + ...(Array.isArray(parsed.allowed_methods) && { codeAllowedMethods: parsed.allowed_methods }), + ...(Array.isArray(parsed.blocked_methods) && { codeBlockedMethods: parsed.blocked_methods }), + }; + getLogger().info( + { clientPermissions: parsed }, + 'Overriding code execution permissions from x-stainless-mcp-client-permissions header', + ); + } + } catch (error) { + getLogger().warn({ error }, 'Failed to parse x-stainless-mcp-client-permissions header'); + } } + await initMcpServer({ + server: server, + mcpOptions: effectiveMcpOptions, + clientOptions: { + ...clientOptions, + ...authOptions, + }, + stainlessApiKey: stainlessApiKey, + upstreamClientEnvs, + mcpSessionId: (req as any).mcpSessionId, + mcpClientInfo: + typeof req.body?.params?.clientInfo?.name === 'string' ? + { name: req.body.params.clientInfo.name, version: String(req.body.params.clientInfo.version ?? '') } + : undefined, + }); + return server; }; const post = (options: { clientOptions: ClientOptions; mcpOptions: McpOptions }) => async (req: express.Request, res: express.Response) => { - const server = newServer({ ...options, req, res }); + const server = await newServer({ ...options, req, res }); // If we return null, we already set the authorization error. if (server === null) return; - const transport = new StreamableHTTPServerTransport({ - // Stateless server - sessionIdGenerator: undefined, - }); - await server.connect(transport); + const transport = new StreamableHTTPServerTransport(); + await server.connect(transport as any); await transport.handleRequest(req, res, req.body); }; @@ -94,17 +119,75 @@ const del = async (req: express.Request, res: express.Response) => { }); }; +const redactHeaders = (headers: Record) => { + const hiddenHeaders = /auth|cookie|key|token|x-stainless-mcp-client-envs/i; + const filtered = { ...headers }; + Object.keys(filtered).forEach((key) => { + if (hiddenHeaders.test(key)) { + filtered[key] = '[REDACTED]'; + } + }); + return filtered; +}; + export const streamableHTTPApp = ({ clientOptions = {}, - mcpOptions = {}, + mcpOptions, }: { clientOptions?: ClientOptions; - mcpOptions?: McpOptions; + mcpOptions: McpOptions; }): express.Express => { const app = express(); app.set('query parser', 'extended'); app.use(express.json()); + app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { + const existing = req.headers['mcp-session-id']; + const sessionId = (Array.isArray(existing) ? existing[0] : existing) || crypto.randomUUID(); + (req as any).mcpSessionId = sessionId; + const origWriteHead = res.writeHead.bind(res); + res.writeHead = function (statusCode: number, ...rest: any[]) { + res.setHeader('mcp-session-id', sessionId); + return origWriteHead(statusCode, ...rest); + } as typeof res.writeHead; + next(); + }); + app.use( + pinoHttp({ + logger: getLogger(), + customLogLevel: (req, res) => { + if (res.statusCode >= 500) { + return 'error'; + } else if (res.statusCode >= 400) { + return 'warn'; + } + return 'info'; + }, + customSuccessMessage: function (req, res) { + return `Request ${req.method} to ${req.url} completed with status ${res.statusCode}`; + }, + customErrorMessage: function (req, res, err) { + return `Request ${req.method} to ${req.url} errored with status ${res.statusCode}`; + }, + serializers: { + req: pino.stdSerializers.wrapRequestSerializer((req) => { + return { + ...req, + headers: redactHeaders(req.raw.headers), + }; + }), + res: pino.stdSerializers.wrapResponseSerializer((res) => { + return { + ...res, + headers: redactHeaders(res.headers), + }; + }), + }, + }), + ); + app.get('/health', async (req: express.Request, res: express.Response) => { + res.status(200).send('OK'); + }); app.get('/', get); app.post('/', post({ clientOptions, mcpOptions })); app.delete('/', del); @@ -112,16 +195,24 @@ export const streamableHTTPApp = ({ return app; }; -export const launchStreamableHTTPServer = async (options: McpOptions, port: number | string | undefined) => { - const app = streamableHTTPApp({ mcpOptions: options }); +export const launchStreamableHTTPServer = async ({ + mcpOptions, + port, +}: { + mcpOptions: McpOptions; + port: number | string | undefined; +}) => { + const app = streamableHTTPApp({ mcpOptions }); const server = app.listen(port); const address = server.address(); + const logger = getLogger(); + if (typeof address === 'string') { - console.error(`MCP Server running on streamable HTTP at ${address}`); + logger.info(`MCP Server running on streamable HTTP at ${address}`); } else if (address !== null) { - console.error(`MCP Server running on streamable HTTP on port ${address.port}`); + logger.info(`MCP Server running on streamable HTTP on port ${address.port}`); } else { - console.error(`MCP Server running on streamable HTTP on port ${port}`); + logger.info(`MCP Server running on streamable HTTP on port ${port}`); } }; diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index 4850a0e2..5bca4a60 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -1,24 +1,24 @@ #!/usr/bin/env node import { selectTools } from './server'; -import { Endpoint, endpoints } from './tools'; import { McpOptions, parseCLIOptions } from './options'; import { launchStdioServer } from './stdio'; import { launchStreamableHTTPServer } from './http'; +import type { McpTool } from './types'; +import { configureLogger, getLogger } from './logger'; async function main() { const options = parseOptionsOrError(); + configureLogger({ + level: options.debug ? 'debug' : 'info', + pretty: options.logFormat === 'pretty', + }); - if (options.list) { - listAllTools(); - return; - } - - const selectedTools = await selectToolsOrError(endpoints, options); + const selectedTools = await selectToolsOrError(options); - console.error( - `MCP Server starting with ${selectedTools.length} tools:`, - selectedTools.map((e) => e.tool.name), + getLogger().info( + { tools: selectedTools.map((e) => e.tool.name) }, + `MCP Server starting with ${selectedTools.length} tools`, ); switch (options.transport) { @@ -26,14 +26,18 @@ async function main() { await launchStdioServer(options); break; case 'http': - await launchStreamableHTTPServer(options, options.port ?? options.socket); + await launchStreamableHTTPServer({ + mcpOptions: options, + port: options.socket ?? options.port, + }); break; } } if (require.main === module) { main().catch((error) => { - console.error('Fatal error in main():', error); + // Logger might not be initialized yet + console.error('Fatal error in main()', error); process.exit(1); }); } @@ -42,67 +46,22 @@ function parseOptionsOrError() { try { return parseCLIOptions(); } catch (error) { - console.error('Error parsing options:', error); + // Logger is initialized after options, so use console.error here + console.error('Error parsing options', error); process.exit(1); } } -async function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): Promise { +async function selectToolsOrError(options: McpOptions): Promise { try { - const includedTools = await selectTools(endpoints, options); + const includedTools = selectTools(options); if (includedTools.length === 0) { - console.error('No tools match the provided filters.'); + getLogger().error('No tools match the provided filters'); process.exit(1); } return includedTools; } catch (error) { - if (error instanceof Error) { - console.error('Error filtering tools:', error.message); - } else { - console.error('Error filtering tools:', error); - } + getLogger().error({ error }, 'Error filtering tools'); process.exit(1); } } - -function listAllTools() { - if (endpoints.length === 0) { - console.log('No tools available.'); - return; - } - console.log('Available tools:\n'); - - // Group endpoints by resource - const resourceGroups = new Map(); - - for (const endpoint of endpoints) { - const resource = endpoint.metadata.resource; - if (!resourceGroups.has(resource)) { - resourceGroups.set(resource, []); - } - resourceGroups.get(resource)!.push(endpoint); - } - - // Sort resources alphabetically - const sortedResources = Array.from(resourceGroups.keys()).sort(); - - // Display hierarchically by resource - for (const resource of sortedResources) { - console.log(`Resource: ${resource}`); - - const resourceEndpoints = resourceGroups.get(resource)!; - // Sort endpoints by tool name - resourceEndpoints.sort((a, b) => a.tool.name.localeCompare(b.tool.name)); - - for (const endpoint of resourceEndpoints) { - const { - tool, - metadata: { operation, tags }, - } = endpoint; - - console.log(` - ${tool.name} (${operation}) ${tags.length > 0 ? `tags: ${tags.join(', ')}` : ''}`); - console.log(` Description: ${tool.description}`); - } - console.log(''); - } -} diff --git a/packages/mcp-server/src/instructions.ts b/packages/mcp-server/src/instructions.ts new file mode 100644 index 00000000..c013483f --- /dev/null +++ b/packages/mcp-server/src/instructions.ts @@ -0,0 +1,83 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import fs from 'fs/promises'; +import { readEnv } from './util'; +import { getLogger } from './logger'; + +const INSTRUCTIONS_CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes + +interface InstructionsCacheEntry { + fetchedInstructions: string; + fetchedAt: number; +} + +const instructionsCache = new Map(); + +export async function getInstructions({ + stainlessApiKey, + customInstructionsPath, +}: { + stainlessApiKey?: string | undefined; + customInstructionsPath?: string | undefined; +}): Promise { + const now = Date.now(); + const cacheKey = customInstructionsPath ?? stainlessApiKey ?? ''; + const cached = instructionsCache.get(cacheKey); + + if (cached && now - cached.fetchedAt <= INSTRUCTIONS_CACHE_TTL_MS) { + return cached.fetchedInstructions; + } + + // Evict stale entries so the cache doesn't grow unboundedly. + for (const [key, entry] of instructionsCache) { + if (now - entry.fetchedAt > INSTRUCTIONS_CACHE_TTL_MS) { + instructionsCache.delete(key); + } + } + + let fetchedInstructions: string; + + if (customInstructionsPath) { + fetchedInstructions = await fetchLatestInstructionsFromFile(customInstructionsPath); + } else { + fetchedInstructions = await fetchLatestInstructionsFromApi(stainlessApiKey); + } + + instructionsCache.set(cacheKey, { fetchedInstructions, fetchedAt: now }); + return fetchedInstructions; +} + +async function fetchLatestInstructionsFromFile(path: string): Promise { + try { + return await fs.readFile(path, 'utf-8'); + } catch (error) { + getLogger().error({ error, path }, 'Error fetching instructions from file'); + throw error; + } +} + +async function fetchLatestInstructionsFromApi(stainlessApiKey: string | undefined): Promise { + // Setting the stainless API key is optional, but may be required + // to authenticate requests to the Stainless API. + const response = await fetch( + readEnv('CODE_MODE_INSTRUCTIONS_URL') ?? 'https://api.stainless.com/api/ai/instructions/writer', + { + method: 'GET', + headers: { ...(stainlessApiKey && { Authorization: stainlessApiKey }) }, + }, + ); + + let instructions: string | undefined; + if (!response.ok) { + getLogger().warn( + 'Warning: failed to retrieve MCP server instructions. Proceeding with default instructions...', + ); + + instructions = + '\n This is the writer MCP server.\n\n Available tools:\n - search_docs: Search SDK documentation to find the right methods and parameters.\n - execute: Run TypeScript code against a pre-authenticated SDK client. Define an async run(client) function.\n\n Workflow:\n - If unsure about the API, call search_docs first.\n - Write complete solutions in a single execute call when possible. For large datasets, use API filters to narrow results or paginate within a single execute block.\n - If execute returns an error, read the error and fix your code rather than retrying the same approach.\n - Variables do not persist between execute calls. Return or log all data you need.\n - Individual HTTP requests to the API have a 30-second timeout. If a request times out, try a smaller query or add filters.\n - Code execution has a total timeout of approximately 5 minutes. If your code times out, simplify it or break it into smaller steps.\n '; + } + + instructions ??= ((await response.json()) as { instructions: string }).instructions; + + return instructions; +} diff --git a/packages/mcp-server/src/local-docs-search.ts b/packages/mcp-server/src/local-docs-search.ts new file mode 100644 index 00000000..62f8ed04 --- /dev/null +++ b/packages/mcp-server/src/local-docs-search.ts @@ -0,0 +1,1574 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import MiniSearch from 'minisearch'; +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; +import { getLogger } from './logger'; + +type PerLanguageData = { + method?: string; + example?: string; +}; + +type MethodEntry = { + name: string; + endpoint: string; + httpMethod: string; + summary: string; + description: string; + stainlessPath: string; + qualified: string; + params?: string[]; + response?: string; + markdown?: string; + perLanguage?: Record; +}; + +type ProseChunk = { + content: string; + tag: string; + sectionContext?: string; + source?: string; +}; + +type MiniSearchDocument = { + id: string; + kind: 'http_method' | 'prose'; + name?: string; + endpoint?: string; + summary?: string; + description?: string; + qualified?: string; + stainlessPath?: string; + content?: string; + sectionContext?: string; + _original: Record; +}; + +type SearchResult = { + results: (string | Record)[]; +}; + +const EMBEDDED_METHODS: MethodEntry[] = [ + { + name: 'generate_content', + endpoint: '/v1/applications/{application_id}', + httpMethod: 'post', + summary: 'Generate from application', + description: + 'Generate content from an existing no-code agent (formerly called no-code applications) with inputs.', + stainlessPath: '(resource) applications > (method) generate_content', + qualified: 'client.applications.generateContent', + params: ['application_id: string;', 'inputs: { id: string; value: string[]; }[];', 'stream?: boolean;'], + response: '{ suggestion: string; title?: string; }', + markdown: + "## generate_content\n\n`client.applications.generateContent(application_id: string, inputs: { id: string; value: string[]; }[], stream?: boolean): { suggestion: string; title?: string; }`\n\n**post** `/v1/applications/{application_id}`\n\nGenerate content from an existing no-code agent (formerly called no-code applications) with inputs.\n\n### Parameters\n\n- `application_id: string`\n\n- `inputs: { id: string; value: string[]; }[]`\n\n- `stream?: boolean`\n Indicates whether the response should be streamed. Currently only supported for research assistant applications.\n\n### Returns\n\n- `{ suggestion: string; title?: string; }`\n\n - `suggestion: string`\n - `title?: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst stream = await client.applications.generateContent('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { inputs: [{ id: 'id', value: ['string'] }] });\nfor await (const applicationGenerateContentChunk of stream) {\n console.log(applicationGenerateContentChunk);\n}\n```", + perLanguage: { + go: { + method: 'client.Applications.GenerateContent', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tapplicationGenerateContentResponse, err := client.Applications.GenerateContent(\n\t\tcontext.TODO(),\n\t\t"182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",\n\t\twritersdk.ApplicationGenerateContentParams{\n\t\t\tInputs: writersdk.F([]writersdk.ApplicationGenerateContentParamsInput{{\n\t\t\t\tID: writersdk.F("id"),\n\t\t\t\tValue: writersdk.F([]string{"string"}),\n\t\t\t}}),\n\t\t},\n\t)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", applicationGenerateContentResponse.Suggestion)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/applications/$APPLICATION_ID \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "inputs": [\n {\n "id": "id",\n "value": [\n "string"\n ]\n }\n ]\n }\'', + }, + python: { + method: 'applications.generate_content', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nfor application in client.applications.generate_content(\n application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",\n inputs=[{\n "id": "id",\n "value": ["string"],\n }],\n):\n print(application)', + }, + typescript: { + method: 'client.applications.generateContent', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst applicationGenerateContentResponse = await client.applications.generateContent(\n '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e',\n { inputs: [{ id: 'id', value: ['string'] }] },\n);\n\nconsole.log(applicationGenerateContentResponse.suggestion);", + }, + }, + }, + { + name: 'list', + endpoint: '/v1/applications', + httpMethod: 'get', + summary: 'List applications', + description: + 'Retrieves a paginated list of no-code agents (formerly called no-code applications) with optional filtering and sorting capabilities.', + stainlessPath: '(resource) applications > (method) list', + qualified: 'client.applications.list', + params: [ + 'after?: string;', + 'before?: string;', + 'limit?: number;', + "order?: 'asc' | 'desc';", + "type?: 'generation';", + ], + response: + "{ id: string; created_at: string; inputs: { input_type: 'text' | 'dropdown' | 'file' | 'media'; name: string; required: boolean; description?: string; options?: { list: string[]; } | { file_types: string[]; max_file_size_mb: number; max_files: number; max_word_count: number; upload_types: 'url' | 'file_id'[]; } | { file_types: string[]; max_image_size_mb: number; } | { max_fields: number; min_fields: number; }; }[]; name: string; status: 'deployed' | 'draft'; type: 'generation'; updated_at: string; last_deployed_at?: string; }", + markdown: + "## list\n\n`client.applications.list(after?: string, before?: string, limit?: number, order?: 'asc' | 'desc', type?: 'generation'): { id: string; created_at: string; inputs: object[]; name: string; status: 'deployed' | 'draft'; type: 'generation'; updated_at: string; last_deployed_at?: string; }`\n\n**get** `/v1/applications`\n\nRetrieves a paginated list of no-code agents (formerly called no-code applications) with optional filtering and sorting capabilities.\n\n### Parameters\n\n- `after?: string`\n Return results after this application ID for pagination.\n\n- `before?: string`\n Return results before this application ID for pagination.\n\n- `limit?: number`\n Maximum number of applications to return in the response.\n\n- `order?: 'asc' | 'desc'`\n Sort order for the results based on creation time.\n\n- `type?: 'generation'`\n Filter applications by their type.\n\n### Returns\n\n- `{ id: string; created_at: string; inputs: { input_type: 'text' | 'dropdown' | 'file' | 'media'; name: string; required: boolean; description?: string; options?: { list: string[]; } | { file_types: string[]; max_file_size_mb: number; max_files: number; max_word_count: number; upload_types: 'url' | 'file_id'[]; } | { file_types: string[]; max_image_size_mb: number; } | { max_fields: number; min_fields: number; }; }[]; name: string; status: 'deployed' | 'draft'; type: 'generation'; updated_at: string; last_deployed_at?: string; }`\n Detailed application object including its input configuration.\n\n - `id: string`\n - `created_at: string`\n - `inputs: { input_type: 'text' | 'dropdown' | 'file' | 'media'; name: string; required: boolean; description?: string; options?: { list: string[]; } | { file_types: string[]; max_file_size_mb: number; max_files: number; max_word_count: number; upload_types: 'url' | 'file_id'[]; } | { file_types: string[]; max_image_size_mb: number; } | { max_fields: number; min_fields: number; }; }[]`\n - `name: string`\n - `status: 'deployed' | 'draft'`\n - `type: 'generation'`\n - `updated_at: string`\n - `last_deployed_at?: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\n// Automatically fetches more pages as needed.\nfor await (const applicationListResponse of client.applications.list()) {\n console.log(applicationListResponse);\n}\n```", + perLanguage: { + go: { + method: 'client.Applications.List', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tpage, err := client.Applications.List(context.TODO(), writersdk.ApplicationListParams{})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", page)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/applications \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'applications.list', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\npage = client.applications.list()\npage = page.data[0]\nprint(page.id)', + }, + typescript: { + method: 'client.applications.list', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\n// Automatically fetches more pages as needed.\nfor await (const applicationListResponse of client.applications.list()) {\n console.log(applicationListResponse.id);\n}", + }, + }, + }, + { + name: 'retrieve', + endpoint: '/v1/applications/{application_id}', + httpMethod: 'get', + summary: 'Application details', + description: + 'Retrieves detailed information for a specific no-code agent (formerly called no-code applications), including its configuration and current status.', + stainlessPath: '(resource) applications > (method) retrieve', + qualified: 'client.applications.retrieve', + params: ['application_id: string;'], + response: + "{ id: string; created_at: string; inputs: { input_type: 'text' | 'dropdown' | 'file' | 'media'; name: string; required: boolean; description?: string; options?: { list: string[]; } | { file_types: string[]; max_file_size_mb: number; max_files: number; max_word_count: number; upload_types: 'url' | 'file_id'[]; } | { file_types: string[]; max_image_size_mb: number; } | { max_fields: number; min_fields: number; }; }[]; name: string; status: 'deployed' | 'draft'; type: 'generation'; updated_at: string; last_deployed_at?: string; }", + markdown: + "## retrieve\n\n`client.applications.retrieve(application_id: string): { id: string; created_at: string; inputs: object[]; name: string; status: 'deployed' | 'draft'; type: 'generation'; updated_at: string; last_deployed_at?: string; }`\n\n**get** `/v1/applications/{application_id}`\n\nRetrieves detailed information for a specific no-code agent (formerly called no-code applications), including its configuration and current status.\n\n### Parameters\n\n- `application_id: string`\n\n### Returns\n\n- `{ id: string; created_at: string; inputs: { input_type: 'text' | 'dropdown' | 'file' | 'media'; name: string; required: boolean; description?: string; options?: { list: string[]; } | { file_types: string[]; max_file_size_mb: number; max_files: number; max_word_count: number; upload_types: 'url' | 'file_id'[]; } | { file_types: string[]; max_image_size_mb: number; } | { max_fields: number; min_fields: number; }; }[]; name: string; status: 'deployed' | 'draft'; type: 'generation'; updated_at: string; last_deployed_at?: string; }`\n Detailed application object including its input configuration.\n\n - `id: string`\n - `created_at: string`\n - `inputs: { input_type: 'text' | 'dropdown' | 'file' | 'media'; name: string; required: boolean; description?: string; options?: { list: string[]; } | { file_types: string[]; max_file_size_mb: number; max_files: number; max_word_count: number; upload_types: 'url' | 'file_id'[]; } | { file_types: string[]; max_image_size_mb: number; } | { max_fields: number; min_fields: number; }; }[]`\n - `name: string`\n - `status: 'deployed' | 'draft'`\n - `type: 'generation'`\n - `updated_at: string`\n - `last_deployed_at?: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst application = await client.applications.retrieve('application_id');\n\nconsole.log(application);\n```", + perLanguage: { + go: { + method: 'client.Applications.Get', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tapplication, err := client.Applications.Get(context.TODO(), "application_id")\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", application.ID)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/applications/$APPLICATION_ID \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'applications.retrieve', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\napplication = client.applications.retrieve(\n "application_id",\n)\nprint(application.id)', + }, + typescript: { + method: 'client.applications.retrieve', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst application = await client.applications.retrieve('application_id');\n\nconsole.log(application.id);", + }, + }, + }, + { + name: 'list', + endpoint: '/v1/applications/{application_id}/jobs', + httpMethod: 'get', + summary: 'Retrieve all jobs', + description: + 'Retrieve all jobs created via the async API, linked to the provided application ID (or alias).', + stainlessPath: '(resource) applications.jobs > (method) list', + qualified: 'client.applications.jobs.list', + params: [ + 'application_id: string;', + 'limit?: number;', + 'offset?: number;', + "status?: 'in_progress' | 'failed' | 'completed';", + ], + response: + "{ id: string; application_id: string; created_at: string; status: 'in_progress' | 'failed' | 'completed'; completed_at?: string; data?: { suggestion: string; title?: string; }; error?: string; updated_at?: string; }", + markdown: + "## list\n\n`client.applications.jobs.list(application_id: string, limit?: number, offset?: number, status?: 'in_progress' | 'failed' | 'completed'): { id: string; application_id: string; created_at: string; status: 'in_progress' | 'failed' | 'completed'; completed_at?: string; data?: application_generate_content_response; error?: string; updated_at?: string; }`\n\n**get** `/v1/applications/{application_id}/jobs`\n\nRetrieve all jobs created via the async API, linked to the provided application ID (or alias).\n\n### Parameters\n\n- `application_id: string`\n\n- `limit?: number`\n The pagination limit for retrieving the jobs.\n\n- `offset?: number`\n The pagination offset for retrieving the jobs.\n\n- `status?: 'in_progress' | 'failed' | 'completed'`\n The status of the job.\n\n### Returns\n\n- `{ id: string; application_id: string; created_at: string; status: 'in_progress' | 'failed' | 'completed'; completed_at?: string; data?: { suggestion: string; title?: string; }; error?: string; updated_at?: string; }`\n\n - `id: string`\n - `application_id: string`\n - `created_at: string`\n - `status: 'in_progress' | 'failed' | 'completed'`\n - `completed_at?: string`\n - `data?: { suggestion: string; title?: string; }`\n - `error?: string`\n - `updated_at?: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\n// Automatically fetches more pages as needed.\nfor await (const applicationGenerateAsyncResponse of client.applications.jobs.list('application_id')) {\n console.log(applicationGenerateAsyncResponse);\n}\n```", + perLanguage: { + go: { + method: 'client.Applications.Jobs.List', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tpage, err := client.Applications.Jobs.List(\n\t\tcontext.TODO(),\n\t\t"application_id",\n\t\twritersdk.ApplicationJobListParams{},\n\t)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", page)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/applications/$APPLICATION_ID/jobs \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'applications.jobs.list', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\npage = client.applications.jobs.list(\n application_id="application_id",\n)\npage = page.result[0]\nprint(page.id)', + }, + typescript: { + method: 'client.applications.jobs.list', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\n// Automatically fetches more pages as needed.\nfor await (const applicationGenerateAsyncResponse of client.applications.jobs.list(\n 'application_id',\n)) {\n console.log(applicationGenerateAsyncResponse.id);\n}", + }, + }, + }, + { + name: 'create', + endpoint: '/v1/applications/{application_id}/jobs', + httpMethod: 'post', + summary: 'Generate from application (async)', + description: + 'Generate content asynchronously from an existing no-code agent (formerly called no-code applications) with inputs.', + stainlessPath: '(resource) applications.jobs > (method) create', + qualified: 'client.applications.jobs.create', + params: ['application_id: string;', 'inputs: { id: string; value: string[]; }[];'], + response: "{ id: string; created_at: string; status: 'in_progress' | 'failed' | 'completed'; }", + markdown: + "## create\n\n`client.applications.jobs.create(application_id: string, inputs: { id: string; value: string[]; }[]): { id: string; created_at: string; status: 'in_progress' | 'failed' | 'completed'; }`\n\n**post** `/v1/applications/{application_id}/jobs`\n\nGenerate content asynchronously from an existing no-code agent (formerly called no-code applications) with inputs.\n\n### Parameters\n\n- `application_id: string`\n\n- `inputs: { id: string; value: string[]; }[]`\n A list of input objects to generate content for.\n\n### Returns\n\n- `{ id: string; created_at: string; status: 'in_progress' | 'failed' | 'completed'; }`\n\n - `id: string`\n - `created_at: string`\n - `status: 'in_progress' | 'failed' | 'completed'`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst job = await client.applications.jobs.create('application_id', { inputs: [{ id: 'id', value: ['string'] }] });\n\nconsole.log(job);\n```", + perLanguage: { + go: { + method: 'client.Applications.Jobs.New', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tjob, err := client.Applications.Jobs.New(\n\t\tcontext.TODO(),\n\t\t"application_id",\n\t\twritersdk.ApplicationJobNewParams{\n\t\t\tInputs: writersdk.F([]writersdk.ApplicationJobNewParamsInput{{\n\t\t\t\tID: writersdk.F("id"),\n\t\t\t\tValue: writersdk.F([]string{"string"}),\n\t\t\t}}),\n\t\t},\n\t)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", job.ID)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/applications/$APPLICATION_ID/jobs \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "inputs": [\n {\n "id": "id",\n "value": [\n "string"\n ]\n }\n ]\n }\'', + }, + python: { + method: 'applications.jobs.create', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\njob = client.applications.jobs.create(\n application_id="application_id",\n inputs=[{\n "id": "id",\n "value": ["string"],\n }],\n)\nprint(job.id)', + }, + typescript: { + method: 'client.applications.jobs.create', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst job = await client.applications.jobs.create('application_id', {\n inputs: [{ id: 'id', value: ['string'] }],\n});\n\nconsole.log(job.id);", + }, + }, + }, + { + name: 'retry', + endpoint: '/v1/applications/jobs/{job_id}/retry', + httpMethod: 'post', + summary: 'Retry job execution', + description: + 'Re-triggers the async execution of a single job previously created via the Async api and terminated in error.', + stainlessPath: '(resource) applications.jobs > (method) retry', + qualified: 'client.applications.jobs.retry', + params: ['job_id: string;'], + response: "{ id: string; created_at: string; status: 'in_progress' | 'failed' | 'completed'; }", + markdown: + "## retry\n\n`client.applications.jobs.retry(job_id: string): { id: string; created_at: string; status: 'in_progress' | 'failed' | 'completed'; }`\n\n**post** `/v1/applications/jobs/{job_id}/retry`\n\nRe-triggers the async execution of a single job previously created via the Async api and terminated in error.\n\n### Parameters\n\n- `job_id: string`\n\n### Returns\n\n- `{ id: string; created_at: string; status: 'in_progress' | 'failed' | 'completed'; }`\n\n - `id: string`\n - `created_at: string`\n - `status: 'in_progress' | 'failed' | 'completed'`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst response = await client.applications.jobs.retry('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e');\n\nconsole.log(response);\n```", + perLanguage: { + go: { + method: 'client.Applications.Jobs.Retry', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tresponse, err := client.Applications.Jobs.Retry(context.TODO(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", response.ID)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/applications/jobs/$JOB_ID/retry \\\n -X POST \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'applications.jobs.retry', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nresponse = client.applications.jobs.retry(\n "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",\n)\nprint(response.id)', + }, + typescript: { + method: 'client.applications.jobs.retry', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst response = await client.applications.jobs.retry('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e');\n\nconsole.log(response.id);", + }, + }, + }, + { + name: 'retrieve', + endpoint: '/v1/applications/jobs/{job_id}', + httpMethod: 'get', + summary: 'Retrieve a single job', + description: 'Retrieves a single job created via the Async API.', + stainlessPath: '(resource) applications.jobs > (method) retrieve', + qualified: 'client.applications.jobs.retrieve', + params: ['job_id: string;'], + response: + "{ id: string; application_id: string; created_at: string; status: 'in_progress' | 'failed' | 'completed'; completed_at?: string; data?: { suggestion: string; title?: string; }; error?: string; updated_at?: string; }", + markdown: + "## retrieve\n\n`client.applications.jobs.retrieve(job_id: string): { id: string; application_id: string; created_at: string; status: 'in_progress' | 'failed' | 'completed'; completed_at?: string; data?: application_generate_content_response; error?: string; updated_at?: string; }`\n\n**get** `/v1/applications/jobs/{job_id}`\n\nRetrieves a single job created via the Async API.\n\n### Parameters\n\n- `job_id: string`\n\n### Returns\n\n- `{ id: string; application_id: string; created_at: string; status: 'in_progress' | 'failed' | 'completed'; completed_at?: string; data?: { suggestion: string; title?: string; }; error?: string; updated_at?: string; }`\n\n - `id: string`\n - `application_id: string`\n - `created_at: string`\n - `status: 'in_progress' | 'failed' | 'completed'`\n - `completed_at?: string`\n - `data?: { suggestion: string; title?: string; }`\n - `error?: string`\n - `updated_at?: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst applicationGenerateAsyncResponse = await client.applications.jobs.retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e');\n\nconsole.log(applicationGenerateAsyncResponse);\n```", + perLanguage: { + go: { + method: 'client.Applications.Jobs.Get', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tapplicationGenerateAsyncResponse, err := client.Applications.Jobs.Get(context.TODO(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", applicationGenerateAsyncResponse.ID)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/applications/jobs/$JOB_ID \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'applications.jobs.retrieve', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\napplication_generate_async_response = client.applications.jobs.retrieve(\n "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",\n)\nprint(application_generate_async_response.id)', + }, + typescript: { + method: 'client.applications.jobs.retrieve', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst applicationGenerateAsyncResponse = await client.applications.jobs.retrieve(\n '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e',\n);\n\nconsole.log(applicationGenerateAsyncResponse.id);", + }, + }, + }, + { + name: 'list', + endpoint: '/v1/applications/{application_id}/graphs', + httpMethod: 'get', + summary: 'Retrieve graphs', + description: 'Retrieve Knowledge Graphs associated with a no-code agent that has chat capabilities.', + stainlessPath: '(resource) applications.graphs > (method) list', + qualified: 'client.applications.graphs.list', + params: ['application_id: string;'], + response: '{ graph_ids: string[]; }', + markdown: + "## list\n\n`client.applications.graphs.list(application_id: string): { graph_ids: string[]; }`\n\n**get** `/v1/applications/{application_id}/graphs`\n\nRetrieve Knowledge Graphs associated with a no-code agent that has chat capabilities.\n\n### Parameters\n\n- `application_id: string`\n\n### Returns\n\n- `{ graph_ids: string[]; }`\n\n - `graph_ids: string[]`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst applicationGraphsResponse = await client.applications.graphs.list('application_id');\n\nconsole.log(applicationGraphsResponse);\n```", + perLanguage: { + go: { + method: 'client.Applications.Graphs.List', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tapplicationGraphsResponse, err := client.Applications.Graphs.List(context.TODO(), "application_id")\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", applicationGraphsResponse.GraphIDs)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/applications/$APPLICATION_ID/graphs \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'applications.graphs.list', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\napplication_graphs_response = client.applications.graphs.list(\n "application_id",\n)\nprint(application_graphs_response.graph_ids)', + }, + typescript: { + method: 'client.applications.graphs.list', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst applicationGraphsResponse = await client.applications.graphs.list('application_id');\n\nconsole.log(applicationGraphsResponse.graph_ids);", + }, + }, + }, + { + name: 'update', + endpoint: '/v1/applications/{application_id}/graphs', + httpMethod: 'put', + summary: 'Associate graphs', + description: 'Updates the list of Knowledge Graphs associated with a no-code chat agent.', + stainlessPath: '(resource) applications.graphs > (method) update', + qualified: 'client.applications.graphs.update', + params: ['application_id: string;', 'graph_ids: string[];'], + response: '{ graph_ids: string[]; }', + markdown: + "## update\n\n`client.applications.graphs.update(application_id: string, graph_ids: string[]): { graph_ids: string[]; }`\n\n**put** `/v1/applications/{application_id}/graphs`\n\nUpdates the list of Knowledge Graphs associated with a no-code chat agent.\n\n### Parameters\n\n- `application_id: string`\n\n- `graph_ids: string[]`\n A list of Knowledge Graph IDs to associate with the application. Note that this will replace the existing list of Knowledge Graphs associated with the application, not add to it.\n\n### Returns\n\n- `{ graph_ids: string[]; }`\n\n - `graph_ids: string[]`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst applicationGraphsResponse = await client.applications.graphs.update('application_id', { graph_ids: ['182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'] });\n\nconsole.log(applicationGraphsResponse);\n```", + perLanguage: { + go: { + method: 'client.Applications.Graphs.Update', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tapplicationGraphsResponse, err := client.Applications.Graphs.Update(\n\t\tcontext.TODO(),\n\t\t"application_id",\n\t\twritersdk.ApplicationGraphUpdateParams{\n\t\t\tGraphIDs: writersdk.F([]string{"182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"}),\n\t\t},\n\t)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", applicationGraphsResponse.GraphIDs)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/applications/$APPLICATION_ID/graphs \\\n -X PUT \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "graph_ids": [\n "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"\n ]\n }\'', + }, + python: { + method: 'applications.graphs.update', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\napplication_graphs_response = client.applications.graphs.update(\n application_id="application_id",\n graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],\n)\nprint(application_graphs_response.graph_ids)', + }, + typescript: { + method: 'client.applications.graphs.update', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst applicationGraphsResponse = await client.applications.graphs.update('application_id', {\n graph_ids: ['182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'],\n});\n\nconsole.log(applicationGraphsResponse.graph_ids);", + }, + }, + }, + { + name: 'chat', + endpoint: '/v1/chat', + httpMethod: 'post', + summary: 'Chat completion', + description: + 'Generate a chat completion based on the provided messages. The response shown below is for non-streaming. To learn about streaming responses, see the [chat completion guide](https://dev.writer.com/home/chat-completion).', + stainlessPath: '(resource) chat > (method) chat', + qualified: 'client.chat.chat', + params: [ + "messages: { role: 'user' | 'assistant' | 'system' | 'tool'; content?: string | { text: string; type: 'text'; } | { image_url: { url: string; }; type: 'image_url'; }[]; graph_data?: { references?: { files?: object[]; web?: object[]; }; sources?: object[]; status?: 'processing' | 'finished'; subqueries?: { answer: string; query: string; sources: source[]; }[]; }; name?: string; refusal?: string; tool_call_id?: string; tool_calls?: { id: string; function: { arguments: string; name?: string; }; type: 'function'; index?: number; }[]; }[];", + 'model: string;', + 'logprobs?: boolean;', + 'max_tokens?: number;', + 'n?: number;', + "response_format?: { type: 'text' | 'json_schema'; json_schema?: object; };", + 'stop?: string[] | string;', + 'stream?: boolean;', + 'stream_options?: { include_usage: boolean; };', + 'temperature?: number;', + "tool_choice?: { value: 'none' | 'auto' | 'required'; } | { value: object; };", + "tools?: { function: { name: string; description?: string; parameters?: function_params; }; type: 'function'; } | { function: { graph_ids: string[]; subqueries: boolean; description?: string; query_config?: { grounding_level?: number; inline_citations?: boolean; keyword_threshold?: number; max_snippets?: number; max_subquestions?: number; max_tokens?: number; search_weight?: number; semantic_threshold?: number; }; }; type: 'graph'; } | { function: { description: string; model: string; }; type: 'llm'; } | { function: { formality: boolean; length_control: boolean; mask_profanity: boolean; model: 'palmyra-translate'; source_language_code?: string; target_language_code?: string; }; type: 'translation'; } | { function: { model: 'palmyra-vision'; variables: { file_id: string; name: string; }[]; }; type: 'vision'; } | { function: { exclude_domains: string[]; include_domains: string[]; }; type: 'web_search'; }[];", + 'top_p?: number;', + ], + response: + "{ id: string; choices: { finish_reason: 'stop' | 'length' | 'content_filter' | 'tool_calls'; index: number; message: chat_completion_message; logprobs?: logprobs; }[]; created: number; model: string; object: 'chat.completion'; service_tier?: string; system_fingerprint?: string; usage?: { completion_tokens: number; prompt_tokens: number; total_tokens: number; completion_tokens_details?: object; prompt_token_details?: object; }; }", + markdown: + "## chat\n\n`client.chat.chat(messages: { role: 'user' | 'assistant' | 'system' | 'tool'; content?: string | { text: string; type: 'text'; } | { image_url: object; type: 'image_url'; }[]; graph_data?: object; name?: string; refusal?: string; tool_call_id?: string; tool_calls?: object[]; }[], model: string, logprobs?: boolean, max_tokens?: number, n?: number, response_format?: { type: 'text' | 'json_schema'; json_schema?: object; }, stop?: string[] | string, stream?: boolean, stream_options?: { include_usage: boolean; }, temperature?: number, tool_choice?: { value: 'none' | 'auto' | 'required'; } | { value: object; }, tools?: { function: function_definition; type: 'function'; } | { function: object; type: 'graph'; } | { function: object; type: 'llm'; } | { function: object; type: 'translation'; } | { function: object; type: 'vision'; } | { function: object; type: 'web_search'; }[], top_p?: number): { id: string; choices: chat_completion_choice[]; created: number; model: string; object: 'chat.completion'; service_tier?: string; system_fingerprint?: string; usage?: chat_completion_usage; }`\n\n**post** `/v1/chat`\n\nGenerate a chat completion based on the provided messages. The response shown below is for non-streaming. To learn about streaming responses, see the [chat completion guide](https://dev.writer.com/home/chat-completion).\n\n### Parameters\n\n- `messages: { role: 'user' | 'assistant' | 'system' | 'tool'; content?: string | { text: string; type: 'text'; } | { image_url: { url: string; }; type: 'image_url'; }[]; graph_data?: { references?: { files?: object[]; web?: object[]; }; sources?: object[]; status?: 'processing' | 'finished'; subqueries?: { answer: string; query: string; sources: source[]; }[]; }; name?: string; refusal?: string; tool_call_id?: string; tool_calls?: { id: string; function: { arguments: string; name?: string; }; type: 'function'; index?: number; }[]; }[]`\n An array of message objects that form the conversation history or context for the model to respond to. The array must contain at least one message.\n\n- `model: string`\n The [ID of the model](https://dev.writer.com/home/models) to use for creating the chat completion. Supports `palmyra-x5`, `palmyra-x4`, `palmyra-fin`, `palmyra-med`, `palmyra-creative`, and `palmyra-x-003-instruct`.\n\n- `logprobs?: boolean`\n Specifies whether to return log probabilities of the output tokens.\n\n- `max_tokens?: number`\n Defines the maximum number of tokens (words and characters) that the model can generate in the response. This can be adjusted to allow for longer or shorter responses as needed. The maximum value varies by model. See the [models overview](/home/models) for more information about the maximum number of tokens for each model.\n\n- `n?: number`\n Specifies the number of completions (responses) to generate from the model in a single request. This parameter allows for generating multiple responses, offering a variety of potential replies from which to choose.\n\n- `response_format?: { type: 'text' | 'json_schema'; json_schema?: object; }`\n The response format to use for the chat completion, available with `palmyra-x4` and `palmyra-x5`.\n\n`text` is the default response format. [JSON Schema](https://json-schema.org/) is supported for structured responses. If you specify `json_schema`, you must also provide a `json_schema` object.\n - `type: 'text' | 'json_schema'`\n The type of response format to use.\n - `json_schema?: object`\n The JSON schema to use for the response format.\n\n- `stop?: string[] | string`\n A token or sequence of tokens that, when generated, will cause the model to stop producing further content. This can be a single token or an array of tokens, acting as a signal to end the output.\n\n- `stream?: boolean`\n Indicates whether the response should be streamed incrementally as it is generated or only returned once fully complete. Streaming can be useful for providing real-time feedback in interactive applications.\n\n- `stream_options?: { include_usage: boolean; }`\n Additional options for streaming.\n - `include_usage: boolean`\n Indicate whether to include usage information.\n\n- `temperature?: number`\n Controls the randomness or creativity of the model's responses. A higher temperature results in more varied and less predictable text, while a lower temperature produces more deterministic and conservative outputs.\n\n- `tool_choice?: { value: 'none' | 'auto' | 'required'; } | { value: object; }`\n Configure how the model will call functions:\n- `auto`: allows the model to automatically choose the tool to use, or not call a tool\n- `none`: disables tool calling; the model will instead generate a message\n- `required`: requires the model to call one or more tools\n\nYou can also use a JSON object to force the model to call a specific tool. For example, `{\"type\": \"function\", \"function\": {\"name\": \"get_current_weather\"}}` requires the model to call the `get_current_weather` function, regardless of the prompt.\n\n- `tools?: { function: { name: string; description?: string; parameters?: function_params; }; type: 'function'; } | { function: { graph_ids: string[]; subqueries: boolean; description?: string; query_config?: { grounding_level?: number; inline_citations?: boolean; keyword_threshold?: number; max_snippets?: number; max_subquestions?: number; max_tokens?: number; search_weight?: number; semantic_threshold?: number; }; }; type: 'graph'; } | { function: { description: string; model: string; }; type: 'llm'; } | { function: { formality: boolean; length_control: boolean; mask_profanity: boolean; model: 'palmyra-translate'; source_language_code?: string; target_language_code?: string; }; type: 'translation'; } | { function: { model: 'palmyra-vision'; variables: { file_id: string; name: string; }[]; }; type: 'vision'; } | { function: { exclude_domains: string[]; include_domains: string[]; }; type: 'web_search'; }[]`\n An array containing tool definitions for tools that the model can use to generate responses. The tool definitions use JSON schema. You can define your own functions or use one of the built-in `graph`, `llm`, `translation`, or `vision` tools. Note that you can only use one built-in tool type in the array (only one of `graph`, `llm`, `translation`, or `vision`). You can pass multiple [custom tools](https://dev.writer.com/home/tool-calling) of type `function` in the same request.\n\n- `top_p?: number`\n Sets the threshold for \"nucleus sampling,\" a technique to focus the model's token generation on the most likely subset of tokens. Only tokens with cumulative probability above this threshold are considered, controlling the trade-off between creativity and coherence.\n\n### Returns\n\n- `{ id: string; choices: { finish_reason: 'stop' | 'length' | 'content_filter' | 'tool_calls'; index: number; message: chat_completion_message; logprobs?: logprobs; }[]; created: number; model: string; object: 'chat.completion'; service_tier?: string; system_fingerprint?: string; usage?: { completion_tokens: number; prompt_tokens: number; total_tokens: number; completion_tokens_details?: object; prompt_token_details?: object; }; }`\n\n - `id: string`\n - `choices: { finish_reason: 'stop' | 'length' | 'content_filter' | 'tool_calls'; index: number; message: { content: string; refusal: string; role: 'assistant'; graph_data?: graph_data; llm_data?: object; tool_calls?: tool_call[]; translation_data?: object; web_search_data?: object; }; logprobs?: { content: logprobs_token[]; refusal: logprobs_token[]; }; }[]`\n - `created: number`\n - `model: string`\n - `object: 'chat.completion'`\n - `service_tier?: string`\n - `system_fingerprint?: string`\n - `usage?: { completion_tokens: number; prompt_tokens: number; total_tokens: number; completion_tokens_details?: { reasoning_tokens: number; }; prompt_token_details?: { cached_tokens: number; }; }`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst stream = await client.chat.chat({ messages: [{ role: 'user' }], model: 'model' });\nfor await (const chatCompletionChunk of stream) {\n console.log(chatCompletionChunk);\n}\n```", + perLanguage: { + go: { + method: 'client.Chat.Chat', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tchatCompletion, err := client.Chat.Chat(context.TODO(), writersdk.ChatChatParams{\n\t\tMessages: writersdk.F([]writersdk.ChatChatParamsMessage{{\n\t\t\tRole: writersdk.F(writersdk.ChatChatParamsMessagesRoleUser),\n\t\t}}),\n\t\tModel: writersdk.F("model"),\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", chatCompletion.ID)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/chat \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "messages": [\n {\n "role": "user"\n }\n ],\n "model": "model"\n }\'', + }, + python: { + method: 'chat.chat', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nfor chat in client.chat.chat(\n messages=[{\n "role": "user"\n }],\n model="model",\n):\n print(chat)', + }, + typescript: { + method: 'client.chat.chat', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst chatCompletion = await client.chat.chat({ messages: [{ role: 'user' }], model: 'model' });\n\nconsole.log(chatCompletion.id);", + }, + }, + }, + { + name: 'create', + endpoint: '/v1/completions', + httpMethod: 'post', + summary: 'Text generation', + description: + "Generate text completions using the specified model and prompt. This endpoint is useful for text generation tasks that don't require conversational context.", + stainlessPath: '(resource) completions > (method) create', + qualified: 'client.completions.create', + params: [ + 'model: string;', + 'prompt: string;', + 'best_of?: number;', + 'max_tokens?: number;', + 'random_seed?: number;', + 'stop?: string[] | string;', + 'stream?: boolean;', + 'temperature?: number;', + 'top_p?: number;', + ], + response: '{ choices: { text: string; log_probs?: object; }[]; model?: string; }', + markdown: + "## create\n\n`client.completions.create(model: string, prompt: string, best_of?: number, max_tokens?: number, random_seed?: number, stop?: string[] | string, stream?: boolean, temperature?: number, top_p?: number): { choices: object[]; model?: string; }`\n\n**post** `/v1/completions`\n\nGenerate text completions using the specified model and prompt. This endpoint is useful for text generation tasks that don't require conversational context.\n\n### Parameters\n\n- `model: string`\n The [ID of the model](https://dev.writer.com/home/models) to use for generating text. Supports `palmyra-x5`, `palmyra-x4`, `palmyra-fin`, `palmyra-med`, `palmyra-creative`, and `palmyra-x-003-instruct`.\n\n- `prompt: string`\n The input text that the model will process to generate a response.\n\n- `best_of?: number`\n Specifies the number of completions to generate and return the best one. Useful for generating multiple outputs and choosing the best based on some criteria.\n\n- `max_tokens?: number`\n The maximum number of tokens that the model can generate in the response.\n\n- `random_seed?: number`\n A seed used to initialize the random number generator for the model, ensuring reproducibility of the output when the same inputs are provided.\n\n- `stop?: string[] | string`\n Specifies stopping conditions for the model's output generation. This can be an array of strings or a single string that the model will look for as a signal to stop generating further tokens.\n\n- `stream?: boolean`\n Determines whether the model's output should be streamed. If true, the output is generated and sent incrementally, which can be useful for real-time applications.\n\n- `temperature?: number`\n Controls the randomness of the model's outputs. Higher values lead to more random outputs, while lower values make the model more deterministic.\n\n- `top_p?: number`\n Used to control the nucleus sampling, where only the most probable tokens with a cumulative probability of top_p are considered for sampling, providing a way to fine-tune the randomness of predictions.\n\n### Returns\n\n- `{ choices: { text: string; log_probs?: object; }[]; model?: string; }`\n\n - `choices: { text: string; log_probs?: { content: object[]; refusal: object[]; }; }[]`\n - `model?: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst stream = await client.completions.create({ model: 'palmyra-x-003-instruct', prompt: 'Write me an SEO article about...' });\nfor await (const completionChunk of stream) {\n console.log(completionChunk);\n}\n```", + perLanguage: { + go: { + method: 'client.Completions.New', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tcompletion, err := client.Completions.New(context.TODO(), writersdk.CompletionNewParams{\n\t\tModel: writersdk.F("palmyra-x-003-instruct"),\n\t\tPrompt: writersdk.F("Write me an SEO article about..."),\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", completion.Choices)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/completions \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "model": "palmyra-x-003-instruct",\n "prompt": "Write me an SEO article about..."\n }\'', + }, + python: { + method: 'completions.create', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nfor completion in client.completions.create(\n model="palmyra-x-003-instruct",\n prompt="Write me an SEO article about...",\n):\n print(completion)', + }, + typescript: { + method: 'client.completions.create', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst completion = await client.completions.create({\n model: 'palmyra-x-003-instruct',\n prompt: 'Write me an SEO article about...',\n});\n\nconsole.log(completion.choices);", + }, + }, + }, + { + name: 'list', + endpoint: '/v1/models', + httpMethod: 'get', + summary: 'List models', + description: + 'Retrieve a list of available models that can be used for text generation, chat completions, and other AI tasks.', + stainlessPath: '(resource) models > (method) list', + qualified: 'client.models.list', + response: '{ models: { id: string; name: string; }[]; }', + markdown: + "## list\n\n`client.models.list(): { models: object[]; }`\n\n**get** `/v1/models`\n\nRetrieve a list of available models that can be used for text generation, chat completions, and other AI tasks.\n\n### Returns\n\n- `{ models: { id: string; name: string; }[]; }`\n\n - `models: { id: string; name: string; }[]`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst models = await client.models.list();\n\nconsole.log(models);\n```", + perLanguage: { + go: { + method: 'client.Models.List', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tmodels, err := client.Models.List(context.TODO())\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", models.Models)\n}\n', + }, + http: { + example: 'curl https://api.writer.com/v1/models \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'models.list', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nmodels = client.models.list()\nprint(models.models)', + }, + typescript: { + method: 'client.models.list', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst models = await client.models.list();\n\nconsole.log(models.models);", + }, + }, + }, + { + name: 'list', + endpoint: '/v1/graphs', + httpMethod: 'get', + summary: 'List graphs', + description: 'Retrieve a list of Knowledge Graphs.', + stainlessPath: '(resource) graphs > (method) list', + qualified: 'client.graphs.list', + params: ['after?: string;', 'before?: string;', 'limit?: number;', "order?: 'asc' | 'desc';"], + response: + "{ id: string; created_at: string; file_status: { completed: number; failed: number; in_progress: number; total: number; }; name: string; type: 'manual' | 'connector' | 'web'; description?: string; urls?: { status: { status: 'validating' | 'success' | 'error'; error_type?: 'invalid_url' | 'not_searchable' | 'not_found' | 'paywall_or_login_page' | 'unexpected_error'; }; type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]; }", + markdown: + "## list\n\n`client.graphs.list(after?: string, before?: string, limit?: number, order?: 'asc' | 'desc'): { id: string; created_at: string; file_status: object; name: string; type: 'manual' | 'connector' | 'web'; description?: string; urls?: object[]; }`\n\n**get** `/v1/graphs`\n\nRetrieve a list of Knowledge Graphs.\n\n### Parameters\n\n- `after?: string`\n The ID of the last object in the previous page. This parameter instructs the API to return the next page of results.\n\n- `before?: string`\n The ID of the first object in the previous page. This parameter instructs the API to return the previous page of results.\n\n- `limit?: number`\n Specifies the maximum number of objects returned in a page. The default value is 50. The minimum value is 1, and the maximum value is 100.\n\n- `order?: 'asc' | 'desc'`\n Specifies the order of the results. Valid values are asc for ascending and desc for descending.\n\n### Returns\n\n- `{ id: string; created_at: string; file_status: { completed: number; failed: number; in_progress: number; total: number; }; name: string; type: 'manual' | 'connector' | 'web'; description?: string; urls?: { status: { status: 'validating' | 'success' | 'error'; error_type?: 'invalid_url' | 'not_searchable' | 'not_found' | 'paywall_or_login_page' | 'unexpected_error'; }; type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]; }`\n\n - `id: string`\n - `created_at: string`\n - `file_status: { completed: number; failed: number; in_progress: number; total: number; }`\n - `name: string`\n - `type: 'manual' | 'connector' | 'web'`\n - `description?: string`\n - `urls?: { status: { status: 'validating' | 'success' | 'error'; error_type?: 'invalid_url' | 'not_searchable' | 'not_found' | 'paywall_or_login_page' | 'unexpected_error'; }; type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\n// Automatically fetches more pages as needed.\nfor await (const graph of client.graphs.list()) {\n console.log(graph);\n}\n```", + perLanguage: { + go: { + method: 'client.Graphs.List', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tpage, err := client.Graphs.List(context.TODO(), writersdk.GraphListParams{})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", page)\n}\n', + }, + http: { + example: 'curl https://api.writer.com/v1/graphs \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'graphs.list', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\npage = client.graphs.list()\npage = page.data[0]\nprint(page.id)', + }, + typescript: { + method: 'client.graphs.list', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\n// Automatically fetches more pages as needed.\nfor await (const graph of client.graphs.list()) {\n console.log(graph.id);\n}", + }, + }, + }, + { + name: 'create', + endpoint: '/v1/graphs', + httpMethod: 'post', + summary: 'Create graph', + description: 'Create a new Knowledge Graph.', + stainlessPath: '(resource) graphs > (method) create', + qualified: 'client.graphs.create', + params: ['description?: string;', 'name?: string;'], + response: + "{ id: string; created_at: string; name: string; description?: string; urls?: { status: { status: 'validating' | 'success' | 'error'; error_type?: 'invalid_url' | 'not_searchable' | 'not_found' | 'paywall_or_login_page' | 'unexpected_error'; }; type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]; }", + markdown: + "## create\n\n`client.graphs.create(description?: string, name?: string): { id: string; created_at: string; name: string; description?: string; urls?: object[]; }`\n\n**post** `/v1/graphs`\n\nCreate a new Knowledge Graph.\n\n### Parameters\n\n- `description?: string`\n A description of the Knowledge Graph (max 255 characters). Omitting this field leaves the description unchanged.\n\n- `name?: string`\n The name of the Knowledge Graph (max 255 characters). Omitting this field leaves the name unchanged.\n\n### Returns\n\n- `{ id: string; created_at: string; name: string; description?: string; urls?: { status: { status: 'validating' | 'success' | 'error'; error_type?: 'invalid_url' | 'not_searchable' | 'not_found' | 'paywall_or_login_page' | 'unexpected_error'; }; type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]; }`\n\n - `id: string`\n - `created_at: string`\n - `name: string`\n - `description?: string`\n - `urls?: { status: { status: 'validating' | 'success' | 'error'; error_type?: 'invalid_url' | 'not_searchable' | 'not_found' | 'paywall_or_login_page' | 'unexpected_error'; }; type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst graph = await client.graphs.create();\n\nconsole.log(graph);\n```", + perLanguage: { + go: { + method: 'client.Graphs.New', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tgraph, err := client.Graphs.New(context.TODO(), writersdk.GraphNewParams{})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", graph.ID)\n}\n', + }, + http: { + example: + "curl https://api.writer.com/v1/graphs \\\n -H 'Content-Type: application/json' \\\n -H \"Authorization: Bearer $WRITER_API_KEY\" \\\n -d '{}'", + }, + python: { + method: 'graphs.create', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\ngraph = client.graphs.create()\nprint(graph.id)', + }, + typescript: { + method: 'client.graphs.create', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst graph = await client.graphs.create();\n\nconsole.log(graph.id);", + }, + }, + }, + { + name: 'retrieve', + endpoint: '/v1/graphs/{graph_id}', + httpMethod: 'get', + summary: 'Retrieve graph', + description: 'Retrieve a Knowledge Graph.', + stainlessPath: '(resource) graphs > (method) retrieve', + qualified: 'client.graphs.retrieve', + params: ['graph_id: string;'], + response: + "{ id: string; created_at: string; file_status: { completed: number; failed: number; in_progress: number; total: number; }; name: string; type: 'manual' | 'connector' | 'web'; description?: string; urls?: { status: { status: 'validating' | 'success' | 'error'; error_type?: 'invalid_url' | 'not_searchable' | 'not_found' | 'paywall_or_login_page' | 'unexpected_error'; }; type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]; }", + markdown: + "## retrieve\n\n`client.graphs.retrieve(graph_id: string): { id: string; created_at: string; file_status: object; name: string; type: 'manual' | 'connector' | 'web'; description?: string; urls?: object[]; }`\n\n**get** `/v1/graphs/{graph_id}`\n\nRetrieve a Knowledge Graph.\n\n### Parameters\n\n- `graph_id: string`\n\n### Returns\n\n- `{ id: string; created_at: string; file_status: { completed: number; failed: number; in_progress: number; total: number; }; name: string; type: 'manual' | 'connector' | 'web'; description?: string; urls?: { status: { status: 'validating' | 'success' | 'error'; error_type?: 'invalid_url' | 'not_searchable' | 'not_found' | 'paywall_or_login_page' | 'unexpected_error'; }; type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]; }`\n\n - `id: string`\n - `created_at: string`\n - `file_status: { completed: number; failed: number; in_progress: number; total: number; }`\n - `name: string`\n - `type: 'manual' | 'connector' | 'web'`\n - `description?: string`\n - `urls?: { status: { status: 'validating' | 'success' | 'error'; error_type?: 'invalid_url' | 'not_searchable' | 'not_found' | 'paywall_or_login_page' | 'unexpected_error'; }; type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst graph = await client.graphs.retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e');\n\nconsole.log(graph);\n```", + perLanguage: { + go: { + method: 'client.Graphs.Get', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tgraph, err := client.Graphs.Get(context.TODO(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", graph.ID)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/graphs/$GRAPH_ID \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'graphs.retrieve', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\ngraph = client.graphs.retrieve(\n "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",\n)\nprint(graph.id)', + }, + typescript: { + method: 'client.graphs.retrieve', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst graph = await client.graphs.retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e');\n\nconsole.log(graph.id);", + }, + }, + }, + { + name: 'update', + endpoint: '/v1/graphs/{graph_id}', + httpMethod: 'put', + summary: 'Update graph', + description: 'Update the name and description of a Knowledge Graph.', + stainlessPath: '(resource) graphs > (method) update', + qualified: 'client.graphs.update', + params: [ + 'graph_id: string;', + 'description?: string;', + 'name?: string;', + "urls?: { type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[];", + ], + response: + "{ id: string; created_at: string; name: string; description?: string; urls?: { status: { status: 'validating' | 'success' | 'error'; error_type?: 'invalid_url' | 'not_searchable' | 'not_found' | 'paywall_or_login_page' | 'unexpected_error'; }; type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]; }", + markdown: + "## update\n\n`client.graphs.update(graph_id: string, description?: string, name?: string, urls?: { type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]): { id: string; created_at: string; name: string; description?: string; urls?: object[]; }`\n\n**put** `/v1/graphs/{graph_id}`\n\nUpdate the name and description of a Knowledge Graph.\n\n### Parameters\n\n- `graph_id: string`\n\n- `description?: string`\n A description of the Knowledge Graph (max 255 characters). Omitting this field leaves the description unchanged.\n\n- `name?: string`\n The name of the Knowledge Graph (max 255 characters). Omitting this field leaves the name unchanged.\n\n- `urls?: { type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]`\n An array of web connector URLs to update for this Knowledge Graph. You can only connect URLs to Knowledge Graphs with the type `web`. To clear the list of URLs, set this field to an empty array.\n\n### Returns\n\n- `{ id: string; created_at: string; name: string; description?: string; urls?: { status: { status: 'validating' | 'success' | 'error'; error_type?: 'invalid_url' | 'not_searchable' | 'not_found' | 'paywall_or_login_page' | 'unexpected_error'; }; type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]; }`\n\n - `id: string`\n - `created_at: string`\n - `name: string`\n - `description?: string`\n - `urls?: { status: { status: 'validating' | 'success' | 'error'; error_type?: 'invalid_url' | 'not_searchable' | 'not_found' | 'paywall_or_login_page' | 'unexpected_error'; }; type: 'single_page' | 'sub_pages'; url: string; exclude_urls?: string[]; }[]`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst graph = await client.graphs.update('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e');\n\nconsole.log(graph);\n```", + perLanguage: { + go: { + method: 'client.Graphs.Update', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tgraph, err := client.Graphs.Update(\n\t\tcontext.TODO(),\n\t\t"182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",\n\t\twritersdk.GraphUpdateParams{},\n\t)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", graph.ID)\n}\n', + }, + http: { + example: + "curl https://api.writer.com/v1/graphs/$GRAPH_ID \\\n -X PUT \\\n -H 'Content-Type: application/json' \\\n -H \"Authorization: Bearer $WRITER_API_KEY\" \\\n -d '{}'", + }, + python: { + method: 'graphs.update', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\ngraph = client.graphs.update(\n graph_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",\n)\nprint(graph.id)', + }, + typescript: { + method: 'client.graphs.update', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst graph = await client.graphs.update('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e');\n\nconsole.log(graph.id);", + }, + }, + }, + { + name: 'delete', + endpoint: '/v1/graphs/{graph_id}', + httpMethod: 'delete', + summary: 'Delete graph', + description: 'Delete a Knowledge Graph.', + stainlessPath: '(resource) graphs > (method) delete', + qualified: 'client.graphs.delete', + params: ['graph_id: string;'], + response: '{ id: string; deleted: boolean; }', + markdown: + "## delete\n\n`client.graphs.delete(graph_id: string): { id: string; deleted: boolean; }`\n\n**delete** `/v1/graphs/{graph_id}`\n\nDelete a Knowledge Graph.\n\n### Parameters\n\n- `graph_id: string`\n\n### Returns\n\n- `{ id: string; deleted: boolean; }`\n\n - `id: string`\n - `deleted: boolean`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst graph = await client.graphs.delete('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e');\n\nconsole.log(graph);\n```", + perLanguage: { + go: { + method: 'client.Graphs.Delete', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tgraph, err := client.Graphs.Delete(context.TODO(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", graph.ID)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/graphs/$GRAPH_ID \\\n -X DELETE \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'graphs.delete', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\ngraph = client.graphs.delete(\n "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",\n)\nprint(graph.id)', + }, + typescript: { + method: 'client.graphs.delete', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst graph = await client.graphs.delete('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e');\n\nconsole.log(graph.id);", + }, + }, + }, + { + name: 'add_file_to_graph', + endpoint: '/v1/graphs/{graph_id}/file', + httpMethod: 'post', + summary: 'Add file to graph', + description: 'Add a file to a Knowledge Graph.', + stainlessPath: '(resource) graphs > (method) add_file_to_graph', + qualified: 'client.graphs.addFileToGraph', + params: ['graph_id: string;', 'file_id: string;'], + response: '{ id: string; created_at: string; graph_ids: string[]; name: string; status: string; }', + markdown: + "## add_file_to_graph\n\n`client.graphs.addFileToGraph(graph_id: string, file_id: string): { id: string; created_at: string; graph_ids: string[]; name: string; status: string; }`\n\n**post** `/v1/graphs/{graph_id}/file`\n\nAdd a file to a Knowledge Graph.\n\n### Parameters\n\n- `graph_id: string`\n\n- `file_id: string`\n The unique identifier of the file.\n\n### Returns\n\n- `{ id: string; created_at: string; graph_ids: string[]; name: string; status: string; }`\n\n - `id: string`\n - `created_at: string`\n - `graph_ids: string[]`\n - `name: string`\n - `status: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst file = await client.graphs.addFileToGraph('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { file_id: 'file_id' });\n\nconsole.log(file);\n```", + perLanguage: { + go: { + method: 'client.Graphs.AddFileToGraph', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tfile, err := client.Graphs.AddFileToGraph(\n\t\tcontext.TODO(),\n\t\t"182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",\n\t\twritersdk.GraphAddFileToGraphParams{\n\t\t\tFileID: writersdk.F("file_id"),\n\t\t},\n\t)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", file.ID)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/graphs/$GRAPH_ID/file \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "file_id": "file_id"\n }\'', + }, + python: { + method: 'graphs.add_file_to_graph', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nfile = client.graphs.add_file_to_graph(\n graph_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",\n file_id="file_id",\n)\nprint(file.id)', + }, + typescript: { + method: 'client.graphs.addFileToGraph', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst file = await client.graphs.addFileToGraph('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', {\n file_id: 'file_id',\n});\n\nconsole.log(file.id);", + }, + }, + }, + { + name: 'remove_file_from_graph', + endpoint: '/v1/graphs/{graph_id}/file/{file_id}', + httpMethod: 'delete', + summary: 'Remove file from graph', + description: 'Remove a file from a Knowledge Graph.', + stainlessPath: '(resource) graphs > (method) remove_file_from_graph', + qualified: 'client.graphs.removeFileFromGraph', + params: ['graph_id: string;', 'file_id: string;'], + response: '{ id: string; deleted: boolean; }', + markdown: + "## remove_file_from_graph\n\n`client.graphs.removeFileFromGraph(graph_id: string, file_id: string): { id: string; deleted: boolean; }`\n\n**delete** `/v1/graphs/{graph_id}/file/{file_id}`\n\nRemove a file from a Knowledge Graph.\n\n### Parameters\n\n- `graph_id: string`\n\n- `file_id: string`\n\n### Returns\n\n- `{ id: string; deleted: boolean; }`\n\n - `id: string`\n - `deleted: boolean`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst response = await client.graphs.removeFileFromGraph('file_id', { graph_id: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e' });\n\nconsole.log(response);\n```", + perLanguage: { + go: { + method: 'client.Graphs.RemoveFileFromGraph', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tresponse, err := client.Graphs.RemoveFileFromGraph(\n\t\tcontext.TODO(),\n\t\t"182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",\n\t\t"file_id",\n\t)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", response.ID)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/graphs/$GRAPH_ID/file/$FILE_ID \\\n -X DELETE \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'graphs.remove_file_from_graph', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nresponse = client.graphs.remove_file_from_graph(\n file_id="file_id",\n graph_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",\n)\nprint(response.id)', + }, + typescript: { + method: 'client.graphs.removeFileFromGraph', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst response = await client.graphs.removeFileFromGraph('file_id', {\n graph_id: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e',\n});\n\nconsole.log(response.id);", + }, + }, + }, + { + name: 'question', + endpoint: '/v1/graphs/question', + httpMethod: 'post', + summary: 'Question', + description: 'Ask a question to specified Knowledge Graphs.', + stainlessPath: '(resource) graphs > (method) question', + qualified: 'client.graphs.question', + params: [ + 'graph_ids: string[];', + 'question: string;', + 'query_config?: { grounding_level?: number; inline_citations?: boolean; keyword_threshold?: number; max_snippets?: number; max_subquestions?: number; max_tokens?: number; search_weight?: number; semantic_threshold?: number; };', + 'stream?: boolean;', + 'subqueries?: boolean;', + ], + response: + '{ answer: string; question: string; sources: { file_id: string; snippet: string; }[]; references?: { files?: { fileId: string; score: number; text: string; cite?: string; page?: number; }[]; web?: { score: number; text: string; title: string; url: string; }[]; }; subqueries?: { answer: string; query: string; sources: object[]; }[]; }', + markdown: + "## question\n\n`client.graphs.question(graph_ids: string[], question: string, query_config?: { grounding_level?: number; inline_citations?: boolean; keyword_threshold?: number; max_snippets?: number; max_subquestions?: number; max_tokens?: number; search_weight?: number; semantic_threshold?: number; }, stream?: boolean, subqueries?: boolean): { answer: string; question: string; sources: source[]; references?: object; subqueries?: object[]; }`\n\n**post** `/v1/graphs/question`\n\nAsk a question to specified Knowledge Graphs.\n\n### Parameters\n\n- `graph_ids: string[]`\n The unique identifiers of the Knowledge Graphs to query.\n\n- `question: string`\n The question to answer using the Knowledge Graph.\n\n- `query_config?: { grounding_level?: number; inline_citations?: boolean; keyword_threshold?: number; max_snippets?: number; max_subquestions?: number; max_tokens?: number; search_weight?: number; semantic_threshold?: number; }`\n Configuration options for Knowledge Graph queries, including search parameters and citation settings.\n - `grounding_level?: number`\n Level of grounding required for responses, controlling how closely answers must be tied to source material. Set lower for grounded outputs, higher for creativity. Higher values (closer to 1.0) allow more creative interpretation, while lower values (closer to 0.0) stick more closely to source material. Range: 0.0-1.0, Default: 0.0.\n - `inline_citations?: boolean`\n Whether to include inline citations in the response, showing which Knowledge Graph sources were used. Default: false.\n - `keyword_threshold?: number`\n Threshold for keyword-based matching when searching Knowledge Graph content. Set higher for stricter relevance, lower for broader range. Higher values (closer to 1.0) require stronger keyword matches, while lower values (closer to 0.0) allow more lenient matching. Range: 0.0-1.0, Default: 0.7.\n - `max_snippets?: number`\n Maximum number of text snippets to retrieve from the Knowledge Graph for context. Works in concert with `search_weight` to control best matches vs broader coverage. While technically supports 1-60, values below 5 may return no results due to RAG implementation. Recommended range: 5-25. Due to RAG system behavior, you may see more snippets than requested. Range: 1-60, Default: 30.\n - `max_subquestions?: number`\n Maximum number of subquestions to generate when processing complex queries. Set higher to improve detail, set lower to reduce response time. Range: 1-10, Default: 6.\n - `max_tokens?: number`\n Maximum number of tokens the model can generate in the response. This controls the length of the AI's answer. Set higher for longer answers, set lower for shorter, faster answers. Range: 100-8000, Default: 4000.\n - `search_weight?: number`\n Weight given to search results when ranking and selecting relevant information. Higher values (closer to 100) prioritize keyword-based matching, while lower values (closer to 0) prioritize semantic similarity matching. Use higher values for exact keyword searches, lower values for conceptual similarity searches. Range: 0-100, Default: 50.\n - `semantic_threshold?: number`\n Threshold for semantic similarity matching when searching Knowledge Graph content. Set higher for stricter relevance, lower for broader range. Higher values (closer to 1.0) require stronger semantic similarity, while lower values (closer to 0.0) allow more lenient semantic matching. Range: 0.0-1.0, Default: 0.7.\n\n- `stream?: boolean`\n Determines whether the model's output should be streamed. If true, the output is generated and sent incrementally, which can be useful for real-time applications.\n\n- `subqueries?: boolean`\n Specify whether to include subqueries.\n\n### Returns\n\n- `{ answer: string; question: string; sources: { file_id: string; snippet: string; }[]; references?: { files?: { fileId: string; score: number; text: string; cite?: string; page?: number; }[]; web?: { score: number; text: string; title: string; url: string; }[]; }; subqueries?: { answer: string; query: string; sources: object[]; }[]; }`\n\n - `answer: string`\n - `question: string`\n - `sources: { file_id: string; snippet: string; }[]`\n - `references?: { files?: { fileId: string; score: number; text: string; cite?: string; page?: number; }[]; web?: { score: number; text: string; title: string; url: string; }[]; }`\n - `subqueries?: { answer: string; query: string; sources: { file_id: string; snippet: string; }[]; }[]`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst stream = await client.graphs.question({ graph_ids: ['182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'], question: 'question' });\nfor await (const questionResponseChunk of stream) {\n console.log(questionResponseChunk);\n}\n```", + perLanguage: { + go: { + method: 'client.Graphs.Question', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tquestion, err := client.Graphs.Question(context.TODO(), writersdk.GraphQuestionParams{\n\t\tGraphIDs: writersdk.F([]string{"182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"}),\n\t\tQuestion: writersdk.F("question"),\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", question.Answer)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/graphs/question \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "graph_ids": [\n "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"\n ],\n "question": "question"\n }\'', + }, + python: { + method: 'graphs.question', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nfor graph in client.graphs.question(\n graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],\n question="question",\n):\n print(graph)', + }, + typescript: { + method: 'client.graphs.question', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst question = await client.graphs.question({\n graph_ids: ['182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'],\n question: 'question',\n});\n\nconsole.log(question.answer);", + }, + }, + }, + { + name: 'retrieve', + endpoint: '/v1/files/{file_id}', + httpMethod: 'get', + summary: 'Retrieve file', + description: + 'Retrieve detailed information about a specific file, including its metadata, status, and associated graphs.', + stainlessPath: '(resource) files > (method) retrieve', + qualified: 'client.files.retrieve', + params: ['file_id: string;'], + response: '{ id: string; created_at: string; graph_ids: string[]; name: string; status: string; }', + markdown: + "## retrieve\n\n`client.files.retrieve(file_id: string): { id: string; created_at: string; graph_ids: string[]; name: string; status: string; }`\n\n**get** `/v1/files/{file_id}`\n\nRetrieve detailed information about a specific file, including its metadata, status, and associated graphs.\n\n### Parameters\n\n- `file_id: string`\n\n### Returns\n\n- `{ id: string; created_at: string; graph_ids: string[]; name: string; status: string; }`\n\n - `id: string`\n - `created_at: string`\n - `graph_ids: string[]`\n - `name: string`\n - `status: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst file = await client.files.retrieve('file_id');\n\nconsole.log(file);\n```", + perLanguage: { + go: { + method: 'client.Files.Get', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tfile, err := client.Files.Get(context.TODO(), "file_id")\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", file.ID)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/files/$FILE_ID \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'files.retrieve', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nfile = client.files.retrieve(\n "file_id",\n)\nprint(file.id)', + }, + typescript: { + method: 'client.files.retrieve', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst file = await client.files.retrieve('file_id');\n\nconsole.log(file.id);", + }, + }, + }, + { + name: 'delete', + endpoint: '/v1/files/{file_id}', + httpMethod: 'delete', + summary: 'Delete file', + description: 'Permanently delete a file from the system. This action cannot be undone.', + stainlessPath: '(resource) files > (method) delete', + qualified: 'client.files.delete', + params: ['file_id: string;'], + response: '{ id: string; deleted: boolean; }', + markdown: + "## delete\n\n`client.files.delete(file_id: string): { id: string; deleted: boolean; }`\n\n**delete** `/v1/files/{file_id}`\n\nPermanently delete a file from the system. This action cannot be undone.\n\n### Parameters\n\n- `file_id: string`\n\n### Returns\n\n- `{ id: string; deleted: boolean; }`\n\n - `id: string`\n - `deleted: boolean`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst file = await client.files.delete('file_id');\n\nconsole.log(file);\n```", + perLanguage: { + go: { + method: 'client.Files.Delete', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tfile, err := client.Files.Delete(context.TODO(), "file_id")\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", file.ID)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/files/$FILE_ID \\\n -X DELETE \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'files.delete', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nfile = client.files.delete(\n "file_id",\n)\nprint(file.id)', + }, + typescript: { + method: 'client.files.delete', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst file = await client.files.delete('file_id');\n\nconsole.log(file.id);", + }, + }, + }, + { + name: 'list', + endpoint: '/v1/files', + httpMethod: 'get', + summary: 'List files', + description: + 'Retrieve a paginated list of files with optional filtering by status, graph association, and file type.', + stainlessPath: '(resource) files > (method) list', + qualified: 'client.files.list', + params: [ + 'after?: string;', + 'before?: string;', + 'file_types?: string;', + 'graph_id?: string;', + 'limit?: number;', + "order?: 'asc' | 'desc';", + "status?: 'in_progress' | 'completed' | 'failed';", + ], + response: '{ id: string; created_at: string; graph_ids: string[]; name: string; status: string; }', + markdown: + "## list\n\n`client.files.list(after?: string, before?: string, file_types?: string, graph_id?: string, limit?: number, order?: 'asc' | 'desc', status?: 'in_progress' | 'completed' | 'failed'): { id: string; created_at: string; graph_ids: string[]; name: string; status: string; }`\n\n**get** `/v1/files`\n\nRetrieve a paginated list of files with optional filtering by status, graph association, and file type.\n\n### Parameters\n\n- `after?: string`\n The ID of the last object in the previous page. This parameter instructs the API to return the next page of results.\n\n- `before?: string`\n The ID of the first object in the previous page. This parameter instructs the API to return the previous page of results.\n\n- `file_types?: string`\n The extensions of the files to retrieve. Separate multiple extensions with a comma. For example: `pdf,jpg,docx`.\n\n- `graph_id?: string`\n The unique identifier of the graph to which the files belong.\n\n- `limit?: number`\n Specifies the maximum number of objects returned in a page. The default value is 50. The minimum value is 1, and the maximum value is 100.\n\n- `order?: 'asc' | 'desc'`\n Specifies the order of the results. Valid values are asc for ascending and desc for descending.\n\n- `status?: 'in_progress' | 'completed' | 'failed'`\n Specifies the status of the files to retrieve. Valid values are in_progress, completed or failed.\n\n### Returns\n\n- `{ id: string; created_at: string; graph_ids: string[]; name: string; status: string; }`\n\n - `id: string`\n - `created_at: string`\n - `graph_ids: string[]`\n - `name: string`\n - `status: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\n// Automatically fetches more pages as needed.\nfor await (const file of client.files.list()) {\n console.log(file);\n}\n```", + perLanguage: { + go: { + method: 'client.Files.List', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tpage, err := client.Files.List(context.TODO(), writersdk.FileListParams{})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", page)\n}\n', + }, + http: { + example: 'curl https://api.writer.com/v1/files \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'files.list', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\npage = client.files.list()\npage = page.data[0]\nprint(page.id)', + }, + typescript: { + method: 'client.files.list', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\n// Automatically fetches more pages as needed.\nfor await (const file of client.files.list()) {\n console.log(file.id);\n}", + }, + }, + }, + { + name: 'upload', + endpoint: '/v1/files', + httpMethod: 'post', + summary: 'Upload file', + description: + 'Upload a new file to the system. Supports various file formats including PDF, DOC, DOCX, PPT, PPTX, JPG, PNG, EML, HTML, SRT, CSV, XLS, and XLSX.', + stainlessPath: '(resource) files > (method) upload', + qualified: 'client.files.upload', + params: ['content: string;', 'Content-Disposition: string;', 'graphId?: string;'], + response: '{ id: string; created_at: string; graph_ids: string[]; name: string; status: string; }', + markdown: + "## upload\n\n`client.files.upload(content: string, Content-Disposition: string, graphId?: string): { id: string; created_at: string; graph_ids: string[]; name: string; status: string; }`\n\n**post** `/v1/files`\n\nUpload a new file to the system. Supports various file formats including PDF, DOC, DOCX, PPT, PPTX, JPG, PNG, EML, HTML, SRT, CSV, XLS, and XLSX.\n\n### Parameters\n\n- `content: string`\n\n- `Content-Disposition: string`\n\n- `graphId?: string`\n The unique identifier of the Knowledge Graph to associate the uploaded file with.\n\nNote: The response from the upload endpoint does not include the `graphId` field, but the association will be visible when you retrieve the file using the file retrieval endpoint.\n\n### Returns\n\n- `{ id: string; created_at: string; graph_ids: string[]; name: string; status: string; }`\n\n - `id: string`\n - `created_at: string`\n - `graph_ids: string[]`\n - `name: string`\n - `status: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst file = await client.files.upload({ content: fs.createReadStream('path/to/file'), 'Content-Disposition': 'Content-Disposition' });\n\nconsole.log(file);\n```", + perLanguage: { + go: { + method: 'client.Files.Upload', + example: + 'package main\n\nimport (\n\t"bytes"\n\t"context"\n\t"fmt"\n\t"io"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tfile, err := client.Files.Upload(context.TODO(), writersdk.FileUploadParams{\n\t\tContent: io.Reader(bytes.NewBuffer([]byte("Example data"))),\n\t\tContentDisposition: writersdk.F("Content-Disposition"),\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", file.ID)\n}\n', + }, + http: { + example: + "curl https://api.writer.com/v1/files \\\n -H 'Content-Type: text/plain' \\\n -H \"Authorization: Bearer $WRITER_API_KEY\" \\\n -F 'content=@/path/to/content'", + }, + python: { + method: 'files.upload', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nfile = client.files.upload(\n content=b"Example data",\n content_disposition="Content-Disposition",\n)\nprint(file.id)', + }, + typescript: { + method: 'client.files.upload', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst file = await client.files.upload({\n content: fs.createReadStream('path/to/file'),\n 'Content-Disposition': 'Content-Disposition',\n});\n\nconsole.log(file.id);", + }, + }, + }, + { + name: 'download', + endpoint: '/v1/files/{file_id}/download', + httpMethod: 'get', + summary: 'Download file', + description: + 'Download the binary content of a file. The response will contain the file data in the appropriate MIME type.', + stainlessPath: '(resource) files > (method) download', + qualified: 'client.files.download', + params: ['file_id: string;'], + response: 'string', + markdown: + "## download\n\n`client.files.download(file_id: string): string`\n\n**get** `/v1/files/{file_id}/download`\n\nDownload the binary content of a file. The response will contain the file data in the appropriate MIME type.\n\n### Parameters\n\n- `file_id: string`\n\n### Returns\n\n- `string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst response = await client.files.download('file_id');\n\nconsole.log(response);\n\nconst content = await response.blob()\nconsole.log(content)\n```", + perLanguage: { + go: { + method: 'client.Files.Download', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tresponse, err := client.Files.Download(context.TODO(), "file_id")\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", response)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/files/$FILE_ID/download \\\n -H "Authorization: Bearer $WRITER_API_KEY"', + }, + python: { + method: 'files.download', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nresponse = client.files.download(\n "file_id",\n)\nprint(response)\ncontent = response.read()\nprint(content)', + }, + typescript: { + method: 'client.files.download', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst response = await client.files.download('file_id');\n\nconsole.log(response);\n\nconst content = await response.blob();\nconsole.log(content);", + }, + }, + }, + { + name: 'retry', + endpoint: '/v1/files/retry', + httpMethod: 'post', + summary: 'Retry failed files', + description: + 'Retry processing of files that previously failed to process. This will re-attempt the processing of the specified files.', + stainlessPath: '(resource) files > (method) retry', + qualified: 'client.files.retry', + params: ['file_ids: string[];'], + response: '{ success?: boolean; }', + markdown: + "## retry\n\n`client.files.retry(file_ids: string[]): { success?: boolean; }`\n\n**post** `/v1/files/retry`\n\nRetry processing of files that previously failed to process. This will re-attempt the processing of the specified files.\n\n### Parameters\n\n- `file_ids: string[]`\n The unique identifier of the files to retry.\n\n### Returns\n\n- `{ success?: boolean; }`\n\n - `success?: boolean`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst response = await client.files.retry({ file_ids: ['182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'] });\n\nconsole.log(response);\n```", + perLanguage: { + go: { + method: 'client.Files.Retry', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tresponse, err := client.Files.Retry(context.TODO(), writersdk.FileRetryParams{\n\t\tFileIDs: writersdk.F([]string{"182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"}),\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", response.Success)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/files/retry \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "file_ids": [\n "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"\n ]\n }\'', + }, + python: { + method: 'files.retry', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nresponse = client.files.retry(\n file_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],\n)\nprint(response.success)', + }, + typescript: { + method: 'client.files.retry', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst response = await client.files.retry({ file_ids: ['182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'] });\n\nconsole.log(response.success);", + }, + }, + }, + { + name: 'ai_detect', + endpoint: '/v1/tools/ai-detect', + httpMethod: 'post', + summary: 'AI detection', + description: + 'Detects if content is AI- or human-generated, with a confidence score. Content must have at least 350 characters', + stainlessPath: '(resource) tools > (method) ai_detect', + qualified: 'client.tools.aiDetect', + params: ['input: string;'], + response: "{ label: 'fake' | 'real'; score: number; }", + markdown: + "## ai_detect\n\n`client.tools.aiDetect(input: string): { label: 'fake' | 'real'; score: number; }`\n\n**post** `/v1/tools/ai-detect`\n\nDetects if content is AI- or human-generated, with a confidence score. Content must have at least 350 characters\n\n### Parameters\n\n- `input: string`\n The content to determine if it is AI- or human-generated. Content must have at least 350 characters.\n\n### Returns\n\n- `{ label: 'fake' | 'real'; score: number; }`\n\n - `label: 'fake' | 'real'`\n - `score: number`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst response = await client.tools.aiDetect({ input: 'AI and ML continue to be at the forefront of technological advancements. In 2025, we can expect more sophisticated AI systems that can handle complex tasks with greater efficiency. AI will play a crucial role in various sectors, including healthcare, finance, and manufacturing. For instance, AI-powered diagnostic tools will become more accurate, helping doctors detect diseases at an early stage. In finance, AI algorithms will enhance fraud detection and risk management.' });\n\nconsole.log(response);\n```", + perLanguage: { + go: { + method: 'client.Tools.AIDetect', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tresponse, err := client.Tools.AIDetect(context.TODO(), writersdk.ToolAIDetectParams{\n\t\tInput: writersdk.F("AI and ML continue to be at the forefront of technological advancements. In 2025, we can expect more sophisticated AI systems that can handle complex tasks with greater efficiency. AI will play a crucial role in various sectors, including healthcare, finance, and manufacturing. For instance, AI-powered diagnostic tools will become more accurate, helping doctors detect diseases at an early stage. In finance, AI algorithms will enhance fraud detection and risk management."),\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", response.Label)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/tools/ai-detect \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "input": "AI and ML continue to be at the forefront of technological advancements. In 2025, we can expect more sophisticated AI systems that can handle complex tasks with greater efficiency. AI will play a crucial role in various sectors, including healthcare, finance, and manufacturing. For instance, AI-powered diagnostic tools will become more accurate, helping doctors detect diseases at an early stage. In finance, AI algorithms will enhance fraud detection and risk management."\n }\'', + }, + python: { + method: 'tools.ai_detect', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nresponse = client.tools.ai_detect(\n input="AI and ML continue to be at the forefront of technological advancements. In 2025, we can expect more sophisticated AI systems that can handle complex tasks with greater efficiency. AI will play a crucial role in various sectors, including healthcare, finance, and manufacturing. For instance, AI-powered diagnostic tools will become more accurate, helping doctors detect diseases at an early stage. In finance, AI algorithms will enhance fraud detection and risk management.",\n)\nprint(response.label)', + }, + typescript: { + method: 'client.tools.aiDetect', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst response = await client.tools.aiDetect({\n input:\n 'AI and ML continue to be at the forefront of technological advancements. In 2025, we can expect more sophisticated AI systems that can handle complex tasks with greater efficiency. AI will play a crucial role in various sectors, including healthcare, finance, and manufacturing. For instance, AI-powered diagnostic tools will become more accurate, helping doctors detect diseases at an early stage. In finance, AI algorithms will enhance fraud detection and risk management.',\n});\n\nconsole.log(response.label);", + }, + }, + }, + { + name: 'context_aware_splitting', + endpoint: '/v1/tools/context-aware-splitting', + httpMethod: 'post', + summary: 'Context-aware text splitting', + description: + 'Splits a long block of text (maximum 4000 words) into smaller chunks while preserving the semantic meaning of the text and context between the chunks.', + stainlessPath: '(resource) tools > (method) context_aware_splitting', + qualified: 'client.tools.contextAwareSplitting', + params: ["strategy: 'llm_split' | 'fast_split' | 'hybrid_split';", 'text: string;'], + response: '{ chunks: string[]; }', + markdown: + "## context_aware_splitting\n\n`client.tools.contextAwareSplitting(strategy: 'llm_split' | 'fast_split' | 'hybrid_split', text: string): { chunks: string[]; }`\n\n**post** `/v1/tools/context-aware-splitting`\n\nSplits a long block of text (maximum 4000 words) into smaller chunks while preserving the semantic meaning of the text and context between the chunks.\n\n### Parameters\n\n- `strategy: 'llm_split' | 'fast_split' | 'hybrid_split'`\n The strategy to use for splitting the text into chunks. `llm_split` uses the language model to split the text, `fast_split` uses a fast heuristic-based approach, and `hybrid_split` combines both strategies.\n\n- `text: string`\n The text to split into chunks.\n\n### Returns\n\n- `{ chunks: string[]; }`\n\n - `chunks: string[]`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst response = await client.tools.contextAwareSplitting({ strategy: 'llm_split', text: 'text' });\n\nconsole.log(response);\n```", + perLanguage: { + go: { + method: 'client.Tools.ContextAwareSplitting', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tresponse, err := client.Tools.ContextAwareSplitting(context.TODO(), writersdk.ToolContextAwareSplittingParams{\n\t\tStrategy: writersdk.F(writersdk.ToolContextAwareSplittingParamsStrategyLlmSplit),\n\t\tText: writersdk.F("text"),\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", response.Chunks)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/tools/context-aware-splitting \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "strategy": "llm_split",\n "text": "text"\n }\'', + }, + python: { + method: 'tools.context_aware_splitting', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nresponse = client.tools.context_aware_splitting(\n strategy="llm_split",\n text="text",\n)\nprint(response.chunks)', + }, + typescript: { + method: 'client.tools.contextAwareSplitting', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst response = await client.tools.contextAwareSplitting({ strategy: 'llm_split', text: 'text' });\n\nconsole.log(response.chunks);", + }, + }, + }, + { + name: 'parse_pdf', + endpoint: '/v1/tools/pdf-parser/{file_id}', + httpMethod: 'post', + summary: 'Parse PDF', + description: 'Parse PDF to other formats.', + stainlessPath: '(resource) tools > (method) parse_pdf', + qualified: 'client.tools.parsePdf', + params: ['file_id: string;', "format: 'text' | 'markdown';"], + response: '{ content: string; }', + markdown: + "## parse_pdf\n\n`client.tools.parsePdf(file_id: string, format: 'text' | 'markdown'): { content: string; }`\n\n**post** `/v1/tools/pdf-parser/{file_id}`\n\nParse PDF to other formats.\n\n### Parameters\n\n- `file_id: string`\n\n- `format: 'text' | 'markdown'`\n The format into which the PDF content should be converted.\n\n### Returns\n\n- `{ content: string; }`\n\n - `content: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst response = await client.tools.parsePdf('file_id', { format: 'text' });\n\nconsole.log(response);\n```", + perLanguage: { + go: { + method: 'client.Tools.ParsePdf', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tresponse, err := client.Tools.ParsePdf(\n\t\tcontext.TODO(),\n\t\t"file_id",\n\t\twritersdk.ToolParsePdfParams{\n\t\t\tFormat: writersdk.F(writersdk.ToolParsePdfParamsFormatText),\n\t\t},\n\t)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", response.Content)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/tools/pdf-parser/$FILE_ID \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "format": "text"\n }\'', + }, + python: { + method: 'tools.parse_pdf', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nresponse = client.tools.parse_pdf(\n file_id="file_id",\n format="text",\n)\nprint(response.content)', + }, + typescript: { + method: 'client.tools.parsePdf', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst response = await client.tools.parsePdf('file_id', { format: 'text' });\n\nconsole.log(response.content);", + }, + }, + }, + { + name: 'web_search', + endpoint: '/v1/tools/web-search', + httpMethod: 'post', + summary: 'Web search', + description: + 'Search the web for information about a given query and return relevant results with source URLs.', + stainlessPath: '(resource) tools > (method) web_search', + qualified: 'client.tools.webSearch', + params: [ + 'chunks_per_source?: number;', + 'country?: string;', + 'days?: number;', + 'exclude_domains?: string[];', + 'include_answer?: boolean;', + 'include_domains?: string[];', + "include_raw_content?: 'text' | 'markdown' | boolean;", + 'max_results?: number;', + 'query?: string;', + "search_depth?: 'basic' | 'advanced';", + 'stream?: boolean;', + "time_range?: 'day' | 'week' | 'month' | 'year' | 'd' | 'w' | 'm' | 'y';", + "topic?: 'general' | 'news';", + ], + response: '{ query: string; sources: { raw_content?: string; url?: string; }[]; answer?: string; }', + markdown: + "## web_search\n\n`client.tools.webSearch(chunks_per_source?: number, country?: string, days?: number, exclude_domains?: string[], include_answer?: boolean, include_domains?: string[], include_raw_content?: 'text' | 'markdown' | boolean, max_results?: number, query?: string, search_depth?: 'basic' | 'advanced', stream?: boolean, time_range?: 'day' | 'week' | 'month' | 'year' | 'd' | 'w' | 'm' | 'y', topic?: 'general' | 'news'): { query: string; sources: object[]; answer?: string; }`\n\n**post** `/v1/tools/web-search`\n\nSearch the web for information about a given query and return relevant results with source URLs.\n\n### Parameters\n\n- `chunks_per_source?: number`\n Only applies when `search_depth` is `advanced`. Specifies how many text segments to extract from each source. Limited to 3 chunks maximum.\n\n- `country?: string`\n Localizes search results to a specific country. Only applies to general topic searches.\n\n- `days?: number`\n For news topic searches, specifies how many days of news coverage to include.\n\n- `exclude_domains?: string[]`\n Domains to exclude from the search. If unset, the search includes all domains.\n\n- `include_answer?: boolean`\n Whether to include a generated answer to the query in the response. If `false`, only search results are returned.\n\n- `include_domains?: string[]`\n Domains to include in the search. If unset, the search includes all domains.\n\n- `include_raw_content?: 'text' | 'markdown' | boolean`\n Controls how raw content is included in search results:\n\n- `text`: Returns plain text without formatting markup\n- `markdown`: Returns structured content with markdown formatting (headers, links, bold text)\n- `true`: Same as `markdown`\n- `false`: Raw content is not included (default if unset)\n\n- `max_results?: number`\n Limits the number of search results returned. Cannot exceed 20 sources.\n\n- `query?: string`\n The search query.\n\n- `search_depth?: 'basic' | 'advanced'`\n Controls search comprehensiveness:\n\n- `basic`: Returns fewer but highly relevant results\n- `advanced`: Performs a deeper search with more results\n\n- `stream?: boolean`\n Enables streaming of search results as they become available.\n\n- `time_range?: 'day' | 'week' | 'month' | 'year' | 'd' | 'w' | 'm' | 'y'`\n Filters results to content published within the specified time range back from the current date. For example, `week` or `w` returns results from the past 7 days.\n\n- `topic?: 'general' | 'news'`\n The search topic category. Use `news` for current events and news articles, or `general` for broader web search.\n\n### Returns\n\n- `{ query: string; sources: { raw_content?: string; url?: string; }[]; answer?: string; }`\n\n - `query: string`\n - `sources: { raw_content?: string; url?: string; }[]`\n - `answer?: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst response = await client.tools.webSearch();\n\nconsole.log(response);\n```", + perLanguage: { + go: { + method: 'client.Tools.WebSearch', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tresponse, err := client.Tools.WebSearch(context.TODO(), writersdk.ToolWebSearchParams{\n\t\tIncludeDomains: writersdk.F([]string{"dev.writer.com"}),\n\t\tQuery: writersdk.F("How do I get an API key for the Writer API?"),\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", response.Query)\n}\n', + }, + http: { + example: + "curl https://api.writer.com/v1/tools/web-search \\\n -H 'Content-Type: application/json' \\\n -H \"Authorization: Bearer $WRITER_API_KEY\" \\\n -d '{}'", + }, + python: { + method: 'tools.web_search', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nresponse = client.tools.web_search(\n include_domains=["dev.writer.com"],\n query="How do I get an API key for the Writer API?",\n)\nprint(response.query)', + }, + typescript: { + method: 'client.tools.webSearch', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst response = await client.tools.webSearch({\n include_domains: ['dev.writer.com'],\n query: 'How do I get an API key for the Writer API?',\n});\n\nconsole.log(response.query);", + }, + }, + }, + { + name: 'medical', + endpoint: '/v1/tools/comprehend/medical', + httpMethod: 'post', + summary: 'Medical comprehend', + description: + 'Analyze unstructured medical text to extract entities labeled with standardized medical codes and confidence scores.', + stainlessPath: '(resource) tools.comprehend > (method) medical', + qualified: 'client.tools.comprehend.medical', + params: ['content: string;', "response_type: 'Entities' | 'RxNorm' | 'ICD-10-CM' | 'SNOMED CT';"], + response: + '{ entities: { attributes: { begin_offset: number; concepts: object[]; end_offset: number; relationship_score: number; score: number; text: string; traits: object[]; type: string; category?: string; relationship_type?: string; }[]; begin_offset: number; category: string; concepts: { code: string; description: string; score: number; }[]; end_offset: number; score: number; text: string; traits: { name: string; score: number; }[]; type: string; }[]; }', + markdown: + "## medical\n\n`client.tools.comprehend.medical(content: string, response_type: 'Entities' | 'RxNorm' | 'ICD-10-CM' | 'SNOMED CT'): { entities: object[]; }`\n\n**post** `/v1/tools/comprehend/medical`\n\nAnalyze unstructured medical text to extract entities labeled with standardized medical codes and confidence scores.\n\n### Parameters\n\n- `content: string`\n The text to analyze.\n\n- `response_type: 'Entities' | 'RxNorm' | 'ICD-10-CM' | 'SNOMED CT'`\n The structure of the response to return. `Entities` returns medical entities, `RxNorm` returns medication information, `ICD-10-CM` returns diagnosis codes, and `SNOMED CT` returns medical concepts.\n\n### Returns\n\n- `{ entities: { attributes: { begin_offset: number; concepts: object[]; end_offset: number; relationship_score: number; score: number; text: string; traits: object[]; type: string; category?: string; relationship_type?: string; }[]; begin_offset: number; category: string; concepts: { code: string; description: string; score: number; }[]; end_offset: number; score: number; text: string; traits: { name: string; score: number; }[]; type: string; }[]; }`\n\n - `entities: { attributes: { begin_offset: number; concepts: { code: string; description: string; score: number; }[]; end_offset: number; relationship_score: number; score: number; text: string; traits: { name: string; score: number; }[]; type: string; category?: string; relationship_type?: string; }[]; begin_offset: number; category: string; concepts: { code: string; description: string; score: number; }[]; end_offset: number; score: number; text: string; traits: { name: string; score: number; }[]; type: string; }[]`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst response = await client.tools.comprehend.medical({ content: 'content', response_type: 'Entities' });\n\nconsole.log(response);\n```", + perLanguage: { + go: { + method: 'client.Tools.Comprehend.Medical', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tresponse, err := client.Tools.Comprehend.Medical(context.TODO(), writersdk.ToolComprehendMedicalParams{\n\t\tContent: writersdk.F("content"),\n\t\tResponseType: writersdk.F(writersdk.ToolComprehendMedicalParamsResponseTypeEntities),\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", response.Entities)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/tools/comprehend/medical \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "content": "content",\n "response_type": "Entities"\n }\'', + }, + python: { + method: 'tools.comprehend.medical', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nresponse = client.tools.comprehend.medical(\n content="content",\n response_type="Entities",\n)\nprint(response.entities)', + }, + typescript: { + method: 'client.tools.comprehend.medical', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst response = await client.tools.comprehend.medical({\n content: 'content',\n response_type: 'Entities',\n});\n\nconsole.log(response.entities);", + }, + }, + }, + { + name: 'translate', + endpoint: '/v1/translation', + httpMethod: 'post', + summary: 'Translate text', + description: 'Translate text from one language to another.', + stainlessPath: '(resource) translation > (method) translate', + qualified: 'client.translation.translate', + params: [ + 'formality: boolean;', + 'length_control: boolean;', + 'mask_profanity: boolean;', + "model: 'palmyra-translate';", + 'source_language_code: string;', + 'target_language_code: string;', + 'text: string;', + ], + response: '{ data: string; }', + markdown: + "## translate\n\n`client.translation.translate(formality: boolean, length_control: boolean, mask_profanity: boolean, model: 'palmyra-translate', source_language_code: string, target_language_code: string, text: string): { data: string; }`\n\n**post** `/v1/translation`\n\nTranslate text from one language to another.\n\n### Parameters\n\n- `formality: boolean`\n Whether to use formal or informal language in the translation. See the [list of languages that support formality](https://dev.writer.com/api-reference/translation-api/language-support#formality). If the language does not support formality, this parameter is ignored.\n\n- `length_control: boolean`\n Whether to control the length of the translated text. See the [list of languages that support length control](https://dev.writer.com/api-reference/translation-api/language-support#length-control). If the language does not support length control, this parameter is ignored.\n\n- `mask_profanity: boolean`\n Whether to mask profane words in the translated text. See the [list of languages that do not support profanity masking](https://dev.writer.com/api-reference/translation-api/language-support#profanity-masking). If the language does not support profanity masking, this parameter is ignored.\n\n- `model: 'palmyra-translate'`\n The model to use for translation.\n\n- `source_language_code: string`\n The [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) language code of the original text to translate. For example, `en` for English, `zh` for Chinese, `fr` for French, `es` for Spanish. If the language has a variant, the code appends the two-digit [ISO-3166 country code](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes). For example, Mexican Spanish is `es-MX`. See the [list of supported languages and language codes](https://dev.writer.com/api-reference/translation-api/language-support).\n\n- `target_language_code: string`\n The [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) language code of the target language for the translation. For example, `en` for English, `zh` for Chinese, `fr` for French, `es` for Spanish. If the language has a variant, the code appends the two-digit [ISO-3166 country code](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes). For example, Mexican Spanish is `es-MX`. See the [list of supported languages and language codes](https://dev.writer.com/api-reference/translation-api/language-support).\n\n- `text: string`\n The text to translate. Maximum of 100,000 words.\n\n### Returns\n\n- `{ data: string; }`\n\n - `data: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst translationResponse = await client.translation.translate({\n formality: true,\n length_control: true,\n mask_profanity: true,\n model: 'palmyra-translate',\n source_language_code: 'en',\n target_language_code: 'es',\n text: 'Hello, world!',\n});\n\nconsole.log(translationResponse);\n```", + perLanguage: { + go: { + method: 'client.Translation.Translate', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\ttranslationResponse, err := client.Translation.Translate(context.TODO(), writersdk.TranslationTranslateParams{\n\t\tTranslationRequest: writersdk.TranslationRequestParam{\n\t\t\tFormality: writersdk.F(true),\n\t\t\tLengthControl: writersdk.F(true),\n\t\t\tMaskProfanity: writersdk.F(true),\n\t\t\tModel: writersdk.F(writersdk.TranslationRequestModelPalmyraTranslate),\n\t\t\tSourceLanguageCode: writersdk.F("en"),\n\t\t\tTargetLanguageCode: writersdk.F("es"),\n\t\t\tText: writersdk.F("Hello, world!"),\n\t\t},\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", translationResponse.Data)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/translation \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "formality": true,\n "length_control": true,\n "mask_profanity": true,\n "model": "palmyra-translate",\n "source_language_code": "en",\n "target_language_code": "es",\n "text": "Hello, world!"\n }\'', + }, + python: { + method: 'translation.translate', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\ntranslation_response = client.translation.translate(\n formality=True,\n length_control=True,\n mask_profanity=True,\n model="palmyra-translate",\n source_language_code="en",\n target_language_code="es",\n text="Hello, world!",\n)\nprint(translation_response.data)', + }, + typescript: { + method: 'client.translation.translate', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst translationResponse = await client.translation.translate({\n formality: true,\n length_control: true,\n mask_profanity: true,\n model: 'palmyra-translate',\n source_language_code: 'en',\n target_language_code: 'es',\n text: 'Hello, world!',\n});\n\nconsole.log(translationResponse.data);", + }, + }, + }, + { + name: 'analyze', + endpoint: '/v1/vision', + httpMethod: 'post', + summary: 'Analyze images', + description: + 'Submit images and documents with a prompt to generate an analysis. Supports JPG, PNG, PDF, and TXT files up to 7MB each.', + stainlessPath: '(resource) vision > (method) analyze', + qualified: 'client.vision.analyze', + params: [ + "model: 'palmyra-vision';", + 'prompt: string;', + 'variables: { file_id: string; name: string; }[];', + ], + response: '{ data: string; }', + markdown: + "## analyze\n\n`client.vision.analyze(model: 'palmyra-vision', prompt: string, variables: { file_id: string; name: string; }[]): { data: string; }`\n\n**post** `/v1/vision`\n\nSubmit images and documents with a prompt to generate an analysis. Supports JPG, PNG, PDF, and TXT files up to 7MB each.\n\n### Parameters\n\n- `model: 'palmyra-vision'`\n The model to use for image analysis.\n\n- `prompt: string`\n The prompt to use for the image analysis. The prompt must include the name of each image variable, surrounded by double curly braces (`{{}}`). For example, `Describe the difference between the image {{image_1}} and the image {{image_2}}`.\n\n- `variables: { file_id: string; name: string; }[]`\n\n### Returns\n\n- `{ data: string; }`\n\n - `data: string`\n\n### Example\n\n```typescript\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst visionResponse = await client.vision.analyze({\n model: 'palmyra-vision',\n prompt: 'Describe the difference between the image {{image_1}} and the image {{image_2}}.',\n variables: [{ file_id: 'f1234', name: 'image_1' }, { file_id: 'f9876', name: 'image_2' }],\n});\n\nconsole.log(visionResponse);\n```", + perLanguage: { + go: { + method: 'client.Vision.Analyze', + example: + 'package main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"),\n\t)\n\tvisionResponse, err := client.Vision.Analyze(context.TODO(), writersdk.VisionAnalyzeParams{\n\t\tVisionRequest: writersdk.VisionRequestParam{\n\t\t\tModel: writersdk.F(writersdk.VisionRequestModelPalmyraVision),\n\t\t\tPrompt: writersdk.F("Describe the difference between the image {{image_1}} and the image {{image_2}}."),\n\t\t\tVariables: writersdk.F([]writersdk.VisionRequestVariableParam{{\n\t\t\t\tFileID: writersdk.F("f1234"),\n\t\t\t\tName: writersdk.F("image_1"),\n\t\t\t}, {\n\t\t\t\tFileID: writersdk.F("f9876"),\n\t\t\t\tName: writersdk.F("image_2"),\n\t\t\t}}),\n\t\t},\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", visionResponse.Data)\n}\n', + }, + http: { + example: + 'curl https://api.writer.com/v1/vision \\\n -H \'Content-Type: application/json\' \\\n -H "Authorization: Bearer $WRITER_API_KEY" \\\n -d \'{\n "model": "palmyra-vision",\n "prompt": "Describe the difference between the image {{image_1}} and the image {{image_2}}.",\n "variables": [\n {\n "file_id": "f1234",\n "name": "image_1"\n },\n {\n "file_id": "f9876",\n "name": "image_2"\n }\n ]\n }\'', + }, + python: { + method: 'vision.analyze', + example: + 'import os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\nvision_response = client.vision.analyze(\n model="palmyra-vision",\n prompt="Describe the difference between the image {{image_1}} and the image {{image_2}}.",\n variables=[{\n "name": "image_1",\n "file_id": "f1234",\n }, {\n "name": "image_2",\n "file_id": "f9876",\n }],\n)\nprint(vision_response.data)', + }, + typescript: { + method: 'client.vision.analyze', + example: + "import Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst visionResponse = await client.vision.analyze({\n model: 'palmyra-vision',\n prompt: 'Describe the difference between the image {{image_1}} and the image {{image_2}}.',\n variables: [\n { name: 'image_1', file_id: 'f1234' },\n { name: 'image_2', file_id: 'f9876' },\n ],\n});\n\nconsole.log(visionResponse.data);", + }, + }, + }, +]; + +const EMBEDDED_READMES: { language: string; content: string }[] = [ + { + language: 'python', + content: + '# Writer Python API library\n\n\n[![PyPI version](https://img.shields.io/pypi/v/writer-sdk.svg?label=pypi%20(stable))](https://pypi.org/project/writer-sdk/)\n\nThe Writer Python library provides convenient access to the Writer REST API from any Python 3.9+\napplication. The library includes type definitions for all request params and response fields,\nand offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).\n\n\n\nIt is generated with [Stainless](https://www.stainless.com/).\n\n## MCP Server\n\nUse the Writer MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application.\n\n[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=writer-sdk-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIndyaXRlci1zZGstbWNwIl0sImVudiI6eyJXUklURVJfQVBJX0tFWSI6Ik15IEFQSSBLZXkifX0)\n[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22writer-sdk-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22writer-sdk-mcp%22%5D%2C%22env%22%3A%7B%22WRITER_API_KEY%22%3A%22My%20API%20Key%22%7D%7D)\n\n> Note: You may need to set environment variables in your MCP client.\n\n## Documentation\n\nThe REST API documentation can be found on [dev.writer.com](https://dev.writer.com/api-guides/introduction). The full API of this library can be found in [api.md](api.md).\n\n## Installation\n\n```sh\n# install from PyPI\npip install writer-sdk\n```\n\n## Usage\n\nThe full API of this library can be found in [api.md](api.md).\n\n```python\nimport os\nfrom writerai import Writer\n\nclient = Writer(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\n\nchat_completion = client.chat.chat(\n messages=[{\n "content": "Write a haiku about programming",\n "role": "user",\n }],\n model="palmyra-x5",\n)\nprint(chat_completion.id)\n```\n\nWhile you can provide an `api_key` keyword argument,\nwe recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)\nto add `WRITER_API_KEY="My API Key"` to your `.env` file\nso that your API Key is not stored in source control.\n\n## Async usage\n\nSimply import `AsyncWriter` instead of `Writer` and use `await` with each API call:\n\n```python\nimport os\nimport asyncio\nfrom writerai import AsyncWriter\n\nclient = AsyncWriter(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n)\n\nasync def main() -> None:\n chat_completion = await client.chat.chat(\n messages=[{\n "content": "Write a haiku about programming",\n "role": "user",\n }],\n model="palmyra-x5",\n )\n print(chat_completion.id)\n\nasyncio.run(main())\n```\n\nFunctionality between the synchronous and asynchronous clients is otherwise identical.\n\n### With aiohttp\n\nBy default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.\n\nYou can enable this by installing `aiohttp`:\n\n```sh\n# install from PyPI\npip install writer-sdk[aiohttp]\n```\n\nThen you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:\n\n```python\nimport os\nimport asyncio\nfrom writerai import DefaultAioHttpClient\nfrom writerai import AsyncWriter\n\nasync def main() -> None:\n async with AsyncWriter(\n api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted\n http_client=DefaultAioHttpClient(),\n) as client:\n chat_completion = await client.chat.chat(\n messages=[{\n "content": "Write a haiku about programming",\n "role": "user",\n }],\n model="palmyra-x5",\n )\n print(chat_completion.id)\n\nasyncio.run(main())\n```\n\n## Streaming responses\n\nWe provide support for streaming responses using Server Side Events (SSE).\n\n```python\nfrom writerai import Writer\n\nclient = Writer()\n\nstream = client.chat.chat(\n messages=[{\n "content": "Write a haiku about programming",\n "role": "user",\n }],\n model="palmyra-x5",\n stream=True,\n)\nfor chat_completion in stream:\n print(chat_completion.id)\n```\n\nThe async client uses the exact same interface.\n\n```python\nfrom writerai import AsyncWriter\n\nclient = AsyncWriter()\n\nstream = await client.chat.chat(\n messages=[{\n "content": "Write a haiku about programming",\n "role": "user",\n }],\n model="palmyra-x5",\n stream=True,\n)\nasync for chat_completion in stream:\n print(chat_completion.id)\n```\n\n## Using types\n\nNested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like:\n\n- Serializing back into JSON, `model.to_json()`\n- Converting to a dictionary, `model.to_dict()`\n\nTyped requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.\n\n## Pagination\n\nList methods in the Writer API are paginated.\n\nThis library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:\n\n```python\nfrom writerai import Writer\n\nclient = Writer()\n\nall_graphs = []\n# Automatically fetches more pages as needed.\nfor graph in client.graphs.list():\n # Do something with graph here\n all_graphs.append(graph)\nprint(all_graphs)\n```\n\nOr, asynchronously:\n\n```python\nimport asyncio\nfrom writerai import AsyncWriter\n\nclient = AsyncWriter()\n\nasync def main() -> None:\n all_graphs = []\n # Iterate through items across all pages, issuing requests as needed.\n async for graph in client.graphs.list():\n all_graphs.append(graph)\n print(all_graphs)\n\nasyncio.run(main())\n```\n\nAlternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:\n\n```python\nfirst_page = await client.graphs.list()\nif first_page.has_next_page():\n print(f"will fetch next page using these details: {first_page.next_page_info()}")\n next_page = await first_page.get_next_page()\n print(f"number of items we just fetched: {len(next_page.data)}")\n\n# Remove `await` for non-async usage.\n```\n\nOr just work directly with the returned data:\n\n```python\nfirst_page = await client.graphs.list()\n\nprint(f"next page cursor: {first_page.after}") # => "next page cursor: ..."\nfor graph in first_page.data:\n print(graph.id)\n\n# Remove `await` for non-async usage.\n```\n\n## Nested params\n\nNested parameters are dictionaries, typed using `TypedDict`, for example:\n\n```python\nfrom writerai import Writer\n\nclient = Writer()\n\nchat_completion = client.chat.chat(\n messages=[{\n "role": "user"\n }],\n model="model",\n response_format={\n "type": "text"\n },\n)\nprint(chat_completion.response_format)\n```\n\n\n\n## Handling errors\n\nWhen the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `writerai.APIConnectionError` is raised.\n\nWhen the API returns a non-success status code (that is, 4xx or 5xx\nresponse), a subclass of `writerai.APIStatusError` is raised, containing `status_code` and `response` properties.\n\nAll errors inherit from `writerai.APIError`.\n\n```python\nimport writerai\nfrom writerai import Writer\n\nclient = Writer()\n\ntry:\n client.chat.chat(\n messages=[{\n "content": "Write a haiku about programming",\n "role": "user",\n }],\n model="palmyra-x5",\n )\nexcept writerai.APIConnectionError as e:\n print("The server could not be reached")\n print(e.__cause__) # an underlying Exception, likely raised within httpx.\nexcept writerai.RateLimitError as e:\n print("A 429 status code was received; we should back off a bit.")\nexcept writerai.APIStatusError as e:\n print("Another non-200-range status code was received")\n print(e.status_code)\n print(e.response)\n```\n\nError codes are as follows:\n\n| Status Code | Error Type |\n| ----------- | -------------------------- |\n| 400 | `BadRequestError` |\n| 401 | `AuthenticationError` |\n| 403 | `PermissionDeniedError` |\n| 404 | `NotFoundError` |\n| 422 | `UnprocessableEntityError` |\n| 429 | `RateLimitError` |\n| >=500 | `InternalServerError` |\n| N/A | `APIConnectionError` |\n\n### Retries\n\nCertain errors are automatically retried 7 times by default, with a short exponential backoff.\nConnection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict,\n429 Rate Limit, and >=500 Internal errors are all retried by default.\n\nYou can use the `max_retries` option to configure or disable retry settings:\n\n```python\nfrom writerai import Writer\n\n# Configure the default for all requests:\nclient = Writer(\n # default is 2\n max_retries=0,\n)\n\n# Or, configure per-request:\nclient.with_options(max_retries = 5).chat.chat(\n messages=[{\n "content": "Write a haiku about programming",\n "role": "user",\n }],\n model="palmyra-x5",\n)\n```\n\n### Timeouts\n\nBy default requests time out after 3 minutes. You can configure this with a `timeout` option,\nwhich accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:\n\n```python\nfrom writerai import Writer\n\n# Configure the default for all requests:\nclient = Writer(\n # 20 seconds (default is 3 minutes)\n timeout=20.0,\n)\n\n# More granular control:\nclient = Writer(\n timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),\n)\n\n# Override per-request:\nclient.with_options(timeout = 5.0).chat.chat(\n messages=[{\n "content": "Write a haiku about programming",\n "role": "user",\n }],\n model="palmyra-x5",\n)\n```\n\nOn timeout, an `APITimeoutError` is thrown.\n\nNote that requests that time out are [retried twice by default](#retries).\n\n\n\n## Advanced\n\n### Logging\n\nWe use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module.\n\nYou can enable logging by setting the environment variable `WRITER_LOG` to `info`.\n\n```shell\n$ export WRITER_LOG=info\n```\n\nOr to `debug` for more verbose logging.\n\n### How to tell whether `None` means `null` or missing\n\nIn an API response, a field may be explicitly `null`, or missing entirely; in either case, its value is `None` in this library. You can differentiate the two cases with `.model_fields_set`:\n\n```py\nif response.my_field is None:\n if \'my_field\' not in response.model_fields_set:\n print(\'Got json like {}, without a "my_field" key present at all.\')\n else:\n print(\'Got json like {"my_field": null}.\')\n```\n\n### Accessing raw response data (e.g. headers)\n\nThe "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g.,\n\n```py\nfrom writerai import Writer\n\nclient = Writer()\nresponse = client.chat.with_raw_response.chat(\n messages=[{\n "content": "Write a haiku about programming",\n "role": "user",\n }],\n model="palmyra-x5",\n)\nprint(response.headers.get(\'X-My-Header\'))\n\nchat = response.parse() # get the object that `chat.chat()` would have returned\nprint(chat.id)\n```\n\nThese methods return an [`APIResponse`](https://github.com/writer/writer-python/tree/main/src/writerai/_response.py) object.\n\nThe async client returns an [`AsyncAPIResponse`](https://github.com/writer/writer-python/tree/main/src/writerai/_response.py) with the same structure, the only difference being `await`able methods for reading the response content.\n\n#### `.with_streaming_response`\n\nThe above interface eagerly reads the full response body when you make the request, which may not always be what you want.\n\nTo stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods.\n\n```python\nwith client.chat.with_streaming_response.chat(\n messages=[{\n "content": "Write a haiku about programming",\n "role": "user",\n }],\n model="palmyra-x5",\n) as response :\n print(response.headers.get(\'X-My-Header\'))\n\n for line in response.iter_lines():\n print(line)\n```\n\nThe context manager is required so that the response will reliably be closed.\n\n### Making custom/undocumented requests\n\nThis library is typed for convenient access to the documented API.\n\nIf you need to access undocumented endpoints, params, or response properties, the library can still be used.\n\n#### Undocumented endpoints\n\nTo make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other\nhttp verbs. Options on the client will be respected (such as retries) when making this request.\n\n```py\nimport httpx\n\nresponse = client.post(\n "/foo",\n cast_to=httpx.Response,\n body={"my_param": True},\n)\n\nprint(response.headers.get("x-foo"))\n```\n\n#### Undocumented request params\n\nIf you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` request\noptions.\n\n#### Undocumented response properties\n\nTo access undocumented response properties, you can access the extra fields like `response.unknown_prop`. You\ncan also get all the extra fields on the Pydantic model as a dict with\n[`response.model_extra`](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_extra).\n\n### Configuring the HTTP client\n\nYou can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including:\n\n- Support for [proxies](https://www.python-httpx.org/advanced/proxies/)\n- Custom [transports](https://www.python-httpx.org/advanced/transports/)\n- Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality\n\n```python\nimport httpx\nfrom writerai import Writer, DefaultHttpxClient\n\nclient = Writer(\n # Or use the `WRITER_BASE_URL` env var\n base_url="http://my.test.server.example.com:8083",\n http_client=DefaultHttpxClient(proxy="http://my.test.proxy.example.com", transport=httpx.HTTPTransport(local_address="0.0.0.0")),\n)\n```\n\nYou can also customize the client on a per-request basis by using `with_options()`:\n\n```python\nclient.with_options(http_client=DefaultHttpxClient(...))\n```\n\n### Managing HTTP resources\n\nBy default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting.\n\n```py\nfrom writerai import Writer\n\nwith Writer() as client:\n # make requests here\n ...\n\n# HTTP client is now closed\n```\n\n## Versioning\n\nThis package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:\n\n1. Changes that only affect static types, without breaking runtime behavior.\n2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_\n3. Changes that we do not expect to impact the vast majority of users in practice.\n\nWe take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.\n\nWe are keen for your feedback; please open an [issue](https://www.github.com/writer/writer-python/issues) with questions, bugs, or suggestions.\n\n### Determining the installed version\n\nIf you\'ve upgraded to the latest version but aren\'t seeing any new features you were expecting then your python environment is likely still using an older version.\n\nYou can determine the version that is being used at runtime with:\n\n```py\nimport writerai\nprint(writerai.__version__)\n```\n\n## Requirements\n\nPython 3.9 or higher.\n\n## Contributing\n\nSee [the contributing documentation](./CONTRIBUTING.md).\n', + }, + { + language: 'go', + content: + '# Writer Go API Library\n\nGo Reference\n\nThe Writer Go library provides convenient access to the [Writer REST API](https://dev.writer.com/api-guides/introduction)\nfrom applications written in Go.\n\nIt is generated with [Stainless](https://www.stainless.com/).\n\n## MCP Server\n\nUse the Writer MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application.\n\n[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=writer-sdk-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIndyaXRlci1zZGstbWNwIl0sImVudiI6eyJXUklURVJfQVBJX0tFWSI6Ik15IEFQSSBLZXkifX0)\n[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22writer-sdk-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22writer-sdk-mcp%22%5D%2C%22env%22%3A%7B%22WRITER_API_KEY%22%3A%22My%20API%20Key%22%7D%7D)\n\n> Note: You may need to set environment variables in your MCP client.\n\n## Installation\n\n\n\n```go\nimport (\n\t"github.com/stainless-sdks/writer-go" // imported as SDK_PackageName\n)\n```\n\n\n\nOr to pin the version:\n\n\n\n```sh\ngo get -u \'github.com/stainless-sdks/writer-go@v0.0.1\'\n```\n\n\n\n## Requirements\n\nThis library requires Go 1.22+.\n\n## Usage\n\nThe full API of this library can be found in [api.md](api.md).\n\n```go\npackage main\n\nimport (\n\t"context"\n\t"fmt"\n\n\t"github.com/stainless-sdks/writer-go"\n\t"github.com/stainless-sdks/writer-go/option"\n\t"github.com/stainless-sdks/writer-go/shared"\n)\n\nfunc main() {\n\tclient := writersdk.NewClient(\n\t\toption.WithAPIKey("My API Key"), // defaults to os.LookupEnv("WRITER_API_KEY")\n\t)\n\tchatCompletion, err := client.Chat.Chat(context.TODO(), writersdk.ChatChatParams{\n\t\tMessages: writersdk.F([]writersdk.ChatChatParamsMessage{{\n\t\t\tContent: writersdk.F[writersdk.ChatChatParamsMessagesContentUnion](shared.UnionString("Write a haiku about programming")),\n\t\t\tRole: writersdk.F(writersdk.ChatChatParamsMessagesRoleUser),\n\t\t}}),\n\t\tModel: writersdk.F("palmyra-x5"),\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf("%+v\\n", chatCompletion.ID)\n}\n\n```\n\n### Request fields\n\nAll request parameters are wrapped in a generic `Field` type,\nwhich we use to distinguish zero values from null or omitted fields.\n\nThis prevents accidentally sending a zero value if you forget a required parameter,\nand enables explicitly sending `null`, `false`, `\'\'`, or `0` on optional parameters.\nAny field not specified is not sent.\n\nTo construct fields with values, use the helpers `String()`, `Int()`, `Float()`, or most commonly, the generic `F[T]()`.\nTo send a null, use `Null[T]()`, and to send a nonconforming value, use `Raw[T](any)`. For example:\n\n```go\nparams := FooParams{\n\tName: SDK_PackageName.F("hello"),\n\n\t// Explicitly send `"description": null`\n\tDescription: SDK_PackageName.Null[string](),\n\n\tPoint: SDK_PackageName.F(SDK_PackageName.Point{\n\t\tX: SDK_PackageName.Int(0),\n\t\tY: SDK_PackageName.Int(1),\n\n\t\t// In cases where the API specifies a given type,\n\t\t// but you want to send something else, use `Raw`:\n\t\tZ: SDK_PackageName.Raw[int64](0.01), // sends a float\n\t}),\n}\n```\n\n### Response objects\n\nAll fields in response structs are value types (not pointers or wrappers).\n\nIf a given field is `null`, not present, or invalid, the corresponding field\nwill simply be its zero value.\n\nAll response structs also include a special `JSON` field, containing more detailed\ninformation about each property, which you can use like so:\n\n```go\nif res.Name == "" {\n\t// true if `"name"` is either not present or explicitly null\n\tres.JSON.Name.IsNull()\n\n\t// true if the `"name"` key was not present in the response JSON at all\n\tres.JSON.Name.IsMissing()\n\n\t// When the API returns data that cannot be coerced to the expected type:\n\tif res.JSON.Name.IsInvalid() {\n\t\traw := res.JSON.Name.Raw()\n\n\t\tlegacyName := struct{\n\t\t\tFirst string `json:"first"`\n\t\t\tLast string `json:"last"`\n\t\t}{}\n\t\tjson.Unmarshal([]byte(raw), &legacyName)\n\t\tname = legacyName.First + " " + legacyName.Last\n\t}\n}\n```\n\nThese `.JSON` structs also include an `Extras` map containing\nany properties in the json response that were not specified\nin the struct. This can be useful for API features not yet\npresent in the SDK.\n\n```go\nbody := res.JSON.ExtraFields["my_unexpected_field"].Raw()\n```\n\n### RequestOptions\n\nThis library uses the functional options pattern. Functions defined in the\n`SDK_PackageOptionName` package return a `RequestOption`, which is a closure that mutates a\n`RequestConfig`. These options can be supplied to the client or at individual\nrequests. For example:\n\n```go\nclient := SDK_PackageName.SDK_ClientInitializerName(\n\t// Adds a header to every request made by the client\n\tSDK_PackageOptionName.WithHeader("X-Some-Header", "custom_header_info"),\n)\n\nclient.Chat.Chat(context.TODO(), ...,\n\t// Override the header\n\tSDK_PackageOptionName.WithHeader("X-Some-Header", "some_other_custom_header_info"),\n\t// Add an undocumented field to the request body, using sjson syntax\n\tSDK_PackageOptionName.WithJSONSet("some.json.path", map[string]string{"my": "object"}),\n)\n```\n\nSee the [full list of request options](https://pkg.go.dev/github.com/stainless-sdks/writer-go/SDK_PackageOptionName).\n\n### Pagination\n\nThis library provides some conveniences for working with paginated list endpoints.\n\nYou can use `.ListAutoPaging()` methods to iterate through items across all pages:\n\n```go\niter := client.Graphs.ListAutoPaging(context.TODO(), writersdk.GraphListParams{})\n// Automatically fetches more pages as needed.\nfor iter.Next() {\n\tgraph := iter.Current()\n\tfmt.Printf("%+v\\n", graph)\n}\nif err := iter.Err(); err != nil {\n\tpanic(err.Error())\n}\n```\n\nOr you can use simple `.List()` methods to fetch a single page and receive a standard response object\nwith additional helper methods like `.GetNextPage()`, e.g.:\n\n```go\npage, err := client.Graphs.List(context.TODO(), writersdk.GraphListParams{})\nfor page != nil {\n\tfor _, graph := range page.Data {\n\t\tfmt.Printf("%+v\\n", graph)\n\t}\n\tpage, err = page.GetNextPage()\n}\nif err != nil {\n\tpanic(err.Error())\n}\n```\n\n### Errors\n\nWhen the API returns a non-success status code, we return an error with type\n`*SDK_PackageName.Error`. This contains the `StatusCode`, `*http.Request`, and\n`*http.Response` values of the request, as well as the JSON of the error body\n(much like other response objects in the SDK).\n\nTo handle errors, we recommend that you use the `errors.As` pattern:\n\n```go\n_, err := client.Chat.Chat(context.TODO(), writersdk.ChatChatParams{\n\tMessages: writersdk.F([]writersdk.ChatChatParamsMessage{{\n\t\tContent: writersdk.F[writersdk.ChatChatParamsMessagesContentUnion](shared.UnionString("Write a haiku about programming")),\n\t\tRole: writersdk.F(writersdk.ChatChatParamsMessagesRoleUser),\n\t}}),\n\tModel: writersdk.F("palmyra-x5"),\n})\nif err != nil {\n\tvar apierr *writersdk.Error\n\tif errors.As(err, &apierr) {\n\t\tprintln(string(apierr.DumpRequest(true))) // Prints the serialized HTTP request\n\t\tprintln(string(apierr.DumpResponse(true))) // Prints the serialized HTTP response\n\t}\n\tpanic(err.Error()) // GET "/v1/chat": 400 Bad Request { ... }\n}\n```\n\nWhen other errors occur, they are returned unwrapped; for example,\nif HTTP transport fails, you might receive `*url.Error` wrapping `*net.OpError`.\n\n### Timeouts\n\nRequests do not time out by default; use context to configure a timeout for a request lifecycle.\n\nNote that if a request is [retried](#retries), the context timeout does not start over.\nTo set a per-retry timeout, use `SDK_PackageOptionName.WithRequestTimeout()`.\n\n```go\n// This sets the timeout for the request, including all the retries.\nctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)\ndefer cancel()\nclient.Chat.Chat(\n\tctx,\n\twritersdk.ChatChatParams{\n\t\tMessages: writersdk.F([]writersdk.ChatChatParamsMessage{{\n\t\t\tContent: writersdk.F[writersdk.ChatChatParamsMessagesContentUnion](shared.UnionString("Write a haiku about programming")),\n\t\t\tRole: writersdk.F(writersdk.ChatChatParamsMessagesRoleUser),\n\t\t}}),\n\t\tModel: writersdk.F("palmyra-x5"),\n\t},\n\t// This sets the per-retry timeout\n\toption.WithRequestTimeout(20*time.Second),\n)\n```\n\n### File uploads\n\nRequest parameters that correspond to file uploads in multipart requests are typed as\n`param.Field[io.Reader]`. The contents of the `io.Reader` will by default be sent as a multipart form\npart with the file name of "anonymous_file" and content-type of "application/octet-stream".\n\nThe file name and content-type can be customized by implementing `Name() string` or `ContentType()\nstring` on the run-time type of `io.Reader`. Note that `os.File` implements `Name() string`, so a\nfile returned by `os.Open` will be sent with the file name on disk.\n\nWe also provide a helper `SDK_PackageName.FileParam(reader io.Reader, filename string, contentType string)`\nwhich can be used to wrap any `io.Reader` with the appropriate file name and content type.\n\n\n\n### Retries\n\nCertain errors will be automatically retried 7 times by default, with a short exponential backoff.\nWe retry by default all connection errors, 408 Request Timeout, 409 Conflict, 429 Rate Limit,\nand >=500 Internal errors.\n\nYou can use the `WithMaxRetries` option to configure or disable this:\n\n```go\n// Configure the default for all requests:\nclient := writersdk.NewClient(\n\toption.WithMaxRetries(0), // default is 2\n)\n\n// Override per-request:\nclient.Chat.Chat(\n\tcontext.TODO(),\n\twritersdk.ChatChatParams{\n\t\tMessages: writersdk.F([]writersdk.ChatChatParamsMessage{{\n\t\t\tContent: writersdk.F[writersdk.ChatChatParamsMessagesContentUnion](shared.UnionString("Write a haiku about programming")),\n\t\t\tRole: writersdk.F(writersdk.ChatChatParamsMessagesRoleUser),\n\t\t}}),\n\t\tModel: writersdk.F("palmyra-x5"),\n\t},\n\toption.WithMaxRetries(5),\n)\n```\n\n\n### Accessing raw response data (e.g. response headers)\n\nYou can access the raw HTTP response data by using the `option.WithResponseInto()` request option. This is useful when\nyou need to examine response headers, status codes, or other details.\n\n```go\n// Create a variable to store the HTTP response\nvar response *http.Response\nchatCompletion, err := client.Chat.Chat(\n\tcontext.TODO(),\n\twritersdk.ChatChatParams{\n\t\tMessages: writersdk.F([]writersdk.ChatChatParamsMessage{{\n\t\t\tContent: writersdk.F[writersdk.ChatChatParamsMessagesContentUnion](shared.UnionString("Write a haiku about programming")),\n\t\t\tRole: writersdk.F(writersdk.ChatChatParamsMessagesRoleUser),\n\t\t}}),\n\t\tModel: writersdk.F("palmyra-x5"),\n\t},\n\toption.WithResponseInto(&response),\n)\nif err != nil {\n\t// handle error\n}\nfmt.Printf("%+v\\n", chatCompletion)\n\nfmt.Printf("Status Code: %d\\n", response.StatusCode)\nfmt.Printf("Headers: %+#v\\n", response.Header)\n```\n\n### Making custom/undocumented requests\n\nThis library is typed for convenient access to the documented API. If you need to access undocumented\nendpoints, params, or response properties, the library can still be used.\n\n#### Undocumented endpoints\n\nTo make requests to undocumented endpoints, you can use `client.Get`, `client.Post`, and other HTTP verbs.\n`RequestOptions` on the client, such as retries, will be respected when making these requests.\n\n```go\nvar (\n // params can be an io.Reader, a []byte, an encoding/json serializable object,\n // or a "…Params" struct defined in this library.\n params map[string]interface{}\n\n // result can be an []byte, *http.Response, a encoding/json deserializable object,\n // or a model defined in this library.\n result *http.Response\n)\nerr := client.Post(context.Background(), "/unspecified", params, &result)\nif err != nil {\n …\n}\n```\n\n#### Undocumented request params\n\nTo make requests using undocumented parameters, you may use either the `SDK_PackageOptionName.WithQuerySet()`\nor the `SDK_PackageOptionName.WithJSONSet()` methods.\n\n```go\nparams := FooNewParams{\n ID: SDK_PackageName.F("id_xxxx"),\n Data: SDK_PackageName.F(FooNewParamsData{\n FirstName: SDK_PackageName.F("John"),\n }),\n}\nclient.Foo.New(context.Background(), params, SDK_PackageOptionName.WithJSONSet("data.last_name", "Doe"))\n```\n\n#### Undocumented response properties\n\nTo access undocumented response properties, you may either access the raw JSON of the response as a string\nwith `result.JSON.RawJSON()`, or get the raw JSON of a particular field on the result with\n`result.JSON.Foo.Raw()`.\n\nAny fields that are not present on the response struct will be saved and can be accessed by `result.JSON.ExtraFields()` which returns the extra fields as a `map[string]Field`.\n\n### Middleware\n\nWe provide `SDK_PackageOptionName.WithMiddleware` which applies the given\nmiddleware to requests.\n\n```go\nfunc Logger(req *http.Request, next SDK_PackageOptionName.MiddlewareNext) (res *http.Response, err error) {\n\t// Before the request\n\tstart := time.Now()\n\tLogReq(req)\n\n\t// Forward the request to the next handler\n\tres, err = next(req)\n\n\t// Handle stuff after the request\n\tend := time.Now()\n\tLogRes(res, err, start - end)\n\n return res, err\n}\n\nclient := SDK_PackageName.SDK_ClientInitializerName(\n\tSDK_PackageOptionName.WithMiddleware(Logger),\n)\n```\n\nWhen multiple middlewares are provided as variadic arguments, the middlewares\nare applied left to right. If `SDK_PackageOptionName.WithMiddleware` is given\nmultiple times, for example first in the client then the method, the\nmiddleware in the client will run first and the middleware given in the method\nwill run next.\n\nYou may also replace the default `http.Client` with\n`SDK_PackageOptionName.WithHTTPClient(client)`. Only one http client is\naccepted (this overwrites any previous client) and receives requests after any\nmiddleware has been applied.\n\n## Semantic versioning\n\nThis package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:\n\n1. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_\n2. Changes that we do not expect to impact the vast majority of users in practice.\n\nWe take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.\n\nWe are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/writer-go/issues) with questions, bugs, or suggestions.\n\n## Contributing\n\nSee [the contributing documentation](./CONTRIBUTING.md).\n', + }, + { + language: 'typescript', + content: + "# Writer TypeScript API Library\n\n[![NPM version](https://img.shields.io/npm/v/writer-sdk.svg?label=npm%20(stable))](https://npmjs.org/package/writer-sdk) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/writer-sdk)\n\nThis library provides convenient access to the Writer REST API from server-side TypeScript or JavaScript.\n\n\n\nThe REST API documentation can be found on [dev.writer.com](https://dev.writer.com/api-guides/introduction). The full API of this library can be found in [api.md](api.md).\n\nIt is generated with [Stainless](https://www.stainless.com/).\n\n## MCP Server\n\nUse the Writer MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application.\n\n[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=writer-sdk-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIndyaXRlci1zZGstbWNwIl0sImVudiI6eyJXUklURVJfQVBJX0tFWSI6Ik15IEFQSSBLZXkifX0)\n[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22writer-sdk-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22writer-sdk-mcp%22%5D%2C%22env%22%3A%7B%22WRITER_API_KEY%22%3A%22My%20API%20Key%22%7D%7D)\n\n> Note: You may need to set environment variables in your MCP client.\n\n## Installation\n\n```sh\nnpm install writer-sdk\n```\n\n\n\n## Usage\n\nThe full API of this library can be found in [api.md](api.md).\n\n\n```js\nimport Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst chatCompletion = await client.chat.chat({\n messages: [{ content: 'Write a haiku about programming', role: 'user' }],\n model: 'palmyra-x5',\n});\n\nconsole.log(chatCompletion.id);\n```\n\n## Streaming responses\n\nWe provide support for streaming responses using Server Sent Events (SSE).\n\n```ts\nimport Writer from 'writer-sdk';\n\nconst client = new Writer();\n\nconst stream = await client.chat.chat({\n messages: [{ content: 'Write a haiku about programming', role: 'user' }],\n model: 'palmyra-x5',\n stream: true,\n});\nfor await (const chatCompletionChunk of stream) {\n console.log(chatCompletionChunk.id);\n}\n```\n\nIf you need to cancel a stream, you can `break` from the loop\nor call `stream.controller.abort()`.\n\n### Request & Response types\n\nThis library includes TypeScript definitions for all request params and response fields. You may import and use them like so:\n\n\n```ts\nimport Writer from 'writer-sdk';\n\nconst client = new Writer({\n apiKey: process.env['WRITER_API_KEY'], // This is the default and can be omitted\n});\n\nconst params: Writer.ChatChatParams = {\n messages: [{ content: 'Write a haiku about programming', role: 'user' }],\n model: 'palmyra-x5',\n};\nconst chatCompletion: Writer.ChatCompletion = await client.chat.chat(params);\n```\n\nDocumentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors.\n\n\n\n\n\n## Handling errors\n\nWhen the library is unable to connect to the API,\nor if the API returns a non-success status code (i.e., 4xx or 5xx response),\na subclass of `APIError` will be thrown:\n\n\n```ts\nconst chatCompletion = await client.chat\n .chat({\n messages: [{ content: 'Write a haiku about programming', role: 'user' }],\n model: 'palmyra-x5',\n })\n .catch(async (err) => {\n if (err instanceof Writer.APIError) {\n console.log(err.status); // 400\n console.log(err.name); // BadRequestError\n console.log(err.headers); // {server: 'nginx', ...}\n } else {\n throw err;\n }\n });\n```\n\nError codes are as follows:\n\n| Status Code | Error Type |\n| ----------- | -------------------------- |\n| 400 | `BadRequestError` |\n| 401 | `AuthenticationError` |\n| 403 | `PermissionDeniedError` |\n| 404 | `NotFoundError` |\n| 422 | `UnprocessableEntityError` |\n| 429 | `RateLimitError` |\n| >=500 | `InternalServerError` |\n| N/A | `APIConnectionError` |\n\n### Retries\n\nCertain errors will be automatically retried 7 times by default, with a short exponential backoff.\nConnection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict,\n429 Rate Limit, and >=500 Internal errors will all be retried by default.\n\nYou can use the `maxRetries` option to configure or disable this:\n\n\n```js\n// Configure the default for all requests:\nconst client = new Writer({\n maxRetries: 0, // default is 2\n});\n\n// Or, configure per-request:\nawait client.chat.chat({ messages: [{ content: 'Write a haiku about programming', role: 'user' }], model: 'palmyra-x5' }, {\n maxRetries: 5,\n});\n```\n\n### Timeouts\n\nRequests time out after 3 minutes by default. You can configure this with a `timeout` option:\n\n\n```ts\n// Configure the default for all requests:\nconst client = new Writer({\n timeout: 20 * 1000, // 20 seconds (default is 3 minutes)\n});\n\n// Override per-request:\nawait client.chat.chat({ messages: [{ content: 'Write a haiku about programming', role: 'user' }], model: 'palmyra-x5' }, {\n timeout: 5 * 1000,\n});\n```\n\nOn timeout, an `APIConnectionTimeoutError` is thrown.\n\nNote that requests which time out will be [retried twice by default](#retries).\n\n## Auto-pagination\n\nList methods in the Writer API are paginated.\nYou can use the `for await … of` syntax to iterate through items across all pages:\n\n```ts\nasync function fetchAllGraphs(params) {\n const allGraphs = [];\n // Automatically fetches more pages as needed.\n for await (const graph of client.graphs.list()) {\n allGraphs.push(graph);\n }\n return allGraphs;\n}\n```\n\nAlternatively, you can request a single page at a time:\n\n```ts\nlet page = await client.graphs.list();\nfor (const graph of page.data) {\n console.log(graph);\n}\n\n// Convenience methods are provided for manually paginating:\nwhile (page.hasNextPage()) {\n page = await page.getNextPage();\n // ...\n}\n```\n\n\n\n## Advanced Usage\n\n### Accessing raw Response data (e.g., headers)\n\nThe \"raw\" `Response` returned by `fetch()` can be accessed through the `.asResponse()` method on the `APIPromise` type that all methods return.\nThis method returns as soon as the headers for a successful response are received and does not consume the response body, so you are free to write custom parsing or streaming logic.\n\nYou can also use the `.withResponse()` method to get the raw `Response` along with the parsed data.\nUnlike `.asResponse()` this method consumes the body, returning once it is parsed.\n\n\n```ts\nconst client = new Writer();\n\nconst response = await client.chat\n .chat({\n messages: [{ content: 'Write a haiku about programming', role: 'user' }],\n model: 'palmyra-x5',\n })\n .asResponse();\nconsole.log(response.headers.get('X-My-Header'));\nconsole.log(response.statusText); // access the underlying Response object\n\nconst { data: chatCompletion, response: raw } = await client.chat\n .chat({\n messages: [{ content: 'Write a haiku about programming', role: 'user' }],\n model: 'palmyra-x5',\n })\n .withResponse();\nconsole.log(raw.headers.get('X-My-Header'));\nconsole.log(chatCompletion.id);\n```\n\n### Logging\n\n> [!IMPORTANT]\n> All log messages are intended for debugging only. The format and content of log messages\n> may change between releases.\n\n#### Log levels\n\nThe log level can be configured in two ways:\n\n1. Via the `WRITER_LOG` environment variable\n2. Using the `logLevel` client option (overrides the environment variable if set)\n\n```ts\nimport Writer from 'writer-sdk';\n\nconst client = new Writer({\n logLevel: 'debug', // Show all log messages\n});\n```\n\nAvailable log levels, from most to least verbose:\n\n- `'debug'` - Show debug messages, info, warnings, and errors\n- `'info'` - Show info messages, warnings, and errors\n- `'warn'` - Show warnings and errors (default)\n- `'error'` - Show only errors\n- `'off'` - Disable all logging\n\nAt the `'debug'` level, all HTTP requests and responses are logged, including headers and bodies.\nSome authentication-related headers are redacted, but sensitive data in request and response bodies\nmay still be visible.\n\n#### Custom logger\n\nBy default, this library logs to `globalThis.console`. You can also provide a custom logger.\nMost logging libraries are supported, including [pino](https://www.npmjs.com/package/pino), [winston](https://www.npmjs.com/package/winston), [bunyan](https://www.npmjs.com/package/bunyan), [consola](https://www.npmjs.com/package/consola), [signale](https://www.npmjs.com/package/signale), and [@std/log](https://jsr.io/@std/log). If your logger doesn't work, please open an issue.\n\nWhen providing a custom logger, the `logLevel` option still controls which messages are emitted, messages\nbelow the configured level will not be sent to your logger.\n\n```ts\nimport Writer from 'writer-sdk';\nimport pino from 'pino';\n\nconst logger = pino();\n\nconst client = new Writer({\n logger: logger.child({ name: 'Writer' }),\n logLevel: 'debug', // Send all messages to pino, allowing it to filter\n});\n```\n\n### Making custom/undocumented requests\n\nThis library is typed for convenient access to the documented API. If you need to access undocumented\nendpoints, params, or response properties, the library can still be used.\n\n#### Undocumented endpoints\n\nTo make requests to undocumented endpoints, you can use `client.get`, `client.post`, and other HTTP verbs.\nOptions on the client, such as retries, will be respected when making these requests.\n\n```ts\nawait client.post('/some/path', {\n body: { some_prop: 'foo' },\n query: { some_query_arg: 'bar' },\n});\n```\n\n#### Undocumented request params\n\nTo make requests using undocumented parameters, you may use `// @ts-expect-error` on the undocumented\nparameter. This library doesn't validate at runtime that the request matches the type, so any extra values you\nsend will be sent as-is.\n\n```ts\nclient.chat.chat({\n // ...\n // @ts-expect-error baz is not yet public\n baz: 'undocumented option',\n});\n```\n\nFor requests with the `GET` verb, any extra params will be in the query, all other requests will send the\nextra param in the body.\n\nIf you want to explicitly send an extra argument, you can do so with the `query`, `body`, and `headers` request\noptions.\n\n#### Undocumented response properties\n\nTo access undocumented response properties, you may access the response object with `// @ts-expect-error` on\nthe response object, or cast the response object to the requisite type. Like the request params, we do not\nvalidate or strip extra properties from the response from the API.\n\n### Customizing the fetch client\n\nBy default, this library expects a global `fetch` function is defined.\n\nIf you want to use a different `fetch` function, you can either polyfill the global:\n\n```ts\nimport fetch from 'my-fetch';\n\nglobalThis.fetch = fetch;\n```\n\nOr pass it to the client:\n\n```ts\nimport Writer from 'writer-sdk';\nimport fetch from 'my-fetch';\n\nconst client = new Writer({ fetch });\n```\n\n### Fetch options\n\nIf you want to set custom `fetch` options without overriding the `fetch` function, you can provide a `fetchOptions` object when instantiating the client or making a request. (Request-specific options override client options.)\n\n```ts\nimport Writer from 'writer-sdk';\n\nconst client = new Writer({\n fetchOptions: {\n // `RequestInit` options\n },\n});\n```\n\n#### Configuring proxies\n\nTo modify proxy behavior, you can provide custom `fetchOptions` that add runtime-specific proxy\noptions to requests:\n\n **Node** [[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)]\n\n```ts\nimport Writer from 'writer-sdk';\nimport * as undici from 'undici';\n\nconst proxyAgent = new undici.ProxyAgent('http://localhost:8888');\nconst client = new Writer({\n fetchOptions: {\n dispatcher: proxyAgent,\n },\n});\n```\n\n **Bun** [[docs](https://bun.sh/guides/http/proxy)]\n\n```ts\nimport Writer from 'writer-sdk';\n\nconst client = new Writer({\n fetchOptions: {\n proxy: 'http://localhost:8888',\n },\n});\n```\n\n **Deno** [[docs](https://docs.deno.com/api/deno/~/Deno.createHttpClient)]\n\n```ts\nimport Writer from 'npm:writer-sdk';\n\nconst httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } });\nconst client = new Writer({\n fetchOptions: {\n client: httpClient,\n },\n});\n```\n\n## Frequently Asked Questions\n\n## Semantic versioning\n\nThis package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:\n\n1. Changes that only affect static types, without breaking runtime behavior.\n2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_\n3. Changes that we do not expect to impact the vast majority of users in practice.\n\nWe take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.\n\nWe are keen for your feedback; please open an [issue](https://www.github.com/writer/writer-node/issues) with questions, bugs, or suggestions.\n\n## Requirements\n\nTypeScript >= 4.9 is supported.\n\nThe following runtimes are supported:\n\n- Web browsers (Up-to-date Chrome, Firefox, Safari, Edge, and more)\n- Node.js 20 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions.\n- Deno v1.28.0 or higher.\n- Bun 1.0 or later.\n- Cloudflare Workers.\n- Vercel Edge Runtime.\n- Jest 28 or greater with the `\"node\"` environment (`\"jsdom\"` is not supported at this time).\n- Nitro v2.6 or greater.\n\nNote that React Native is not supported at this time.\n\nIf you are interested in other runtime environments, please open or upvote an issue on GitHub.\n\n## Contributing\n\nSee [the contributing documentation](./CONTRIBUTING.md).\n", + }, +]; + +const INDEX_OPTIONS = { + fields: [ + 'name', + 'endpoint', + 'summary', + 'description', + 'qualified', + 'stainlessPath', + 'content', + 'sectionContext', + ], + storeFields: ['kind', '_original'], + searchOptions: { + prefix: true, + fuzzy: 0.1, + boost: { + name: 5, + stainlessPath: 3, + endpoint: 3, + qualified: 3, + summary: 2, + content: 1, + description: 1, + } as Record, + }, +}; + +/** + * Self-contained local search engine backed by MiniSearch. + * Method data is embedded at SDK build time; prose documents + * can be loaded from an optional docs directory at runtime. + */ +export class LocalDocsSearch { + private methodIndex: MiniSearch; + private proseIndex: MiniSearch; + + private constructor() { + this.methodIndex = new MiniSearch(INDEX_OPTIONS); + this.proseIndex = new MiniSearch(INDEX_OPTIONS); + } + + static async create(opts?: { docsDir?: string }): Promise { + const instance = new LocalDocsSearch(); + instance.indexMethods(EMBEDDED_METHODS); + for (const readme of EMBEDDED_READMES) { + instance.indexProse(readme.content, `readme:${readme.language}`); + } + if (opts?.docsDir) { + await instance.loadDocsDirectory(opts.docsDir); + } + return instance; + } + + search(props: { + query: string; + language?: string; + detail?: string; + maxResults?: number; + maxLength?: number; + }): SearchResult { + const { query, language = 'typescript', detail = 'default', maxResults = 5, maxLength = 100_000 } = props; + + const useMarkdown = detail === 'verbose' || detail === 'high'; + + // Search both indices and merge results by score. + // Filter prose hits so language-tagged content (READMEs and docs with + // frontmatter) only matches the requested language. + const methodHits = this.methodIndex + .search(query) + .map((hit) => ({ ...hit, _kind: 'http_method' as const })); + const proseHits = this.proseIndex + .search(query) + .filter((hit) => { + const source = ((hit as Record)['_original'] as ProseChunk | undefined)?.source; + if (!source) return true; + // Check for language-tagged sources: "readme:" or "lang::" + let taggedLang: string | undefined; + if (source.startsWith('readme:')) taggedLang = source.slice('readme:'.length); + else if (source.startsWith('lang:')) taggedLang = source.split(':')[1]; + if (!taggedLang) return true; + return taggedLang === language || (language === 'javascript' && taggedLang === 'typescript'); + }) + .map((hit) => ({ ...hit, _kind: 'prose' as const })); + const merged = [...methodHits, ...proseHits].sort((a, b) => b.score - a.score); + const top = merged.slice(0, maxResults); + + const fullResults: (string | Record)[] = []; + + for (const hit of top) { + const original = (hit as Record)['_original']; + if (hit._kind === 'http_method') { + const m = original as MethodEntry; + if (useMarkdown && m.markdown) { + fullResults.push(m.markdown); + } else { + // Use per-language data when available, falling back to the + // top-level fields (which are TypeScript-specific in the + // legacy codepath). + const langData = m.perLanguage?.[language]; + fullResults.push({ + method: langData?.method ?? m.qualified, + summary: m.summary, + description: m.description, + endpoint: `${m.httpMethod.toUpperCase()} ${m.endpoint}`, + ...(langData?.example ? { example: langData.example } : {}), + ...(m.params ? { params: m.params } : {}), + ...(m.response ? { response: m.response } : {}), + }); + } + } else { + const c = original as ProseChunk; + fullResults.push({ + content: c.content, + ...(c.source ? { source: c.source } : {}), + }); + } + } + + let totalLength = 0; + const results: (string | Record)[] = []; + for (const result of fullResults) { + const len = typeof result === 'string' ? result.length : JSON.stringify(result).length; + totalLength += len; + if (totalLength > maxLength) break; + results.push(result); + } + + if (results.length < fullResults.length) { + results.unshift(`Truncated; showing ${results.length} of ${fullResults.length} results.`); + } + + return { results }; + } + + private indexMethods(methods: MethodEntry[]): void { + const docs: MiniSearchDocument[] = methods.map((m, i) => ({ + id: `method-${i}`, + kind: 'http_method' as const, + name: m.name, + endpoint: m.endpoint, + summary: m.summary, + description: m.description, + qualified: m.qualified, + stainlessPath: m.stainlessPath, + _original: m as unknown as Record, + })); + if (docs.length > 0) { + this.methodIndex.addAll(docs); + } + } + + private async loadDocsDirectory(docsDir: string): Promise { + let entries; + try { + entries = await fs.readdir(docsDir, { withFileTypes: true }); + } catch (err) { + getLogger().warn({ err, docsDir }, 'Could not read docs directory'); + return; + } + + const files = entries + .filter((e) => e.isFile()) + .filter((e) => e.name.endsWith('.md') || e.name.endsWith('.markdown') || e.name.endsWith('.json')); + + for (const file of files) { + try { + const filePath = path.join(docsDir, file.name); + const content = await fs.readFile(filePath, 'utf-8'); + + if (file.name.endsWith('.json')) { + const texts = extractTexts(JSON.parse(content)); + if (texts.length > 0) { + this.indexProse(texts.join('\n\n'), file.name); + } + } else { + // Parse optional YAML frontmatter for language tagging. + // Files with a "language" field in frontmatter will only + // surface in searches for that language. + // + // Example: + // --- + // language: python + // --- + // # Error handling in Python + // ... + const frontmatter = parseFrontmatter(content); + const source = frontmatter.language ? `lang:${frontmatter.language}:${file.name}` : file.name; + this.indexProse(content, source); + } + } catch (err) { + getLogger().warn({ err, file: file.name }, 'Failed to index docs file'); + } + } + } + + private indexProse(markdown: string, source: string): void { + const chunks = chunkMarkdown(markdown); + const baseId = this.proseIndex.documentCount; + + const docs: MiniSearchDocument[] = chunks.map((chunk, i) => ({ + id: `prose-${baseId + i}`, + kind: 'prose' as const, + content: chunk.content, + ...(chunk.sectionContext != null ? { sectionContext: chunk.sectionContext } : {}), + _original: { ...chunk, source } as unknown as Record, + })); + + if (docs.length > 0) { + this.proseIndex.addAll(docs); + } + } +} + +/** Lightweight markdown chunker — splits on headers, chunks by word count. */ +function chunkMarkdown(markdown: string): { content: string; tag: string; sectionContext?: string }[] { + // Strip YAML frontmatter + const stripped = markdown.replace(/^---\n[\s\S]*?\n---\n?/, ''); + const lines = stripped.split('\n'); + + const chunks: { content: string; tag: string; sectionContext?: string }[] = []; + const headers: string[] = []; + let current: string[] = []; + + const flush = () => { + const text = current.join('\n').trim(); + if (!text) return; + const sectionContext = headers.length > 0 ? headers.join(' > ') : undefined; + // Split into ~200-word chunks + const words = text.split(/\s+/); + for (let i = 0; i < words.length; i += 200) { + const slice = words.slice(i, i + 200).join(' '); + if (slice) { + chunks.push({ content: slice, tag: 'p', ...(sectionContext != null ? { sectionContext } : {}) }); + } + } + current = []; + }; + + for (const line of lines) { + const headerMatch = line.match(/^(#{1,6})\s+(.+)/); + if (headerMatch) { + flush(); + const level = headerMatch[1]!.length; + const text = headerMatch[2]!.trim(); + while (headers.length >= level) headers.pop(); + headers.push(text); + } else { + current.push(line); + } + } + flush(); + + return chunks; +} + +/** Recursively extracts string values from a JSON structure. */ +function extractTexts(data: unknown, depth = 0): string[] { + if (depth > 10) return []; + if (typeof data === 'string') return data.trim() ? [data] : []; + if (Array.isArray(data)) return data.flatMap((item) => extractTexts(item, depth + 1)); + if (typeof data === 'object' && data !== null) { + return Object.values(data).flatMap((v) => extractTexts(v, depth + 1)); + } + return []; +} + +/** Parses YAML frontmatter from a markdown string, extracting the language field if present. */ +function parseFrontmatter(markdown: string): { language?: string } { + const match = markdown.match(/^---\n([\s\S]*?)\n---/); + if (!match) return {}; + const body = match[1] ?? ''; + const langMatch = body.match(/^language:\s*(.+)$/m); + return langMatch ? { language: langMatch[1]!.trim() } : {}; +} diff --git a/packages/mcp-server/src/logger.ts b/packages/mcp-server/src/logger.ts new file mode 100644 index 00000000..29dab11c --- /dev/null +++ b/packages/mcp-server/src/logger.ts @@ -0,0 +1,28 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { pino, type Level, type Logger } from 'pino'; +import pretty from 'pino-pretty'; + +let _logger: Logger | undefined; + +export function configureLogger({ level, pretty: usePretty }: { level: Level; pretty: boolean }): void { + _logger = pino( + { + level, + timestamp: pino.stdTimeFunctions.isoTime, + formatters: { + level(label) { + return { level: label }; + }, + }, + }, + usePretty ? pretty({ colorize: true, levelFirst: true, destination: 2 }) : process.stderr, + ); +} + +export function getLogger(): Logger { + if (!_logger) { + throw new Error('Logger has not been configured. Call configureLogger() before using the logger.'); + } + return _logger; +} diff --git a/packages/mcp-server/src/methods.ts b/packages/mcp-server/src/methods.ts new file mode 100644 index 00000000..b31a5644 --- /dev/null +++ b/packages/mcp-server/src/methods.ts @@ -0,0 +1,284 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { McpOptions } from './options'; + +export type SdkMethod = { + clientCallName: string; + fullyQualifiedName: string; + httpMethod?: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'query'; + httpPath?: string; +}; + +export const sdkMethods: SdkMethod[] = [ + { + clientCallName: 'client.applications.retrieve', + fullyQualifiedName: 'applications.retrieve', + httpMethod: 'get', + httpPath: '/v1/applications/{application_id}', + }, + { + clientCallName: 'client.applications.list', + fullyQualifiedName: 'applications.list', + httpMethod: 'get', + httpPath: '/v1/applications', + }, + { + clientCallName: 'client.applications.generateContent', + fullyQualifiedName: 'applications.generateContent', + httpMethod: 'post', + httpPath: '/v1/applications/{application_id}', + }, + { + clientCallName: 'client.applications.jobs.create', + fullyQualifiedName: 'applications.jobs.create', + httpMethod: 'post', + httpPath: '/v1/applications/{application_id}/jobs', + }, + { + clientCallName: 'client.applications.jobs.retrieve', + fullyQualifiedName: 'applications.jobs.retrieve', + httpMethod: 'get', + httpPath: '/v1/applications/jobs/{job_id}', + }, + { + clientCallName: 'client.applications.jobs.list', + fullyQualifiedName: 'applications.jobs.list', + httpMethod: 'get', + httpPath: '/v1/applications/{application_id}/jobs', + }, + { + clientCallName: 'client.applications.jobs.retry', + fullyQualifiedName: 'applications.jobs.retry', + httpMethod: 'post', + httpPath: '/v1/applications/jobs/{job_id}/retry', + }, + { + clientCallName: 'client.applications.graphs.update', + fullyQualifiedName: 'applications.graphs.update', + httpMethod: 'put', + httpPath: '/v1/applications/{application_id}/graphs', + }, + { + clientCallName: 'client.applications.graphs.list', + fullyQualifiedName: 'applications.graphs.list', + httpMethod: 'get', + httpPath: '/v1/applications/{application_id}/graphs', + }, + { + clientCallName: 'client.chat.chat', + fullyQualifiedName: 'chat.chat', + httpMethod: 'post', + httpPath: '/v1/chat', + }, + { + clientCallName: 'client.completions.create', + fullyQualifiedName: 'completions.create', + httpMethod: 'post', + httpPath: '/v1/completions', + }, + { + clientCallName: 'client.models.list', + fullyQualifiedName: 'models.list', + httpMethod: 'get', + httpPath: '/v1/models', + }, + { + clientCallName: 'client.graphs.create', + fullyQualifiedName: 'graphs.create', + httpMethod: 'post', + httpPath: '/v1/graphs', + }, + { + clientCallName: 'client.graphs.retrieve', + fullyQualifiedName: 'graphs.retrieve', + httpMethod: 'get', + httpPath: '/v1/graphs/{graph_id}', + }, + { + clientCallName: 'client.graphs.update', + fullyQualifiedName: 'graphs.update', + httpMethod: 'put', + httpPath: '/v1/graphs/{graph_id}', + }, + { + clientCallName: 'client.graphs.list', + fullyQualifiedName: 'graphs.list', + httpMethod: 'get', + httpPath: '/v1/graphs', + }, + { + clientCallName: 'client.graphs.delete', + fullyQualifiedName: 'graphs.delete', + httpMethod: 'delete', + httpPath: '/v1/graphs/{graph_id}', + }, + { + clientCallName: 'client.graphs.addFileToGraph', + fullyQualifiedName: 'graphs.addFileToGraph', + httpMethod: 'post', + httpPath: '/v1/graphs/{graph_id}/file', + }, + { + clientCallName: 'client.graphs.question', + fullyQualifiedName: 'graphs.question', + httpMethod: 'post', + httpPath: '/v1/graphs/question', + }, + { + clientCallName: 'client.graphs.removeFileFromGraph', + fullyQualifiedName: 'graphs.removeFileFromGraph', + httpMethod: 'delete', + httpPath: '/v1/graphs/{graph_id}/file/{file_id}', + }, + { + clientCallName: 'client.files.retrieve', + fullyQualifiedName: 'files.retrieve', + httpMethod: 'get', + httpPath: '/v1/files/{file_id}', + }, + { + clientCallName: 'client.files.list', + fullyQualifiedName: 'files.list', + httpMethod: 'get', + httpPath: '/v1/files', + }, + { + clientCallName: 'client.files.delete', + fullyQualifiedName: 'files.delete', + httpMethod: 'delete', + httpPath: '/v1/files/{file_id}', + }, + { + clientCallName: 'client.files.download', + fullyQualifiedName: 'files.download', + httpMethod: 'get', + httpPath: '/v1/files/{file_id}/download', + }, + { + clientCallName: 'client.files.retry', + fullyQualifiedName: 'files.retry', + httpMethod: 'post', + httpPath: '/v1/files/retry', + }, + { + clientCallName: 'client.files.upload', + fullyQualifiedName: 'files.upload', + httpMethod: 'post', + httpPath: '/v1/files', + }, + { + clientCallName: 'client.tools.aiDetect', + fullyQualifiedName: 'tools.aiDetect', + httpMethod: 'post', + httpPath: '/v1/tools/ai-detect', + }, + { + clientCallName: 'client.tools.contextAwareSplitting', + fullyQualifiedName: 'tools.contextAwareSplitting', + httpMethod: 'post', + httpPath: '/v1/tools/context-aware-splitting', + }, + { + clientCallName: 'client.tools.parsePdf', + fullyQualifiedName: 'tools.parsePdf', + httpMethod: 'post', + httpPath: '/v1/tools/pdf-parser/{file_id}', + }, + { + clientCallName: 'client.tools.webSearch', + fullyQualifiedName: 'tools.webSearch', + httpMethod: 'post', + httpPath: '/v1/tools/web-search', + }, + { + clientCallName: 'client.tools.comprehend.medical', + fullyQualifiedName: 'tools.comprehend.medical', + httpMethod: 'post', + httpPath: '/v1/tools/comprehend/medical', + }, + { + clientCallName: 'client.translation.translate', + fullyQualifiedName: 'translation.translate', + httpMethod: 'post', + httpPath: '/v1/translation', + }, + { + clientCallName: 'client.vision.analyze', + fullyQualifiedName: 'vision.analyze', + httpMethod: 'post', + httpPath: '/v1/vision', + }, +]; + +function allowedMethodsForCodeTool(options: McpOptions | undefined): SdkMethod[] | undefined { + if (!options) { + return undefined; + } + + let allowedMethods: SdkMethod[]; + + if (options.codeAllowHttpGets || options.codeAllowedMethods) { + // Start with nothing allowed and then add into it from options + let allowedMethodsSet = new Set(); + + if (options.codeAllowHttpGets) { + // Add all methods that map to an HTTP GET + sdkMethods + .filter((method) => method.httpMethod === 'get') + .forEach((method) => allowedMethodsSet.add(method)); + } + + if (options.codeAllowedMethods) { + // Add all methods that match any of the allowed regexps + const allowedRegexps = options.codeAllowedMethods.map((pattern) => { + try { + return new RegExp(pattern); + } catch (e) { + throw new Error( + `Invalid regex pattern for allowed method: "${pattern}": ${e instanceof Error ? e.message : e}`, + ); + } + }); + + sdkMethods + .filter((method) => allowedRegexps.some((regexp) => regexp.test(method.fullyQualifiedName))) + .forEach((method) => allowedMethodsSet.add(method)); + } + + allowedMethods = Array.from(allowedMethodsSet); + } else { + // Start with everything allowed + allowedMethods = [...sdkMethods]; + } + + if (options.codeBlockedMethods) { + // Filter down based on blocked regexps + const blockedRegexps = options.codeBlockedMethods.map((pattern) => { + try { + return new RegExp(pattern); + } catch (e) { + throw new Error( + `Invalid regex pattern for blocked method: "${pattern}": ${e instanceof Error ? e.message : e}`, + ); + } + }); + + allowedMethods = allowedMethods.filter( + (method) => !blockedRegexps.some((regexp) => regexp.test(method.fullyQualifiedName)), + ); + } + + return allowedMethods; +} + +export function blockedMethodsForCodeTool(options: McpOptions | undefined): SdkMethod[] | undefined { + const allowedMethods = allowedMethodsForCodeTool(options); + if (!allowedMethods) { + return undefined; + } + + const allowedSet = new Set(allowedMethods.map((method) => method.fullyQualifiedName)); + + // Return any methods that are not explicitly allowed + return sdkMethods.filter((method) => !allowedSet.has(method.fullyQualifiedName)); +} diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts index b6ff5976..f1518764 100644 --- a/packages/mcp-server/src/options.ts +++ b/packages/mcp-server/src/options.ts @@ -1,140 +1,105 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + import qs from 'qs'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import z from 'zod'; -import { endpoints, Filter } from './tools'; -import { ClientCapabilities, knownClients, ClientType } from './compat'; +import { readEnv } from './util'; export type CLIOptions = McpOptions & { - list: boolean; + debug: boolean; + logFormat: 'json' | 'pretty'; transport: 'stdio' | 'http'; port: number | undefined; socket: string | undefined; }; export type McpOptions = { - client?: ClientType | undefined; - includeDynamicTools?: boolean | undefined; - includeAllTools?: boolean | undefined; - includeCodeTools?: boolean | undefined; + includeCodeTool?: boolean | undefined; includeDocsTools?: boolean | undefined; - filters?: Filter[] | undefined; - capabilities?: Partial | undefined; + stainlessApiKey?: string | undefined; + docsSearchMode?: 'stainless-api' | 'local' | undefined; + docsDir?: string | undefined; + codeAllowHttpGets?: boolean | undefined; + codeAllowedMethods?: string[] | undefined; + codeBlockedMethods?: string[] | undefined; + codeExecutionMode: McpCodeExecutionMode; + customInstructionsPath?: string | undefined; }; -const CAPABILITY_CHOICES = [ - 'top-level-unions', - 'valid-json', - 'refs', - 'unions', - 'formats', - 'tool-name-length', -] as const; - -type Capability = (typeof CAPABILITY_CHOICES)[number]; - -function parseCapabilityValue(cap: string): { name: Capability; value?: number } { - if (cap.startsWith('tool-name-length=')) { - const parts = cap.split('='); - if (parts.length === 2) { - const length = parseInt(parts[1]!, 10); - if (!isNaN(length)) { - return { name: 'tool-name-length', value: length }; - } - throw new Error(`Invalid tool-name-length value: ${parts[1]}. Expected a number.`); - } - throw new Error(`Invalid format for tool-name-length. Expected tool-name-length=N.`); - } - if (!CAPABILITY_CHOICES.includes(cap as Capability)) { - throw new Error(`Unknown capability: ${cap}. Valid capabilities are: ${CAPABILITY_CHOICES.join(', ')}`); - } - return { name: cap as Capability }; -} +export type McpCodeExecutionMode = 'stainless-sandbox' | 'local'; export function parseCLIOptions(): CLIOptions { const opts = yargs(hideBin(process.argv)) - .option('tools', { - type: 'string', - array: true, - choices: ['dynamic', 'all', 'code', 'docs'], - description: 'Use dynamic tools or all tools', - }) - .option('no-tools', { - type: 'string', - array: true, - choices: ['dynamic', 'all', 'code', 'docs'], - description: 'Do not use any dynamic or all tools', + .option('code-allow-http-gets', { + type: 'boolean', + description: + 'Allow all code tool methods that map to HTTP GET operations. If all code-allow-* flags are unset, then everything is allowed.', }) - .option('tool', { + .option('code-allowed-methods', { type: 'string', array: true, - description: 'Include tools matching the specified names', + description: + 'Methods to explicitly allow for code tool. Evaluated as regular expressions against method fully qualified names. If all code-allow-* flags are unset, then everything is allowed.', }) - .option('resource', { + .option('code-blocked-methods', { type: 'string', array: true, - description: 'Include tools matching the specified resources', + description: + 'Methods to explicitly block for code tool. Evaluated as regular expressions against method fully qualified names. If all code-allow-* flags are unset, then everything is allowed.', }) - .option('operation', { + .option('code-execution-mode', { type: 'string', - array: true, - choices: ['read', 'write'], - description: 'Include tools matching the specified operations', + choices: ['stainless-sandbox', 'local'], + default: 'stainless-sandbox', + description: + "Where to run code execution in code tool; 'stainless-sandbox' will execute code in Stainless-hosted sandboxes whereas 'local' will execute code locally on the MCP server machine.", }) - .option('tag', { + .option('custom-instructions-path', { type: 'string', - array: true, - description: 'Include tools with the specified tags', + description: 'Path to custom instructions for the MCP server', }) - .option('no-tool', { + .option('debug', { type: 'boolean', description: 'Enable debug logging' }) + .option('docs-dir', { type: 'string', - array: true, - description: 'Exclude tools matching the specified names', + description: + 'Path to a directory of local documentation files (markdown/JSON) to include in local docs search.', }) - .option('no-resource', { + .option('docs-search-mode', { type: 'string', - array: true, - description: 'Exclude tools matching the specified resources', + choices: ['stainless-api', 'local'], + default: 'stainless-api', + description: + "Where to search documentation; 'stainless-api' uses the Stainless-hosted search API whereas 'local' uses an in-memory search index built from embedded SDK method data and optional local docs files.", }) - .option('no-operation', { + .option('log-format', { type: 'string', - array: true, - description: 'Exclude tools matching the specified operations', + choices: ['json', 'pretty'], + description: 'Format for log output; defaults to json unless tty is detected', }) - .option('no-tag', { + .option('no-tools', { type: 'string', array: true, - description: 'Exclude tools with the specified tags', - }) - .option('list', { - type: 'boolean', - description: 'List all tools and exit', + choices: ['code', 'docs'], + description: 'Tools to explicitly disable', }) - .option('client', { - type: 'string', - choices: Object.keys(knownClients), - description: 'Specify the MCP client being used', + .option('port', { + type: 'number', + default: 3000, + description: 'Port to serve on if using http transport', }) - .option('capability', { + .option('socket', { type: 'string', description: 'Unix socket to serve on if using http transport' }) + .option('stainless-api-key', { type: 'string', - array: true, - description: 'Specify client capabilities', - coerce: (values: string[]) => { - return values.flatMap((v) => v.split(',')); - }, + default: readEnv('STAINLESS_API_KEY'), + description: + 'API key for Stainless. Used to authenticate requests to Stainless-hosted tools endpoints.', }) - .option('no-capability', { + .option('tools', { type: 'string', array: true, - description: 'Unset client capabilities', - choices: CAPABILITY_CHOICES, - coerce: (values: string[]) => { - return values.flatMap((v) => v.split(',')); - }, - }) - .option('describe-capabilities', { - type: 'boolean', - description: 'Print detailed explanation of client capabilities and exit', + choices: ['code', 'docs'], + description: 'Tools to explicitly enable', }) .option('transport', { type: 'string', @@ -142,133 +107,40 @@ export function parseCLIOptions(): CLIOptions { default: 'stdio', description: 'What transport to use; stdio for local servers or http for remote servers', }) - .option('port', { - type: 'number', - description: 'Port to serve on if using http transport', - }) - .option('socket', { - type: 'string', - description: 'Unix socket to serve on if using http transport', - }) + .env('MCP_SERVER') + .version(true) .help(); - for (const [command, desc] of examples()) { - opts.example(command, desc); - } - const argv = opts.parseSync(); - // Handle describe-capabilities flag - if (argv.describeCapabilities) { - console.log(getCapabilitiesExplanation()); - process.exit(0); - } - - const filters: Filter[] = []; - - // Helper function to support comma-separated values - const splitValues = (values: string[] | undefined): string[] => { - if (!values) return []; - return values.flatMap((v) => v.split(',')); - }; - - for (const tag of splitValues(argv.tag)) { - filters.push({ type: 'tag', op: 'include', value: tag }); - } - - for (const tag of splitValues(argv.noTag)) { - filters.push({ type: 'tag', op: 'exclude', value: tag }); - } - - for (const resource of splitValues(argv.resource)) { - filters.push({ type: 'resource', op: 'include', value: resource }); - } - - for (const resource of splitValues(argv.noResource)) { - filters.push({ type: 'resource', op: 'exclude', value: resource }); - } - - for (const tool of splitValues(argv.tool)) { - filters.push({ type: 'tool', op: 'include', value: tool }); - } - - for (const tool of splitValues(argv.noTool)) { - filters.push({ type: 'tool', op: 'exclude', value: tool }); - } - - for (const operation of splitValues(argv.operation)) { - filters.push({ type: 'operation', op: 'include', value: operation }); - } - - for (const operation of splitValues(argv.noOperation)) { - filters.push({ type: 'operation', op: 'exclude', value: operation }); - } - - // Parse client capabilities - const clientCapabilities: Partial = {}; - - // Apply individual capability overrides - if (Array.isArray(argv.capability)) { - for (const cap of argv.capability) { - const parsedCap = parseCapabilityValue(cap); - if (parsedCap.name === 'top-level-unions') { - clientCapabilities.topLevelUnions = true; - } else if (parsedCap.name === 'valid-json') { - clientCapabilities.validJson = true; - } else if (parsedCap.name === 'refs') { - clientCapabilities.refs = true; - } else if (parsedCap.name === 'unions') { - clientCapabilities.unions = true; - } else if (parsedCap.name === 'formats') { - clientCapabilities.formats = true; - } else if (parsedCap.name === 'tool-name-length') { - clientCapabilities.toolNameLength = parsedCap.value; - } - } - } - - // Handle no-capability options to unset capabilities - if (Array.isArray(argv.noCapability)) { - for (const cap of argv.noCapability) { - if (cap === 'top-level-unions') { - clientCapabilities.topLevelUnions = false; - } else if (cap === 'valid-json') { - clientCapabilities.validJson = false; - } else if (cap === 'refs') { - clientCapabilities.refs = false; - } else if (cap === 'unions') { - clientCapabilities.unions = false; - } else if (cap === 'formats') { - clientCapabilities.formats = false; - } else if (cap === 'tool-name-length') { - clientCapabilities.toolNameLength = undefined; - } - } - } - - const shouldIncludeToolType = (toolType: 'dynamic' | 'all' | 'code' | 'docs') => + const shouldIncludeToolType = (toolType: 'code' | 'docs') => argv.noTools?.includes(toolType) ? false : argv.tools?.includes(toolType) ? true : undefined; - const includeDynamicTools = shouldIncludeToolType('dynamic'); - const includeAllTools = shouldIncludeToolType('all'); - const includeCodeTools = shouldIncludeToolType('code'); + const includeCodeTool = shouldIncludeToolType('code'); const includeDocsTools = shouldIncludeToolType('docs'); const transport = argv.transport as 'stdio' | 'http'; + const logFormat = + argv.logFormat ? (argv.logFormat as 'json' | 'pretty') + : process.stderr.isTTY ? 'pretty' + : 'json'; - const client = argv.client as ClientType; return { - client: client && client !== 'infer' && knownClients[client] ? client : undefined, - includeDynamicTools, - includeAllTools, - includeCodeTools, - includeDocsTools, - filters, - capabilities: clientCapabilities, - list: argv.list || false, + ...(includeCodeTool !== undefined && { includeCodeTool }), + ...(includeDocsTools !== undefined && { includeDocsTools }), + debug: !!argv.debug, + stainlessApiKey: argv.stainlessApiKey, + docsSearchMode: argv.docsSearchMode as 'stainless-api' | 'local' | undefined, + docsDir: argv.docsDir, + codeAllowHttpGets: argv.codeAllowHttpGets, + codeAllowedMethods: argv.codeAllowedMethods, + codeBlockedMethods: argv.codeBlockedMethods, + codeExecutionMode: argv.codeExecutionMode as McpCodeExecutionMode, + customInstructionsPath: argv.customInstructionsPath, transport, + logFormat, port: argv.port, socket: argv.socket, }; @@ -284,190 +156,30 @@ const coerceArray = (zodType: T) => ); const QueryOptions = z.object({ - tools: coerceArray(z.enum(['dynamic', 'all', 'code', 'docs'])).describe('Specify which MCP tools to use'), - no_tools: coerceArray(z.enum(['dynamic', 'all', 'code', 'docs'])).describe( - 'Specify which MCP tools to not use.', - ), + tools: coerceArray(z.enum(['code', 'docs'])).describe('Specify which MCP tools to use'), + no_tools: coerceArray(z.enum(['code', 'docs'])).describe('Specify which MCP tools to not use.'), tool: coerceArray(z.string()).describe('Include tools matching the specified names'), - resource: coerceArray(z.string()).describe('Include tools matching the specified resources'), - operation: coerceArray(z.enum(['read', 'write'])).describe( - 'Include tools matching the specified operations', - ), - tag: coerceArray(z.string()).describe('Include tools with the specified tags'), - no_tool: coerceArray(z.string()).describe('Exclude tools matching the specified names'), - no_resource: coerceArray(z.string()).describe('Exclude tools matching the specified resources'), - no_operation: coerceArray(z.enum(['read', 'write'])).describe( - 'Exclude tools matching the specified operations', - ), - no_tag: coerceArray(z.string()).describe('Exclude tools with the specified tags'), - client: ClientType.optional().describe('Specify the MCP client being used'), - capability: coerceArray(z.string()).describe('Specify client capabilities'), - no_capability: coerceArray(z.enum(CAPABILITY_CHOICES)).describe('Unset client capabilities'), }); export function parseQueryOptions(defaultOptions: McpOptions, query: unknown): McpOptions { const queryObject = typeof query === 'string' ? qs.parse(query) : query; const queryOptions = QueryOptions.parse(queryObject); - const filters: Filter[] = [...(defaultOptions.filters ?? [])]; - - for (const resource of queryOptions.resource || []) { - filters.push({ type: 'resource', op: 'include', value: resource }); - } - for (const operation of queryOptions.operation || []) { - filters.push({ type: 'operation', op: 'include', value: operation }); - } - for (const tag of queryOptions.tag || []) { - filters.push({ type: 'tag', op: 'include', value: tag }); - } - for (const tool of queryOptions.tool || []) { - filters.push({ type: 'tool', op: 'include', value: tool }); - } - for (const resource of queryOptions.no_resource || []) { - filters.push({ type: 'resource', op: 'exclude', value: resource }); - } - for (const operation of queryOptions.no_operation || []) { - filters.push({ type: 'operation', op: 'exclude', value: operation }); - } - for (const tag of queryOptions.no_tag || []) { - filters.push({ type: 'tag', op: 'exclude', value: tag }); - } - for (const tool of queryOptions.no_tool || []) { - filters.push({ type: 'tool', op: 'exclude', value: tool }); - } - - // Parse client capabilities - const clientCapabilities: Partial = { ...defaultOptions.capabilities }; - - for (const cap of queryOptions.capability || []) { - const parsed = parseCapabilityValue(cap); - if (parsed.name === 'top-level-unions') { - clientCapabilities.topLevelUnions = true; - } else if (parsed.name === 'valid-json') { - clientCapabilities.validJson = true; - } else if (parsed.name === 'refs') { - clientCapabilities.refs = true; - } else if (parsed.name === 'unions') { - clientCapabilities.unions = true; - } else if (parsed.name === 'formats') { - clientCapabilities.formats = true; - } else if (parsed.name === 'tool-name-length') { - clientCapabilities.toolNameLength = parsed.value; - } - } - - for (const cap of queryOptions.no_capability || []) { - if (cap === 'top-level-unions') { - clientCapabilities.topLevelUnions = false; - } else if (cap === 'valid-json') { - clientCapabilities.validJson = false; - } else if (cap === 'refs') { - clientCapabilities.refs = false; - } else if (cap === 'unions') { - clientCapabilities.unions = false; - } else if (cap === 'formats') { - clientCapabilities.formats = false; - } else if (cap === 'tool-name-length') { - clientCapabilities.toolNameLength = undefined; - } - } - - let dynamicTools: boolean | undefined = - queryOptions.no_tools && queryOptions.no_tools?.includes('dynamic') ? false - : queryOptions.tools?.includes('dynamic') ? true - : defaultOptions.includeDynamicTools; - - let allTools: boolean | undefined = - queryOptions.no_tools && queryOptions.no_tools?.includes('all') ? false - : queryOptions.tools?.includes('all') ? true - : defaultOptions.includeAllTools; + let codeTool: boolean | undefined = + queryOptions.no_tools && queryOptions.no_tools?.includes('code') ? false + : queryOptions.tools?.includes('code') ? true + : defaultOptions.includeCodeTool; let docsTools: boolean | undefined = queryOptions.no_tools && queryOptions.no_tools?.includes('docs') ? false : queryOptions.tools?.includes('docs') ? true : defaultOptions.includeDocsTools; - let codeTools: boolean | undefined = - queryOptions.no_tools && queryOptions.no_tools?.includes('code') ? false - : queryOptions.tools?.includes('code') && defaultOptions.includeCodeTools ? true - : defaultOptions.includeCodeTools; - return { - client: queryOptions.client ?? defaultOptions.client, - includeDynamicTools: dynamicTools, - includeAllTools: allTools, - includeCodeTools: codeTools, - includeDocsTools: docsTools, - filters, - capabilities: clientCapabilities, + ...(codeTool !== undefined && { includeCodeTool: codeTool }), + ...(docsTools !== undefined && { includeDocsTools: docsTools }), + codeExecutionMode: defaultOptions.codeExecutionMode, + docsSearchMode: defaultOptions.docsSearchMode, + docsDir: defaultOptions.docsDir, }; } - -function getCapabilitiesExplanation(): string { - return ` -Client Capabilities Explanation: - -Different Language Models (LLMs) and the MCP clients that use them have varying limitations in how they handle tool schemas. Capability flags allow you to inform the MCP server about these limitations. - -When a capability flag is set to false, the MCP server will automatically adjust the tool schemas to work around that limitation, ensuring broader compatibility. - -Available Capabilities: - -# top-level-unions -Some clients/LLMs do not support JSON schemas with a union type (anyOf) at the root level. If a client lacks this capability, the MCP server splits tools with top-level unions into multiple separate tools, one for each variant in the union. - -# refs -Some clients/LLMs do not support $ref pointers for schema reuse. If a client lacks this capability, the MCP server automatically inlines all references ($defs) directly into the schema. Properties that would cause circular references are removed during this process. - -# valid-json -Some clients/LLMs may incorrectly send arguments as a JSON-encoded string instead of a proper JSON object. If a client *has* this capability, the MCP server will attempt to parse string values as JSON if the initial validation against the schema fails. - -# unions -Some clients/LLMs do not support union types (anyOf) in JSON schemas. If a client lacks this capability, the MCP server removes all anyOf fields and uses only the first variant as the schema. - -# formats -Some clients/LLMs do not support the 'format' keyword in JSON Schema specifications. If a client lacks this capability, the MCP server removes all format fields and appends the format information to the field's description in parentheses. - -# tool-name-length=N -Some clients/LLMs impose a maximum length on tool names. If this capability is set, the MCP server will automatically truncate tool names exceeding the specified length (N), ensuring uniqueness by appending numbers if necessary. - -Client Presets (--client): -Presets like '--client=openai-agents' or '--client=cursor' automatically configure these capabilities based on current known limitations of those clients, simplifying setup. - -Current presets: -${JSON.stringify(knownClients, null, 2)} - `; -} - -function examples(): [string, string][] { - const firstEndpoint = endpoints[0]!; - const secondEndpoint = - endpoints.find((e) => e.metadata.resource !== firstEndpoint.metadata.resource) || endpoints[1]; - const tag = endpoints.find((e) => e.metadata.tags.length > 0)?.metadata.tags[0]; - const otherEndpoint = secondEndpoint || firstEndpoint; - - return [ - [ - `--tool="${firstEndpoint.tool.name}" ${secondEndpoint ? `--tool="${secondEndpoint.tool.name}"` : ''}`, - 'Include tools by name', - ], - [ - `--resource="${firstEndpoint.metadata.resource}" --operation="read"`, - 'Filter by resource and operation', - ], - [ - `--resource="${otherEndpoint.metadata.resource}*" --no-tool="${otherEndpoint.tool.name}"`, - 'Use resource wildcards and exclusions', - ], - [`--client="cursor"`, 'Adjust schemas to be more compatible with Cursor'], - [ - `--capability="top-level-unions" --capability="tool-name-length=40"`, - 'Specify individual client capabilities', - ], - [ - `--client="cursor" --no-capability="tool-name-length"`, - 'Use cursor client preset but remove tool name length limit', - ], - ...(tag ? [[`--tag="${tag}"`, 'Filter based on tags'] as [string, string]] : []), - ]; -} diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 4ec8edfa..41b3cbe0 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -2,75 +2,54 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { Endpoint, endpoints, HandlerFunction, query } from './tools'; import { CallToolRequestSchema, ListToolsRequestSchema, SetLevelRequestSchema, - Implementation, - Tool, } from '@modelcontextprotocol/sdk/types.js'; import { ClientOptions } from 'writer-sdk'; import Writer from 'writer-sdk'; -import { - applyCompatibilityTransformations, - ClientCapabilities, - defaultClientCapabilities, - knownClients, - parseEmbeddedJSON, -} from './compat'; -import { dynamicTools } from './dynamic-tools'; import { codeTool } from './code-tool'; import docsSearchTool from './docs-search-tool'; +import { setLocalSearch } from './docs-search-tool'; +import { LocalDocsSearch } from './local-docs-search'; +import { getInstructions } from './instructions'; import { McpOptions } from './options'; - -export { McpOptions } from './options'; -export { ClientType } from './compat'; -export { Filter } from './tools'; -export { ClientOptions } from 'writer-sdk'; -export { endpoints } from './tools'; - -export const newMcpServer = () => +import { blockedMethodsForCodeTool } from './methods'; +import { HandlerFunction, McpRequestContext, ToolCallResult, McpTool } from './types'; + +export const newMcpServer = async ({ + stainlessApiKey, + customInstructionsPath, +}: { + stainlessApiKey?: string | undefined; + customInstructionsPath?: string | undefined; +}) => new McpServer( { name: 'writer_sdk_api', - version: '2.3.3-rc.1', + version: '2.4.0-rc.1', + }, + { + instructions: await getInstructions({ stainlessApiKey, customInstructionsPath }), + capabilities: { tools: {}, logging: {} }, }, - { capabilities: { tools: {}, logging: {} } }, ); -// Create server instance -export const server = newMcpServer(); - /** * Initializes the provided MCP Server with the given tools and handlers. * If not provided, the default client, tools and handlers will be used. */ -export function initMcpServer(params: { +export async function initMcpServer(params: { server: Server | McpServer; clientOptions?: ClientOptions; mcpOptions?: McpOptions; + stainlessApiKey?: string | undefined; + upstreamClientEnvs?: Record | undefined; + mcpSessionId?: string | undefined; + mcpClientInfo?: { name: string; version: string } | undefined; }) { const server = params.server instanceof McpServer ? params.server.server : params.server; - const mcpOptions = params.mcpOptions ?? {}; - - let providedEndpoints: Endpoint[] | null = null; - let endpointMap: Record | null = null; - - const initTools = async (implementation?: Implementation) => { - if (implementation && (!mcpOptions.client || mcpOptions.client === 'infer')) { - mcpOptions.client = - implementation.name.toLowerCase().includes('claude') ? 'claude' - : implementation.name.toLowerCase().includes('cursor') ? 'cursor' - : undefined; - mcpOptions.capabilities = { - ...(mcpOptions.client && knownClients[mcpOptions.client]), - ...mcpOptions.capabilities, - }; - } - providedEndpoints ??= await selectTools(endpoints, mcpOptions); - endpointMap ??= Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint])); - }; const logAtLevel = (level: 'debug' | 'info' | 'warning' | 'error') => @@ -87,57 +66,108 @@ export function initMcpServer(params: { error: logAtLevel('error'), }; - let client = new Writer({ - logger, - ...params.clientOptions, - defaultHeaders: { - ...params.clientOptions?.defaultHeaders, - 'X-Stainless-MCP': 'true', - }, - }); + if (params.mcpOptions?.docsSearchMode === 'local') { + const docsDir = params.mcpOptions?.docsDir; + const localSearch = await LocalDocsSearch.create(docsDir ? { docsDir } : undefined); + setLocalSearch(localSearch); + } - server.setRequestHandler(ListToolsRequestSchema, async () => { - if (providedEndpoints === null) { - await initTools(server.getClientVersion()); + let _client: Writer | undefined; + let _clientError: Error | undefined; + let _logLevel: 'debug' | 'info' | 'warn' | 'error' | 'off' | undefined; + + const getClient = (): Writer => { + if (_clientError) throw _clientError; + if (!_client) { + try { + _client = new Writer({ + logger, + ...params.clientOptions, + defaultHeaders: { + ...params.clientOptions?.defaultHeaders, + 'X-Stainless-MCP': 'true', + }, + }); + if (_logLevel) { + _client = _client.withOptions({ logLevel: _logLevel }); + } + } catch (e) { + _clientError = e instanceof Error ? e : new Error(String(e)); + throw _clientError; + } } + return _client; + }; + + const providedTools = selectTools(params.mcpOptions); + const toolMap = Object.fromEntries(providedTools.map((mcpTool) => [mcpTool.tool.name, mcpTool])); + + server.setRequestHandler(ListToolsRequestSchema, async () => { return { - tools: providedEndpoints!.map((endpoint) => endpoint.tool), + tools: providedTools.map((mcpTool) => mcpTool.tool), }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { - if (endpointMap === null) { - await initTools(server.getClientVersion()); - } const { name, arguments: args } = request.params; - const endpoint = endpointMap![name]; - if (!endpoint) { + const mcpTool = toolMap[name]; + if (!mcpTool) { throw new Error(`Unknown tool: ${name}`); } - return executeHandler(endpoint.tool, endpoint.handler, client, args, mcpOptions.capabilities); + let client: Writer; + try { + client = getClient(); + } catch (error) { + return { + content: [ + { + type: 'text' as const, + text: `Failed to initialize client: ${error instanceof Error ? error.message : String(error)}`, + }, + ], + isError: true, + }; + } + + return executeHandler({ + handler: mcpTool.handler, + reqContext: { + client, + stainlessApiKey: params.stainlessApiKey ?? params.mcpOptions?.stainlessApiKey, + upstreamClientEnvs: params.upstreamClientEnvs, + mcpSessionId: params.mcpSessionId, + mcpClientInfo: params.mcpClientInfo, + }, + args, + }); }); server.setRequestHandler(SetLevelRequestSchema, async (request) => { const { level } = request.params; + let logLevel: 'debug' | 'info' | 'warn' | 'error' | 'off'; switch (level) { case 'debug': - client = client.withOptions({ logLevel: 'debug' }); + logLevel = 'debug'; break; case 'info': - client = client.withOptions({ logLevel: 'info' }); + logLevel = 'info'; break; case 'notice': case 'warning': - client = client.withOptions({ logLevel: 'warn' }); + logLevel = 'warn'; break; case 'error': - client = client.withOptions({ logLevel: 'error' }); + logLevel = 'error'; break; default: - client = client.withOptions({ logLevel: 'off' }); + logLevel = 'off'; break; } + _logLevel = logLevel; + if (_client) { + _client = _client.withOptions({ logLevel }); + } return {}; }); } @@ -145,63 +175,34 @@ export function initMcpServer(params: { /** * Selects the tools to include in the MCP Server based on the provided options. */ -export async function selectTools(endpoints: Endpoint[], options?: McpOptions): Promise { - const filteredEndpoints = query(options?.filters ?? [], endpoints); - - let includedTools = filteredEndpoints.slice(); - - if (includedTools.length > 0) { - if (options?.includeDynamicTools) { - includedTools = dynamicTools(includedTools); - } - } else { - if (options?.includeAllTools) { - includedTools = endpoints.slice(); - } else if (options?.includeDynamicTools) { - includedTools = dynamicTools(endpoints); - } else if (options?.includeCodeTools) { - includedTools = [await codeTool()]; - } else { - includedTools = endpoints.slice(); - } +export function selectTools(options?: McpOptions): McpTool[] { + const includedTools = []; + + if (options?.includeCodeTool ?? true) { + includedTools.push( + codeTool({ + blockedMethods: blockedMethodsForCodeTool(options), + codeExecutionMode: options?.codeExecutionMode ?? 'stainless-sandbox', + }), + ); } if (options?.includeDocsTools ?? true) { includedTools.push(docsSearchTool); } - const capabilities = { ...defaultClientCapabilities, ...options?.capabilities }; - return applyCompatibilityTransformations(includedTools, capabilities); + return includedTools; } /** * Runs the provided handler with the given client and arguments. */ -export async function executeHandler( - tool: Tool, - handler: HandlerFunction, - client: Writer, - args: Record | undefined, - compatibilityOptions?: Partial, -) { - const options = { ...defaultClientCapabilities, ...compatibilityOptions }; - if (!options.validJson && args) { - args = parseEmbeddedJSON(args, tool.inputSchema); - } - return await handler(client, args || {}); +export async function executeHandler({ + handler, + reqContext, + args, +}: { + handler: HandlerFunction; + reqContext: McpRequestContext; + args: Record | undefined; +}): Promise { + return await handler({ reqContext, args: args || {} }); } - -export const readEnv = (env: string): string | undefined => { - if (typeof (globalThis as any).process !== 'undefined') { - return (globalThis as any).process.env?.[env]?.trim(); - } else if (typeof (globalThis as any).Deno !== 'undefined') { - return (globalThis as any).Deno.env?.get?.(env)?.trim(); - } - return; -}; - -export const readEnvOrError = (env: string): string => { - let envValue = readEnv(env); - if (envValue === undefined) { - throw new Error(`Environment variable ${env} is not set`); - } - return envValue; -}; diff --git a/packages/mcp-server/src/stdio.ts b/packages/mcp-server/src/stdio.ts index d902a5bb..b04a5441 100644 --- a/packages/mcp-server/src/stdio.ts +++ b/packages/mcp-server/src/stdio.ts @@ -1,13 +1,17 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { initMcpServer, newMcpServer } from './server'; import { McpOptions } from './options'; +import { initMcpServer, newMcpServer } from './server'; +import { getLogger } from './logger'; -export const launchStdioServer = async (options: McpOptions) => { - const server = newMcpServer(); +export const launchStdioServer = async (mcpOptions: McpOptions) => { + const server = await newMcpServer({ + stainlessApiKey: mcpOptions.stainlessApiKey, + customInstructionsPath: mcpOptions.customInstructionsPath, + }); - initMcpServer({ server, mcpOptions: options }); + await initMcpServer({ server, mcpOptions, stainlessApiKey: mcpOptions.stainlessApiKey }); const transport = new StdioServerTransport(); await server.connect(transport); - console.error('MCP Server running on stdio'); + getLogger().info('MCP Server running on stdio'); }; diff --git a/packages/mcp-server/src/tools.ts b/packages/mcp-server/src/tools.ts deleted file mode 100644 index 7e516de7..00000000 --- a/packages/mcp-server/src/tools.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tools/index'; diff --git a/packages/mcp-server/src/tools/applications/generate-content-applications.ts b/packages/mcp-server/src/tools/applications/generate-content-applications.ts deleted file mode 100644 index 6fd4a72b..00000000 --- a/packages/mcp-server/src/tools/applications/generate-content-applications.ts +++ /dev/null @@ -1,128 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'applications', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/applications/{application_id}', - operationId: 'generateContent', -}; - -export const tool: Tool = { - name: 'generate_content_applications', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGenerate content using a pre-configured no-code agent. No-code agents are custom AI workflows you've built in AI Studio with specific prompts, models, and settings. Provide the application ID and required inputs to get tailored content. Useful for consistent, repeatable AI tasks like content generation, data extraction, or custom workflows.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/application_generate_content_response',\n $defs: {\n application_generate_content_response: {\n type: 'object',\n title: 'generate_application_response',\n properties: {\n suggestion: {\n type: 'string',\n description: 'The response from the model specified in the application.'\n },\n title: {\n type: 'string',\n description: 'The name of the output field.'\n }\n },\n required: [ 'suggestion'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - anyOf: [ - { - type: 'object', - properties: { - application_id: { - type: 'string', - }, - inputs: { - type: 'array', - items: { - type: 'object', - title: 'generate_application_input', - properties: { - id: { - type: 'string', - description: - 'The unique identifier for the input field from the application. All input types from the No-code application are supported (i.e. Text input, Dropdown, File upload, Image input). The identifier should be the name of the input type.', - }, - value: { - type: 'array', - description: - 'The value for the input field. \n\nIf the input type is "File upload", you must pass the `file_id` of an uploaded file. You cannot pass a file object directly. See the [file upload endpoint](https://dev.writer.com/api-reference/file-api/upload-files) for instructions on uploading files or the [list files endpoint](https://dev.writer.com/api-reference/file-api/get-all-files) for how to see a list of uploaded files and their IDs.', - items: { - type: 'string', - }, - }, - }, - required: ['id', 'value'], - }, - }, - stream: { - type: 'string', - description: - 'Indicates whether the response should be streamed. Currently only supported for research assistant applications.', - enum: [false], - }, - }, - required: ['application_id', 'inputs'], - }, - { - type: 'object', - properties: { - application_id: { - type: 'string', - }, - inputs: { - type: 'array', - items: { - type: 'object', - title: 'generate_application_input', - properties: { - id: { - type: 'string', - description: - 'The unique identifier for the input field from the application. All input types from the No-code application are supported (i.e. Text input, Dropdown, File upload, Image input). The identifier should be the name of the input type.', - }, - value: { - type: 'array', - description: - 'The value for the input field. \n\nIf the input type is "File upload", you must pass the `file_id` of an uploaded file. You cannot pass a file object directly. See the [file upload endpoint](https://dev.writer.com/api-reference/file-api/upload-files) for instructions on uploading files or the [list files endpoint](https://dev.writer.com/api-reference/file-api/get-all-files) for how to see a list of uploaded files and their IDs.', - items: { - type: 'string', - }, - }, - }, - required: ['id', 'value'], - }, - }, - stream: { - type: 'string', - description: - 'Indicates whether the response should be streamed. Currently only supported for research assistant applications.', - enum: [true], - }, - }, - required: ['application_id', 'inputs', 'stream'], - }, - ], - properties: { - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { application_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.applications.generateContent(application_id, body)), - ); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/graphs/list-applications-graphs.ts b/packages/mcp-server/src/tools/applications/graphs/list-applications-graphs.ts deleted file mode 100644 index d0543c5f..00000000 --- a/packages/mcp-server/src/tools/applications/graphs/list-applications-graphs.ts +++ /dev/null @@ -1,55 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'applications.graphs', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/applications/{application_id}/graphs', -}; - -export const tool: Tool = { - name: 'list_applications_graphs', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRetrieve Knowledge Graphs associated with a no-code agent that has chat capabilities.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/application_graphs_response',\n $defs: {\n application_graphs_response: {\n type: 'object',\n title: 'application_graphs_response',\n properties: {\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graphs associated with the application.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'graph_ids'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - application_id: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['application_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { application_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.applications.graphs.list(application_id)), - ); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/graphs/update-applications-graphs.ts b/packages/mcp-server/src/tools/applications/graphs/update-applications-graphs.ts deleted file mode 100644 index 72625bcd..00000000 --- a/packages/mcp-server/src/tools/applications/graphs/update-applications-graphs.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'applications.graphs', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/v1/applications/{application_id}/graphs', -}; - -export const tool: Tool = { - name: 'update_applications_graphs', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nUpdates the list of Knowledge Graphs associated with a no-code chat agent.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/application_graphs_response',\n $defs: {\n application_graphs_response: {\n type: 'object',\n title: 'application_graphs_response',\n properties: {\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graphs associated with the application.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'graph_ids'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - application_id: { - type: 'string', - }, - graph_ids: { - type: 'array', - description: - 'A list of Knowledge Graph IDs to associate with the application. Note that this will replace the existing list of Knowledge Graphs associated with the application, not add to it.', - items: { - type: 'string', - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['application_id', 'graph_ids'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { application_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.applications.graphs.update(application_id, body)), - ); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/jobs/create-applications-jobs.ts b/packages/mcp-server/src/tools/applications/jobs/create-applications-jobs.ts deleted file mode 100644 index cd5a192b..00000000 --- a/packages/mcp-server/src/tools/applications/jobs/create-applications-jobs.ts +++ /dev/null @@ -1,77 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'applications.jobs', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/applications/{application_id}/jobs', -}; - -export const tool: Tool = { - name: 'create_applications_jobs', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGenerate content asynchronously from an existing no-code agent (formerly called no-code applications) with inputs.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/job_create_response',\n $defs: {\n job_create_response: {\n type: 'object',\n title: 'generate_application_async_response',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier for the async job created.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the job was created.',\n format: 'date-time'\n },\n status: {\n type: 'string',\n title: 'api_job_status',\n description: 'The status of the job.',\n enum: [ 'in_progress',\n 'failed',\n 'completed'\n ]\n }\n },\n required: [ 'id',\n 'created_at',\n 'status'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - application_id: { - type: 'string', - }, - inputs: { - type: 'array', - description: 'A list of input objects to generate content for.', - items: { - type: 'object', - title: 'generate_application_input', - properties: { - id: { - type: 'string', - description: - 'The unique identifier for the input field from the application. All input types from the No-code application are supported (i.e. Text input, Dropdown, File upload, Image input). The identifier should be the name of the input type.', - }, - value: { - type: 'array', - description: - 'The value for the input field. \n\nIf the input type is "File upload", you must pass the `file_id` of an uploaded file. You cannot pass a file object directly. See the [file upload endpoint](https://dev.writer.com/api-reference/file-api/upload-files) for instructions on uploading files or the [list files endpoint](https://dev.writer.com/api-reference/file-api/get-all-files) for how to see a list of uploaded files and their IDs.', - items: { - type: 'string', - }, - }, - }, - required: ['id', 'value'], - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['application_id', 'inputs'], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { application_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.applications.jobs.create(application_id, body)), - ); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/jobs/list-applications-jobs.ts b/packages/mcp-server/src/tools/applications/jobs/list-applications-jobs.ts deleted file mode 100644 index a04bcc9e..00000000 --- a/packages/mcp-server/src/tools/applications/jobs/list-applications-jobs.ts +++ /dev/null @@ -1,68 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'applications.jobs', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/applications/{application_id}/jobs', -}; - -export const tool: Tool = { - name: 'list_applications_jobs', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRetrieve all jobs created via the async API, linked to the provided application ID (or alias).\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/application_jobs_list_response',\n $defs: {\n application_jobs_list_response: {\n type: 'object',\n title: 'get_async_application_jobs_response',\n properties: {\n result: {\n type: 'array',\n items: {\n $ref: '#/$defs/application_generate_async_response'\n }\n },\n pagination: {\n type: 'object',\n properties: {\n limit: {\n type: 'integer',\n description: 'The pagination limit for retrieving the jobs.'\n },\n offset: {\n type: 'integer',\n description: 'The pagination offset for retrieving the jobs.'\n }\n }\n },\n totalCount: {\n type: 'integer',\n description: 'The total number of jobs associated with the application.'\n }\n },\n required: [ 'result'\n ]\n },\n application_generate_async_response: {\n type: 'object',\n title: 'get_async_application_job_response',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier for the job.'\n },\n application_id: {\n type: 'string',\n description: 'The ID of the application associated with this job.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the job was created.',\n format: 'date-time'\n },\n status: {\n type: 'string',\n title: 'api_job_status',\n description: 'The status of the job.',\n enum: [ 'in_progress',\n 'failed',\n 'completed'\n ]\n },\n completed_at: {\n type: 'string',\n description: 'The timestamp when the job was completed.',\n format: 'date-time'\n },\n data: {\n $ref: '#/$defs/application_generate_content_response'\n },\n error: {\n type: 'string',\n description: 'The error message if the job failed.'\n },\n updated_at: {\n type: 'string',\n description: 'The timestamp when the job was last updated.',\n format: 'date-time'\n }\n },\n required: [ 'id',\n 'application_id',\n 'created_at',\n 'status'\n ]\n },\n application_generate_content_response: {\n type: 'object',\n title: 'generate_application_response',\n properties: {\n suggestion: {\n type: 'string',\n description: 'The response from the model specified in the application.'\n },\n title: {\n type: 'string',\n description: 'The name of the output field.'\n }\n },\n required: [ 'suggestion'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - application_id: { - type: 'string', - }, - limit: { - type: 'integer', - description: 'The pagination limit for retrieving the jobs.', - }, - offset: { - type: 'integer', - description: 'The pagination offset for retrieving the jobs.', - }, - status: { - type: 'string', - title: 'api_job_status', - description: 'The status of the job.', - enum: ['in_progress', 'failed', 'completed'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['application_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { application_id, jq_filter, ...body } = args as any; - const response = await client.applications.jobs.list(application_id, body).asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/jobs/retrieve-applications-jobs.ts b/packages/mcp-server/src/tools/applications/jobs/retrieve-applications-jobs.ts deleted file mode 100644 index bc431b5d..00000000 --- a/packages/mcp-server/src/tools/applications/jobs/retrieve-applications-jobs.ts +++ /dev/null @@ -1,53 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'applications.jobs', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/applications/jobs/{job_id}', -}; - -export const tool: Tool = { - name: 'retrieve_applications_jobs', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRetrieves a single job created via the Async API.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/application_generate_async_response',\n $defs: {\n application_generate_async_response: {\n type: 'object',\n title: 'get_async_application_job_response',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier for the job.'\n },\n application_id: {\n type: 'string',\n description: 'The ID of the application associated with this job.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the job was created.',\n format: 'date-time'\n },\n status: {\n type: 'string',\n title: 'api_job_status',\n description: 'The status of the job.',\n enum: [ 'in_progress',\n 'failed',\n 'completed'\n ]\n },\n completed_at: {\n type: 'string',\n description: 'The timestamp when the job was completed.',\n format: 'date-time'\n },\n data: {\n $ref: '#/$defs/application_generate_content_response'\n },\n error: {\n type: 'string',\n description: 'The error message if the job failed.'\n },\n updated_at: {\n type: 'string',\n description: 'The timestamp when the job was last updated.',\n format: 'date-time'\n }\n },\n required: [ 'id',\n 'application_id',\n 'created_at',\n 'status'\n ]\n },\n application_generate_content_response: {\n type: 'object',\n title: 'generate_application_response',\n properties: {\n suggestion: {\n type: 'string',\n description: 'The response from the model specified in the application.'\n },\n title: {\n type: 'string',\n description: 'The name of the output field.'\n }\n },\n required: [ 'suggestion'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - job_id: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['job_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { job_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.applications.jobs.retrieve(job_id))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/jobs/retry-applications-jobs.ts b/packages/mcp-server/src/tools/applications/jobs/retry-applications-jobs.ts deleted file mode 100644 index f9b6f099..00000000 --- a/packages/mcp-server/src/tools/applications/jobs/retry-applications-jobs.ts +++ /dev/null @@ -1,51 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'applications.jobs', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/applications/jobs/{job_id}/retry', -}; - -export const tool: Tool = { - name: 'retry_applications_jobs', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRe-triggers the async execution of a single job previously created via the Async api and terminated in error.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/job_retry_response',\n $defs: {\n job_retry_response: {\n type: 'object',\n title: 'generate_application_async_response',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier for the async job created.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the job was created.',\n format: 'date-time'\n },\n status: {\n type: 'string',\n title: 'api_job_status',\n description: 'The status of the job.',\n enum: [ 'in_progress',\n 'failed',\n 'completed'\n ]\n }\n },\n required: [ 'id',\n 'created_at',\n 'status'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - job_id: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['job_id'], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { job_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.applications.jobs.retry(job_id))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/list-applications.ts b/packages/mcp-server/src/tools/applications/list-applications.ts deleted file mode 100644 index 9a2f628a..00000000 --- a/packages/mcp-server/src/tools/applications/list-applications.ts +++ /dev/null @@ -1,74 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'applications', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/applications', -}; - -export const tool: Tool = { - name: 'list_applications', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet all available no-code agents (applications) in your account. No-code agents are pre-configured AI workflows built in Writer's AI Studio. Use this to discover which agents are available before generating content from them.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'get_applications_response',\n description: 'Response object containing a paginated list of applications.',\n properties: {\n data: {\n type: 'array',\n description: 'List of application objects with their configurations.',\n items: {\n $ref: '#/$defs/application_list_response'\n }\n },\n has_more: {\n type: 'boolean',\n description: 'Indicates if there are more results available in subsequent pages.'\n },\n first_id: {\n type: 'string',\n description: 'UUID of the first application in the current page.'\n },\n last_id: {\n type: 'string',\n description: 'UUID of the last application in the current page.'\n }\n },\n required: [ 'data',\n 'has_more'\n ],\n $defs: {\n application_list_response: {\n type: 'object',\n title: 'application_with_inputs',\n description: 'Detailed application object including its input configuration.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the application.'\n },\n created_at: {\n type: 'string',\n description: 'Timestamp when the application was created.',\n format: 'date-time'\n },\n inputs: {\n type: 'array',\n description: 'List of input configurations for the application.',\n items: {\n type: 'object',\n title: 'application_input',\n description: 'Configuration for an individual input field in the application.',\n properties: {\n input_type: {\n type: 'string',\n title: 'application_input_type',\n description: 'Type of input field determining its behavior and validation rules.',\n enum: [ 'text',\n 'dropdown',\n 'file',\n 'media'\n ]\n },\n name: {\n type: 'string',\n description: 'Identifier for the input field.'\n },\n required: {\n type: 'boolean',\n description: 'Indicates if this input field is mandatory.'\n },\n description: {\n type: 'string',\n description: 'Human-readable description of the input field\\'s purpose.'\n },\n options: {\n anyOf: [ {\n type: 'object',\n title: 'Dropdown',\n description: 'Configuration options specific to dropdown-type input fields.',\n properties: {\n list: {\n type: 'array',\n description: 'List of available options in the dropdown menu.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'list'\n ]\n },\n {\n type: 'object',\n title: 'File',\n description: 'Configuration options specific to file upload input fields.',\n properties: {\n file_types: {\n type: 'array',\n description: 'List of allowed file extensions.',\n items: {\n type: 'string'\n }\n },\n max_file_size_mb: {\n type: 'integer',\n description: 'Maximum file size allowed in megabytes.'\n },\n max_files: {\n type: 'integer',\n description: 'Maximum number of files that can be uploaded.'\n },\n max_word_count: {\n type: 'integer',\n description: 'Maximum number of words allowed in text files.'\n },\n upload_types: {\n type: 'array',\n description: 'List of allowed upload types for file inputs.',\n items: {\n type: 'string',\n title: 'file_upload_type',\n description: 'Type of file upload method supported by the application.',\n enum: [ 'url',\n 'file_id'\n ]\n }\n }\n },\n required: [ 'file_types',\n 'max_file_size_mb',\n 'max_files',\n 'max_word_count',\n 'upload_types'\n ]\n },\n {\n type: 'object',\n title: 'Media',\n description: 'Configuration options specific to media upload input fields.',\n properties: {\n file_types: {\n type: 'array',\n description: 'List of allowed media file types.',\n items: {\n type: 'string'\n }\n },\n max_image_size_mb: {\n type: 'integer',\n description: 'Maximum media file size allowed in megabytes.'\n }\n },\n required: [ 'file_types',\n 'max_image_size_mb'\n ]\n },\n {\n type: 'object',\n title: 'Text',\n description: 'Configuration options specific to text input fields.',\n properties: {\n max_fields: {\n type: 'integer',\n description: 'Maximum number of text fields allowed.'\n },\n min_fields: {\n type: 'integer',\n description: 'Minimum number of text fields required.'\n }\n },\n required: [ 'max_fields',\n 'min_fields'\n ]\n }\n ],\n description: 'Type-specific configuration options for input fields.'\n }\n },\n required: [ 'input_type',\n 'name',\n 'required'\n ]\n }\n },\n name: {\n type: 'string',\n description: 'Display name of the application.'\n },\n status: {\n type: 'string',\n title: 'application_status',\n description: 'Current deployment status of the application. Note: currently only `deployed` applications are returned.',\n enum: [ 'deployed',\n 'draft'\n ]\n },\n type: {\n type: 'string',\n title: 'application_type',\n description: 'The type of no-code application.',\n enum: [ 'generation'\n ]\n },\n updated_at: {\n type: 'string',\n description: 'Timestamp when the application was last updated.',\n format: 'date-time'\n },\n last_deployed_at: {\n type: 'string',\n description: 'Timestamp when the application was last deployed.',\n format: 'date-time'\n }\n },\n required: [ 'id',\n 'created_at',\n 'inputs',\n 'name',\n 'status',\n 'type',\n 'updated_at'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - after: { - type: 'string', - description: 'Return results after this application ID for pagination.', - }, - before: { - type: 'string', - description: 'Return results before this application ID for pagination.', - }, - limit: { - type: 'integer', - description: 'Maximum number of applications to return in the response.', - }, - order: { - type: 'string', - description: 'Sort order for the results based on creation time.', - enum: ['asc', 'desc'], - }, - type: { - type: 'string', - title: 'application_type', - description: 'Filter applications by their type.', - enum: ['generation'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - const response = await client.applications.list(body).asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/retrieve-applications.ts b/packages/mcp-server/src/tools/applications/retrieve-applications.ts deleted file mode 100644 index a85120ec..00000000 --- a/packages/mcp-server/src/tools/applications/retrieve-applications.ts +++ /dev/null @@ -1,55 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'applications', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/applications/{application_id}', -}; - -export const tool: Tool = { - name: 'retrieve_applications', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRetrieves detailed information for a specific no-code agent (formerly called no-code applications), including its configuration and current status.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/application_retrieve_response',\n $defs: {\n application_retrieve_response: {\n type: 'object',\n title: 'application_with_inputs',\n description: 'Detailed application object including its input configuration.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the application.'\n },\n created_at: {\n type: 'string',\n description: 'Timestamp when the application was created.',\n format: 'date-time'\n },\n inputs: {\n type: 'array',\n description: 'List of input configurations for the application.',\n items: {\n type: 'object',\n title: 'application_input',\n description: 'Configuration for an individual input field in the application.',\n properties: {\n input_type: {\n type: 'string',\n title: 'application_input_type',\n description: 'Type of input field determining its behavior and validation rules.',\n enum: [ 'text',\n 'dropdown',\n 'file',\n 'media'\n ]\n },\n name: {\n type: 'string',\n description: 'Identifier for the input field.'\n },\n required: {\n type: 'boolean',\n description: 'Indicates if this input field is mandatory.'\n },\n description: {\n type: 'string',\n description: 'Human-readable description of the input field\\'s purpose.'\n },\n options: {\n anyOf: [ {\n type: 'object',\n title: 'Dropdown',\n description: 'Configuration options specific to dropdown-type input fields.',\n properties: {\n list: {\n type: 'array',\n description: 'List of available options in the dropdown menu.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'list'\n ]\n },\n {\n type: 'object',\n title: 'File',\n description: 'Configuration options specific to file upload input fields.',\n properties: {\n file_types: {\n type: 'array',\n description: 'List of allowed file extensions.',\n items: {\n type: 'string'\n }\n },\n max_file_size_mb: {\n type: 'integer',\n description: 'Maximum file size allowed in megabytes.'\n },\n max_files: {\n type: 'integer',\n description: 'Maximum number of files that can be uploaded.'\n },\n max_word_count: {\n type: 'integer',\n description: 'Maximum number of words allowed in text files.'\n },\n upload_types: {\n type: 'array',\n description: 'List of allowed upload types for file inputs.',\n items: {\n type: 'string',\n title: 'file_upload_type',\n description: 'Type of file upload method supported by the application.',\n enum: [ 'url',\n 'file_id'\n ]\n }\n }\n },\n required: [ 'file_types',\n 'max_file_size_mb',\n 'max_files',\n 'max_word_count',\n 'upload_types'\n ]\n },\n {\n type: 'object',\n title: 'Media',\n description: 'Configuration options specific to media upload input fields.',\n properties: {\n file_types: {\n type: 'array',\n description: 'List of allowed media file types.',\n items: {\n type: 'string'\n }\n },\n max_image_size_mb: {\n type: 'integer',\n description: 'Maximum media file size allowed in megabytes.'\n }\n },\n required: [ 'file_types',\n 'max_image_size_mb'\n ]\n },\n {\n type: 'object',\n title: 'Text',\n description: 'Configuration options specific to text input fields.',\n properties: {\n max_fields: {\n type: 'integer',\n description: 'Maximum number of text fields allowed.'\n },\n min_fields: {\n type: 'integer',\n description: 'Minimum number of text fields required.'\n }\n },\n required: [ 'max_fields',\n 'min_fields'\n ]\n }\n ],\n description: 'Type-specific configuration options for input fields.'\n }\n },\n required: [ 'input_type',\n 'name',\n 'required'\n ]\n }\n },\n name: {\n type: 'string',\n description: 'Display name of the application.'\n },\n status: {\n type: 'string',\n title: 'application_status',\n description: 'Current deployment status of the application. Note: currently only `deployed` applications are returned.',\n enum: [ 'deployed',\n 'draft'\n ]\n },\n type: {\n type: 'string',\n title: 'application_type',\n description: 'The type of no-code application.',\n enum: [ 'generation'\n ]\n },\n updated_at: {\n type: 'string',\n description: 'Timestamp when the application was last updated.',\n format: 'date-time'\n },\n last_deployed_at: {\n type: 'string',\n description: 'Timestamp when the application was last deployed.',\n format: 'date-time'\n }\n },\n required: [ 'id',\n 'created_at',\n 'inputs',\n 'name',\n 'status',\n 'type',\n 'updated_at'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - application_id: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['application_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { application_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.applications.retrieve(application_id)), - ); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/chat/chat-chat.ts b/packages/mcp-server/src/tools/chat/chat-chat.ts deleted file mode 100644 index 42b3164b..00000000 --- a/packages/mcp-server/src/tools/chat/chat-chat.ts +++ /dev/null @@ -1,937 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'chat', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/chat', - operationId: 'chat', -}; - -export const tool: Tool = { - name: 'chat_chat', - description: - 'Generate AI responses for conversational interactions. Use this for chat-based tasks, Q&A, content generation, and any natural language processing. Supports tools like Knowledge Graphs, web search, translation, and vision. Choose from models like palmyra-x5, palmyra-x4, palmyra-creative, palmyra-med, or palmyra-fin depending on the task.', - inputSchema: { - type: 'object', - anyOf: [ - { - type: 'object', - properties: { - messages: { - type: 'array', - description: - 'An array of message objects that form the conversation history or context for the model to respond to. The array must contain at least one message.', - items: { - type: 'object', - title: 'chat_message', - properties: { - role: { - type: 'string', - description: - 'The role of the chat message. You can provide a system prompt by setting the role to `system`, or specify that a message is the result of a [tool call](https://dev.writer.com/home/tool-calling) by setting the role to `tool`.', - enum: ['user', 'assistant', 'system', 'tool'], - }, - content: { - anyOf: [ - { - type: 'string', - title: 'TextContent', - }, - { - type: 'array', - title: 'MixedContent', - items: { - anyOf: [ - { - type: 'object', - title: 'Text', - description: 'Represents a text content fragment within a chat message.', - properties: { - text: { - type: 'string', - description: 'The actual text content of the message fragment.', - }, - type: { - type: 'string', - description: - 'The type of content fragment. Must be `text` for text fragments.', - enum: ['text'], - }, - }, - required: ['text', 'type'], - }, - { - type: 'object', - title: 'Image', - description: - 'Represents an image content fragment within a chat message. Note: This content type is only supported with the Palmyra X5 model.', - properties: { - image_url: { - type: 'object', - description: 'The image URL object containing the location of the image.', - properties: { - url: { - type: 'string', - description: - 'The URL pointing to the image file. Supports common image formats like JPEG, PNG, GIF, etc.', - }, - }, - required: ['url'], - }, - type: { - type: 'string', - description: - 'The type of content fragment. Must be `image_url` for image fragments.', - enum: ['image_url'], - }, - }, - required: ['image_url', 'type'], - }, - ], - title: 'composite_content', - description: - 'A union type that can contain either text or image content fragments. This enables chat messages to include mixed content types, allowing users to send both text and images in a single message. Note: Image fragments are only supported with the Palmyra X5 model.', - }, - }, - ], - description: - 'The content of the message. Can be either a string (for text-only messages) or an array of content fragments (for mixed text and image messages).', - }, - graph_data: { - $ref: '#/$defs/graph_data', - }, - name: { - type: 'string', - description: - 'An optional name for the message sender. Useful for identifying different users, personas, or tools in multi-participant conversations.', - }, - refusal: { - type: 'string', - }, - tool_call_id: { - type: 'string', - }, - tool_calls: { - type: 'array', - items: { - $ref: '#/$defs/tool_call', - }, - }, - }, - required: ['role'], - }, - }, - model: { - type: 'string', - description: - 'The [ID of the model](https://dev.writer.com/home/models) to use for creating the chat completion. Supports `palmyra-x5`, `palmyra-x4`, `palmyra-fin`, `palmyra-med`, `palmyra-creative`, and `palmyra-x-003-instruct`.', - }, - logprobs: { - type: 'boolean', - description: 'Specifies whether to return log probabilities of the output tokens.', - }, - max_tokens: { - type: 'integer', - description: - 'Defines the maximum number of tokens (words and characters) that the model can generate in the response. This can be adjusted to allow for longer or shorter responses as needed. The maximum value varies by model. See the [models overview](/home/models) for more information about the maximum number of tokens for each model.', - }, - n: { - type: 'integer', - description: - 'Specifies the number of completions (responses) to generate from the model in a single request. This parameter allows for generating multiple responses, offering a variety of potential replies from which to choose.', - }, - response_format: { - type: 'object', - title: 'response_format', - description: - 'The response format to use for the chat completion, available with `palmyra-x4` and `palmyra-x5`.\n\n`text` is the default response format. [JSON Schema](https://json-schema.org/) is supported for structured responses. If you specify `json_schema`, you must also provide a `json_schema` object.', - properties: { - type: { - type: 'string', - description: 'The type of response format to use.', - enum: ['text', 'json_schema'], - }, - json_schema: { - type: 'object', - description: 'The JSON schema to use for the response format.', - additionalProperties: true, - }, - }, - required: ['type'], - }, - stop: { - anyOf: [ - { - type: 'array', - items: { - type: 'string', - }, - }, - { - type: 'string', - }, - ], - description: - 'A token or sequence of tokens that, when generated, will cause the model to stop producing further content. This can be a single token or an array of tokens, acting as a signal to end the output.', - }, - stream: { - type: 'string', - description: - 'Indicates whether the response should be streamed incrementally as it is generated or only returned once fully complete. Streaming can be useful for providing real-time feedback in interactive applications.', - enum: [false], - }, - stream_options: { - type: 'object', - title: 'stream_options', - description: 'Additional options for streaming.', - properties: { - include_usage: { - type: 'boolean', - description: 'Indicate whether to include usage information.', - }, - }, - required: ['include_usage'], - }, - temperature: { - type: 'number', - description: - "Controls the randomness or creativity of the model's responses. A higher temperature results in more varied and less predictable text, while a lower temperature produces more deterministic and conservative outputs.", - }, - tool_choice: { - anyOf: [ - { - $ref: '#/$defs/tool_choice_string', - }, - { - $ref: '#/$defs/tool_choice_json_object', - }, - ], - description: - 'Configure how the model will call functions:\n- `auto`: allows the model to automatically choose the tool to use, or not call a tool\n- `none`: disables tool calling; the model will instead generate a message\n- `required`: requires the model to call one or more tools\n\nYou can also use a JSON object to force the model to call a specific tool. For example, `{"type": "function", "function": {"name": "get_current_weather"}}` requires the model to call the `get_current_weather` function, regardless of the prompt.', - }, - tools: { - type: 'array', - description: - 'An array containing tool definitions for tools that the model can use to generate responses. The tool definitions use JSON schema. You can define your own functions or use one of the built-in `graph`, `llm`, `translation`, or `vision` tools. Note that you can only use one built-in tool type in the array (only one of `graph`, `llm`, `translation`, or `vision`). You can pass multiple [custom tools](https://dev.writer.com/home/tool-calling) of type `function` in the same request.', - items: { - $ref: '#/$defs/tool_param', - }, - }, - top_p: { - type: 'number', - description: - 'Sets the threshold for "nucleus sampling," a technique to focus the model\'s token generation on the most likely subset of tokens. Only tokens with cumulative probability above this threshold are considered, controlling the trade-off between creativity and coherence.', - }, - }, - required: ['messages', 'model'], - }, - { - type: 'object', - properties: { - messages: { - type: 'array', - description: - 'An array of message objects that form the conversation history or context for the model to respond to. The array must contain at least one message.', - items: { - type: 'object', - title: 'chat_message', - properties: { - role: { - type: 'string', - description: - 'The role of the chat message. You can provide a system prompt by setting the role to `system`, or specify that a message is the result of a [tool call](https://dev.writer.com/home/tool-calling) by setting the role to `tool`.', - enum: ['user', 'assistant', 'system', 'tool'], - }, - content: { - anyOf: [ - { - type: 'string', - title: 'TextContent', - }, - { - type: 'array', - title: 'MixedContent', - items: { - anyOf: [ - { - type: 'object', - title: 'Text', - description: 'Represents a text content fragment within a chat message.', - properties: { - text: { - type: 'string', - description: 'The actual text content of the message fragment.', - }, - type: { - type: 'string', - description: - 'The type of content fragment. Must be `text` for text fragments.', - enum: ['text'], - }, - }, - required: ['text', 'type'], - }, - { - type: 'object', - title: 'Image', - description: - 'Represents an image content fragment within a chat message. Note: This content type is only supported with the Palmyra X5 model.', - properties: { - image_url: { - type: 'object', - description: 'The image URL object containing the location of the image.', - properties: { - url: { - type: 'string', - description: - 'The URL pointing to the image file. Supports common image formats like JPEG, PNG, GIF, etc.', - }, - }, - required: ['url'], - }, - type: { - type: 'string', - description: - 'The type of content fragment. Must be `image_url` for image fragments.', - enum: ['image_url'], - }, - }, - required: ['image_url', 'type'], - }, - ], - title: 'composite_content', - description: - 'A union type that can contain either text or image content fragments. This enables chat messages to include mixed content types, allowing users to send both text and images in a single message. Note: Image fragments are only supported with the Palmyra X5 model.', - }, - }, - ], - description: - 'The content of the message. Can be either a string (for text-only messages) or an array of content fragments (for mixed text and image messages).', - }, - graph_data: { - $ref: '#/$defs/graph_data', - }, - name: { - type: 'string', - description: - 'An optional name for the message sender. Useful for identifying different users, personas, or tools in multi-participant conversations.', - }, - refusal: { - type: 'string', - }, - tool_call_id: { - type: 'string', - }, - tool_calls: { - type: 'array', - items: { - $ref: '#/$defs/tool_call', - }, - }, - }, - required: ['role'], - }, - }, - model: { - type: 'string', - description: - 'The [ID of the model](https://dev.writer.com/home/models) to use for creating the chat completion. Supports `palmyra-x5`, `palmyra-x4`, `palmyra-fin`, `palmyra-med`, `palmyra-creative`, and `palmyra-x-003-instruct`.', - }, - stream: { - type: 'string', - description: - 'Indicates whether the response should be streamed incrementally as it is generated or only returned once fully complete. Streaming can be useful for providing real-time feedback in interactive applications.', - enum: [true], - }, - logprobs: { - type: 'boolean', - description: 'Specifies whether to return log probabilities of the output tokens.', - }, - max_tokens: { - type: 'integer', - description: - 'Defines the maximum number of tokens (words and characters) that the model can generate in the response. This can be adjusted to allow for longer or shorter responses as needed. The maximum value varies by model. See the [models overview](/home/models) for more information about the maximum number of tokens for each model.', - }, - n: { - type: 'integer', - description: - 'Specifies the number of completions (responses) to generate from the model in a single request. This parameter allows for generating multiple responses, offering a variety of potential replies from which to choose.', - }, - response_format: { - type: 'object', - title: 'response_format', - description: - 'The response format to use for the chat completion, available with `palmyra-x4` and `palmyra-x5`.\n\n`text` is the default response format. [JSON Schema](https://json-schema.org/) is supported for structured responses. If you specify `json_schema`, you must also provide a `json_schema` object.', - properties: { - type: { - type: 'string', - description: 'The type of response format to use.', - enum: ['text', 'json_schema'], - }, - json_schema: { - type: 'object', - description: 'The JSON schema to use for the response format.', - additionalProperties: true, - }, - }, - required: ['type'], - }, - stop: { - anyOf: [ - { - type: 'array', - items: { - type: 'string', - }, - }, - { - type: 'string', - }, - ], - description: - 'A token or sequence of tokens that, when generated, will cause the model to stop producing further content. This can be a single token or an array of tokens, acting as a signal to end the output.', - }, - stream_options: { - type: 'object', - title: 'stream_options', - description: 'Additional options for streaming.', - properties: { - include_usage: { - type: 'boolean', - description: 'Indicate whether to include usage information.', - }, - }, - required: ['include_usage'], - }, - temperature: { - type: 'number', - description: - "Controls the randomness or creativity of the model's responses. A higher temperature results in more varied and less predictable text, while a lower temperature produces more deterministic and conservative outputs.", - }, - tool_choice: { - anyOf: [ - { - $ref: '#/$defs/tool_choice_string', - }, - { - $ref: '#/$defs/tool_choice_json_object', - }, - ], - description: - 'Configure how the model will call functions:\n- `auto`: allows the model to automatically choose the tool to use, or not call a tool\n- `none`: disables tool calling; the model will instead generate a message\n- `required`: requires the model to call one or more tools\n\nYou can also use a JSON object to force the model to call a specific tool. For example, `{"type": "function", "function": {"name": "get_current_weather"}}` requires the model to call the `get_current_weather` function, regardless of the prompt.', - }, - tools: { - type: 'array', - description: - 'An array containing tool definitions for tools that the model can use to generate responses. The tool definitions use JSON schema. You can define your own functions or use one of the built-in `graph`, `llm`, `translation`, or `vision` tools. Note that you can only use one built-in tool type in the array (only one of `graph`, `llm`, `translation`, or `vision`). You can pass multiple [custom tools](https://dev.writer.com/home/tool-calling) of type `function` in the same request.', - items: { - $ref: '#/$defs/tool_param', - }, - }, - top_p: { - type: 'number', - description: - 'Sets the threshold for "nucleus sampling," a technique to focus the model\'s token generation on the most likely subset of tokens. Only tokens with cumulative probability above this threshold are considered, controlling the trade-off between creativity and coherence.', - }, - }, - required: ['messages', 'model', 'stream'], - }, - ], - $defs: { - graph_data: { - type: 'object', - title: 'graph_data', - properties: { - references: { - type: 'object', - title: 'references', - description: - 'Detailed source information organized by reference type, providing comprehensive metadata about the sources used to generate the response.', - properties: { - files: { - type: 'array', - description: 'Array of file-based references from uploaded documents in the Knowledge Graph.', - items: { - type: 'object', - title: 'file', - description: - 'A file-based reference containing text snippets from uploaded documents in the Knowledge Graph.', - properties: { - fileId: { - type: 'string', - description: 'The unique identifier of the file in your Writer account.', - }, - score: { - type: 'number', - description: - 'Internal score used during the retrieval process for ranking and selecting relevant snippets.', - }, - text: { - type: 'string', - description: - 'The exact text snippet from the source document that was used to support the response.', - }, - cite: { - type: 'string', - description: - 'Unique citation ID that appears in inline citations within the response text (null if not cited).', - }, - page: { - type: 'integer', - description: 'Page number where this snippet was found in the source document.', - }, - }, - required: ['fileId', 'score', 'text'], - }, - }, - web: { - type: 'array', - description: 'Array of web-based references from online sources accessed during the query.', - items: { - type: 'object', - title: 'web', - description: - 'A web-based reference containing text snippets from online sources accessed during the query.', - properties: { - score: { - type: 'number', - description: - 'Internal score used during the retrieval process for ranking and selecting relevant snippets.', - }, - text: { - type: 'string', - description: - 'The exact text snippet from the web source that was used to support the response.', - }, - title: { - type: 'string', - description: 'The title of the web page where this content was found.', - }, - url: { - type: 'string', - description: 'The URL of the web page where this content was found.', - }, - }, - required: ['score', 'text', 'title', 'url'], - }, - }, - }, - }, - sources: { - type: 'array', - items: { - $ref: '#/$defs/source', - }, - }, - status: { - type: 'string', - title: 'graph_stage_status', - enum: ['processing', 'finished'], - }, - subqueries: { - type: 'array', - items: { - type: 'object', - title: 'sub_query', - description: - 'A sub-question generated to break down complex queries into more manageable parts, along with its answer and supporting sources.', - properties: { - answer: { - type: 'string', - description: 'The answer to the subquery based on Knowledge Graph content.', - }, - query: { - type: 'string', - description: 'The subquery that was generated to help answer the main question.', - }, - sources: { - type: 'array', - description: 'Array of source snippets that were used to answer this subquery.', - items: { - $ref: '#/$defs/source', - }, - }, - }, - required: ['answer', 'query', 'sources'], - }, - }, - }, - }, - source: { - type: 'object', - title: 'source', - description: 'A source snippet containing text and fileId from Knowledge Graph content.', - properties: { - file_id: { - type: 'string', - description: 'The unique identifier of the file in your Writer account.', - }, - snippet: { - type: 'string', - description: - 'The exact text snippet from the source document that was used to support the response.', - }, - }, - required: ['file_id', 'snippet'], - }, - tool_call: { - type: 'object', - title: 'tool_call', - properties: { - id: { - type: 'string', - }, - function: { - type: 'object', - title: 'function', - properties: { - arguments: { - type: 'string', - }, - name: { - type: 'string', - }, - }, - required: ['arguments'], - }, - type: { - type: 'string', - enum: ['function'], - }, - index: { - type: 'integer', - }, - }, - required: ['id', 'function', 'type'], - }, - tool_choice_string: { - type: 'object', - title: 'String', - properties: { - value: { - type: 'string', - title: 'string_tool_choice_options', - enum: ['none', 'auto', 'required'], - }, - }, - required: ['value'], - }, - tool_choice_json_object: { - type: 'object', - title: 'JSON object', - properties: { - value: { - type: 'object', - description: - 'A JSON object that specifies the tool to call. For example, `{"type": "function", "function": {"name": "get_current_weather"}}`', - additionalProperties: true, - }, - }, - required: ['value'], - }, - tool_param: { - anyOf: [ - { - type: 'object', - title: 'Function tool', - properties: { - function: { - $ref: '#/$defs/function_definition', - }, - type: { - type: 'string', - description: 'The type of tool.', - enum: ['function'], - }, - }, - required: ['function', 'type'], - }, - { - type: 'object', - title: 'Graph tool', - properties: { - function: { - type: 'object', - title: 'graph_function', - description: 'A tool that uses Knowledge Graphs as context for responses.', - properties: { - graph_ids: { - type: 'array', - description: 'An array of graph IDs to use in the tool.', - items: { - type: 'string', - }, - }, - subqueries: { - type: 'boolean', - description: 'Boolean to indicate whether to include subqueries in the response.', - }, - description: { - type: 'string', - description: 'A description of the graph content.', - }, - query_config: { - type: 'object', - title: 'graph_query_config', - description: - 'Configuration options for Knowledge Graph queries, including search parameters and citation settings.', - properties: { - grounding_level: { - type: 'number', - description: - 'Level of grounding required for responses, controlling how closely answers must be tied to source material. Set lower for grounded outputs, higher for creativity. Higher values (closer to 1.0) allow more creative interpretation, while lower values (closer to 0.0) stick more closely to source material. Range: 0.0-1.0, Default: 0.0.', - }, - inline_citations: { - type: 'boolean', - description: - 'Whether to include inline citations in the response, showing which Knowledge Graph sources were used. Default: false.', - }, - keyword_threshold: { - type: 'number', - description: - 'Threshold for keyword-based matching when searching Knowledge Graph content. Set higher for stricter relevance, lower for broader range. Higher values (closer to 1.0) require stronger keyword matches, while lower values (closer to 0.0) allow more lenient matching. Range: 0.0-1.0, Default: 0.7.', - }, - max_snippets: { - type: 'integer', - description: - 'Maximum number of text snippets to retrieve from the Knowledge Graph for context. Works in concert with `search_weight` to control best matches vs broader coverage. While technically supports 1-60, values below 5 may return no results due to RAG implementation. Recommended range: 5-25. Due to RAG system behavior, you may see more snippets than requested. Range: 1-60, Default: 30.', - }, - max_subquestions: { - type: 'integer', - description: - 'Maximum number of subquestions to generate when processing complex queries. Set higher to improve detail, set lower to reduce response time. Range: 1-10, Default: 6.', - }, - max_tokens: { - type: 'integer', - description: - "Maximum number of tokens the model can generate in the response. This controls the length of the AI's answer. Set higher for longer answers, set lower for shorter, faster answers. Range: 100-8000, Default: 4000.", - }, - search_weight: { - type: 'integer', - description: - 'Weight given to search results when ranking and selecting relevant information. Higher values (closer to 100) prioritize keyword-based matching, while lower values (closer to 0) prioritize semantic similarity matching. Use higher values for exact keyword searches, lower values for conceptual similarity searches. Range: 0-100, Default: 50.', - }, - semantic_threshold: { - type: 'number', - description: - 'Threshold for semantic similarity matching when searching Knowledge Graph content. Set higher for stricter relevance, lower for broader range. Higher values (closer to 1.0) require stronger semantic similarity, while lower values (closer to 0.0) allow more lenient semantic matching. Range: 0.0-1.0, Default: 0.7.', - }, - }, - }, - }, - required: ['graph_ids', 'subqueries'], - }, - type: { - type: 'string', - description: 'The type of tool.', - enum: ['graph'], - }, - }, - required: ['function', 'type'], - }, - { - type: 'object', - title: 'LLM tool', - properties: { - function: { - type: 'object', - title: 'LLM function', - description: 'A tool that uses another Writer model to generate a response.', - properties: { - description: { - type: 'string', - description: 'A description of the model to use.', - }, - model: { - type: 'string', - description: 'The model to use.', - }, - }, - required: ['description', 'model'], - }, - type: { - type: 'string', - description: 'The type of tool.', - enum: ['llm'], - }, - }, - required: ['function', 'type'], - }, - { - type: 'object', - title: 'Translation tool', - description: - 'A tool that uses Palmyra Translate to translate text. Note that this tool does not stream results. The response is returned after the translation is complete.', - properties: { - function: { - type: 'object', - title: 'Translation function', - description: 'A tool that uses Palmyra Translate to translate text.', - properties: { - formality: { - type: 'boolean', - description: - 'Whether to use formal or informal language in the translation. See the [list of languages that support formality](https://dev.writer.com/api-reference/translation-api/language-support#formality). If the language does not support formality, this parameter is ignored.', - }, - length_control: { - type: 'boolean', - description: - 'Whether to control the length of the translated text. See the [list of languages that support length control](https://dev.writer.com/api-reference/translation-api/language-support#length-control). If the language does not support length control, this parameter is ignored.', - }, - mask_profanity: { - type: 'boolean', - description: - 'Whether to mask profane words in the translated text. See the [list of languages that do not support profanity masking](https://dev.writer.com/api-reference/translation-api/language-support#profanity-masking). If the language does not support profanity masking, this parameter is ignored.', - }, - model: { - type: 'string', - description: 'The model to use for translation.', - enum: ['palmyra-translate'], - }, - source_language_code: { - type: 'string', - description: - 'Optional. The [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) language code of the original text to translate. For example, `en` for English, `zh` for Chinese, `fr` for French, `es` for Spanish. If the language has a variant, the code appends the two-digit [ISO-3166 country code](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes). If you do not provide a language code, the LLM detects the language of the text.', - }, - target_language_code: { - type: 'string', - description: - 'Optional. The [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) language code of the target language for the translation. For example, `en` for English, `zh` for Chinese, `fr` for French, `es` for Spanish. If the language has a variant, the code appends the two-digit [ISO-3166 country code](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes). If you do not provide a language code, the LLM uses the content of the chat message to determine the target language.', - }, - }, - required: ['formality', 'length_control', 'mask_profanity', 'model'], - }, - type: { - type: 'string', - description: 'The type of tool.', - enum: ['translation'], - }, - }, - required: ['function', 'type'], - }, - { - type: 'object', - title: 'Vision tool', - properties: { - function: { - type: 'object', - title: 'Vision function', - description: - 'A tool that uses Palmyra Vision to analyze images and documents. Supports JPG, PNG, PDF, and TXT files up to 7MB each.', - properties: { - model: { - type: 'string', - description: 'The model to use for image analysis.', - enum: ['palmyra-vision'], - }, - variables: { - type: 'array', - items: { - type: 'object', - title: 'Vision Tool Request File Variable', - properties: { - file_id: { - type: 'string', - description: - 'The File ID of the file to analyze. The file must be uploaded to the Writer platform before you use it with the Vision tool. Supported file types: JPG, PNG, PDF, TXT. The maximum allowed file size is 7MB.', - }, - name: { - type: 'string', - description: - 'The name of the file variable. You must reference this name in the `message.content` field of the request to the chat completions endpoint. Use double curly braces (`{{}}`) to reference the file. For example, `Describe the difference between the image {{image_1}} and the image {{image_2}}`.', - }, - }, - required: ['file_id', 'name'], - }, - }, - }, - required: ['model', 'variables'], - }, - type: { - type: 'string', - description: 'The type of tool.', - enum: ['vision'], - }, - }, - required: ['function', 'type'], - }, - { - type: 'object', - title: 'Web search tool', - properties: { - function: { - type: 'object', - title: 'web_search_function', - description: 'A tool that uses web search to find information.', - properties: { - exclude_domains: { - type: 'array', - description: 'An array of domains to exclude from the search results.', - items: { - type: 'string', - }, - }, - include_domains: { - type: 'array', - description: 'An array of domains to include in the search results.', - items: { - type: 'string', - }, - }, - }, - required: ['exclude_domains', 'include_domains'], - }, - type: { - type: 'string', - description: 'The type of tool.', - enum: ['web_search'], - }, - }, - required: ['function', 'type'], - }, - ], - description: - 'A tool that uses Palmyra Translate to translate text. Note that this tool does not stream results. The response is returned after the translation is complete.', - }, - function_definition: { - type: 'object', - title: 'tool_function', - description: 'A tool that uses a custom function.', - properties: { - name: { - type: 'string', - description: 'Name of the function.', - }, - description: { - type: 'string', - description: 'Description of the function.', - }, - parameters: { - $ref: '#/$defs/function_params', - }, - }, - required: ['name'], - }, - function_params: { - type: 'object', - description: 'The parameters of the function.', - additionalProperties: true, - }, - }, - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const body = args as any; - return asTextContentResult(await client.chat.chat(body)); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/completions/create-completions.ts b/packages/mcp-server/src/tools/completions/create-completions.ts deleted file mode 100644 index 9b9a4cad..00000000 --- a/packages/mcp-server/src/tools/completions/create-completions.ts +++ /dev/null @@ -1,170 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'completions', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/completions', - operationId: 'completions', -}; - -export const tool: Tool = { - name: 'create_completions', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGenerate text completions from a single prompt without conversational context. Best for straightforward text generation tasks like article writing, summaries, or creative content. For interactive conversations or multi-turn dialogues, use generate-chat-completion instead.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/completion',\n $defs: {\n completion: {\n type: 'object',\n properties: {\n choices: {\n type: 'array',\n description: 'A list of choices generated by the model, each containing the text of the completion and associated metadata such as log probabilities.',\n items: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'The generated text output from the model, which forms the main content of the response.'\n },\n log_probs: {\n $ref: '#/$defs/logprobs'\n }\n },\n required: [ 'text'\n ]\n }\n },\n model: {\n type: 'string',\n description: 'The identifier of the model that was used to generate the responses in the \\'choices\\' array.'\n }\n },\n required: [ 'choices'\n ]\n },\n logprobs: {\n type: 'object',\n properties: {\n content: {\n type: 'array',\n items: {\n $ref: '#/$defs/logprobs_token'\n }\n },\n refusal: {\n type: 'array',\n items: {\n $ref: '#/$defs/logprobs_token'\n }\n }\n },\n required: [ 'content',\n 'refusal'\n ]\n },\n logprobs_token: {\n type: 'object',\n title: 'logprobs_token',\n properties: {\n token: {\n type: 'string'\n },\n logprob: {\n type: 'number'\n },\n top_logprobs: {\n type: 'array',\n items: {\n type: 'object',\n title: 'top_log_prob',\n description: 'An array of mappings for each token to its top log probabilities, showing detailed prediction probabilities.',\n properties: {\n token: {\n type: 'string'\n },\n logprob: {\n type: 'number'\n },\n bytes: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n },\n required: [ 'token',\n 'logprob'\n ]\n }\n },\n bytes: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n },\n required: [ 'token',\n 'logprob',\n 'top_logprobs'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - anyOf: [ - { - type: 'object', - properties: { - model: { - type: 'string', - description: - 'The [ID of the model](https://dev.writer.com/home/models) to use for generating text. Supports `palmyra-x5`, `palmyra-x4`, `palmyra-fin`, `palmyra-med`, `palmyra-creative`, and `palmyra-x-003-instruct`.', - }, - prompt: { - type: 'string', - description: 'The input text that the model will process to generate a response.', - }, - best_of: { - type: 'integer', - description: - 'Specifies the number of completions to generate and return the best one. Useful for generating multiple outputs and choosing the best based on some criteria.', - }, - max_tokens: { - type: 'integer', - description: 'The maximum number of tokens that the model can generate in the response.', - }, - random_seed: { - type: 'integer', - description: - 'A seed used to initialize the random number generator for the model, ensuring reproducibility of the output when the same inputs are provided.', - }, - stop: { - anyOf: [ - { - type: 'array', - items: { - type: 'string', - }, - }, - { - type: 'string', - }, - ], - description: - "Specifies stopping conditions for the model's output generation. This can be an array of strings or a single string that the model will look for as a signal to stop generating further tokens.", - }, - stream: { - type: 'string', - description: - "Determines whether the model's output should be streamed. If true, the output is generated and sent incrementally, which can be useful for real-time applications.", - enum: [false], - }, - temperature: { - type: 'number', - description: - "Controls the randomness of the model's outputs. Higher values lead to more random outputs, while lower values make the model more deterministic.", - }, - top_p: { - type: 'number', - description: - 'Used to control the nucleus sampling, where only the most probable tokens with a cumulative probability of top_p are considered for sampling, providing a way to fine-tune the randomness of predictions.', - }, - }, - required: ['model', 'prompt'], - }, - { - type: 'object', - properties: { - model: { - type: 'string', - description: - 'The [ID of the model](https://dev.writer.com/home/models) to use for generating text. Supports `palmyra-x5`, `palmyra-x4`, `palmyra-fin`, `palmyra-med`, `palmyra-creative`, and `palmyra-x-003-instruct`.', - }, - prompt: { - type: 'string', - description: 'The input text that the model will process to generate a response.', - }, - stream: { - type: 'string', - description: - "Determines whether the model's output should be streamed. If true, the output is generated and sent incrementally, which can be useful for real-time applications.", - enum: [true], - }, - best_of: { - type: 'integer', - description: - 'Specifies the number of completions to generate and return the best one. Useful for generating multiple outputs and choosing the best based on some criteria.', - }, - max_tokens: { - type: 'integer', - description: 'The maximum number of tokens that the model can generate in the response.', - }, - random_seed: { - type: 'integer', - description: - 'A seed used to initialize the random number generator for the model, ensuring reproducibility of the output when the same inputs are provided.', - }, - stop: { - anyOf: [ - { - type: 'array', - items: { - type: 'string', - }, - }, - { - type: 'string', - }, - ], - description: - "Specifies stopping conditions for the model's output generation. This can be an array of strings or a single string that the model will look for as a signal to stop generating further tokens.", - }, - temperature: { - type: 'number', - description: - "Controls the randomness of the model's outputs. Higher values lead to more random outputs, while lower values make the model more deterministic.", - }, - top_p: { - type: 'number', - description: - 'Used to control the nucleus sampling, where only the most probable tokens with a cumulative probability of top_p are considered for sampling, providing a way to fine-tune the randomness of predictions.', - }, - }, - required: ['model', 'prompt', 'stream'], - }, - ], - properties: { - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.completions.create(body))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/delete-files.ts b/packages/mcp-server/src/tools/files/delete-files.ts deleted file mode 100644 index 43050c44..00000000 --- a/packages/mcp-server/src/tools/files/delete-files.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/files/{file_id}', - operationId: 'gatewayDeleteFile', -}; - -export const tool: Tool = { - name: 'delete_files', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nPermanently delete a file from the system. This action cannot be undone.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file_delete_response',\n $defs: {\n file_delete_response: {\n type: 'object',\n title: 'delete_file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the deleted file.'\n },\n deleted: {\n type: 'boolean',\n description: 'Indicates whether the file was successfully deleted.'\n }\n },\n required: [ 'id',\n 'deleted'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - file_id: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['file_id'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { file_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.delete(file_id))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/download-files.ts b/packages/mcp-server/src/tools/files/download-files.ts deleted file mode 100644 index 0afb4831..00000000 --- a/packages/mcp-server/src/tools/files/download-files.ts +++ /dev/null @@ -1,40 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asBinaryContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/{file_id}/download', - operationId: 'gatewayDownloadFile', -}; - -export const tool: Tool = { - name: 'download_files', - description: - 'Download the binary content of a file. The response will contain the file data in the appropriate MIME type.', - inputSchema: { - type: 'object', - properties: { - file_id: { - type: 'string', - }, - }, - required: ['file_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { file_id, ...body } = args as any; - return asBinaryContentResult(await client.files.download(file_id).asResponse()); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/list-files.ts b/packages/mcp-server/src/tools/files/list-files.ts deleted file mode 100644 index ce78108e..00000000 --- a/packages/mcp-server/src/tools/files/list-files.ts +++ /dev/null @@ -1,88 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files', - operationId: 'gatewayGetFiles', -}; - -export const tool: Tool = { - name: 'list_files', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet a paginated list of all uploaded files. Filter by processing status (in_progress, completed, failed), Knowledge Graph association, or file type. Use this to discover available files, monitor processing status, or find files to add to Knowledge Graphs.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'files_response',\n properties: {\n data: {\n type: 'array',\n items: {\n $ref: '#/$defs/file'\n }\n },\n has_more: {\n type: 'boolean',\n description: 'Indicates if there are more files available beyond the current page.'\n },\n first_id: {\n type: 'string',\n description: 'The ID of the first file in the current response.'\n },\n last_id: {\n type: 'string',\n description: 'The ID of the last file in the current response.'\n }\n },\n required: [ 'data',\n 'has_more'\n ],\n $defs: {\n file: {\n type: 'object',\n title: 'file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the file.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the file was uploaded.',\n format: 'date-time'\n },\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graph IDs that the file is associated with.\\n\\nIf you provided a `graphId` during upload, the file is associated with that Knowledge Graph. However, the `graph_ids` field in the upload response is an empty list. The association will be visible in the `graph_ids` list when you retrieve the file using the file retrieval endpoint.',\n items: {\n type: 'string'\n }\n },\n name: {\n type: 'string',\n description: 'The name of the file.'\n },\n status: {\n type: 'string',\n description: 'The processing status of the file.'\n }\n },\n required: [ 'id',\n 'created_at',\n 'graph_ids',\n 'name',\n 'status'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - after: { - type: 'string', - description: - 'The ID of the last object in the previous page. This parameter instructs the API to return the next page of results.', - }, - before: { - type: 'string', - description: - 'The ID of the first object in the previous page. This parameter instructs the API to return the previous page of results.', - }, - file_types: { - type: 'string', - description: - 'The extensions of the files to retrieve. Separate multiple extensions with a comma. For example: `pdf,jpg,docx`.', - }, - graph_id: { - type: 'string', - description: 'The unique identifier of the graph to which the files belong.', - }, - limit: { - type: 'integer', - description: - 'Specifies the maximum number of objects returned in a page. The default value is 50. The minimum value is 1, and the maximum value is 100.', - }, - order: { - type: 'string', - description: - 'Specifies the order of the results. Valid values are asc for ascending and desc for descending.', - enum: ['asc', 'desc'], - }, - status: { - type: 'string', - description: - 'Specifies the status of the files to retrieve. Valid values are in_progress, completed or failed.', - enum: ['in_progress', 'completed', 'failed'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - const response = await client.files.list(body).asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/retrieve-files.ts b/packages/mcp-server/src/tools/files/retrieve-files.ts deleted file mode 100644 index 2621dd27..00000000 --- a/packages/mcp-server/src/tools/files/retrieve-files.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/{file_id}', - operationId: 'gatewayGetFile', -}; - -export const tool: Tool = { - name: 'retrieve_files', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet metadata and status information for a specific file by its ID. Returns file name, creation date, processing status, and associated Knowledge Graph IDs. Use this to check if a file has finished processing or to find which Knowledge Graphs contain a specific file.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the file.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the file was uploaded.',\n format: 'date-time'\n },\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graph IDs that the file is associated with.\\n\\nIf you provided a `graphId` during upload, the file is associated with that Knowledge Graph. However, the `graph_ids` field in the upload response is an empty list. The association will be visible in the `graph_ids` list when you retrieve the file using the file retrieval endpoint.',\n items: {\n type: 'string'\n }\n },\n name: {\n type: 'string',\n description: 'The name of the file.'\n },\n status: {\n type: 'string',\n description: 'The processing status of the file.'\n }\n },\n required: [ 'id',\n 'created_at',\n 'graph_ids',\n 'name',\n 'status'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - file_id: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['file_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { file_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.retrieve(file_id))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/retry-files.ts b/packages/mcp-server/src/tools/files/retry-files.ts deleted file mode 100644 index cc62a08b..00000000 --- a/packages/mcp-server/src/tools/files/retry-files.ts +++ /dev/null @@ -1,56 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/retry', - operationId: 'gatewayRetryFailedFiles', -}; - -export const tool: Tool = { - name: 'retry_files', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRetry processing of files that previously failed to process. This will re-attempt the processing of the specified files.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file_retry_response',\n $defs: {\n file_retry_response: {\n type: 'object',\n title: 'retry_files_response',\n properties: {\n success: {\n type: 'boolean',\n description: 'Indicates whether the retry operation was successful.'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - file_ids: { - type: 'array', - description: 'The unique identifier of the files to retry.', - items: { - type: 'string', - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['file_ids'], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.retry(body))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/upload-files.ts b/packages/mcp-server/src/tools/files/upload-files.ts deleted file mode 100644 index 4e9c7356..00000000 --- a/packages/mcp-server/src/tools/files/upload-files.ts +++ /dev/null @@ -1,60 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files', - operationId: 'gatewayUploadFile', -}; - -export const tool: Tool = { - name: 'upload_files', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nUpload documents and files to Writer. Supports PDF, DOC, DOCX, PPT, PPTX, JPG, PNG, EML, HTML, SRT, CSV, XLS, XLSX, MP3, and MP4 formats. Once uploaded, files can be added to Knowledge Graphs for querying or used with Vision API for image analysis. Returns a file ID for subsequent operations.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the file.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the file was uploaded.',\n format: 'date-time'\n },\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graph IDs that the file is associated with.\\n\\nIf you provided a `graphId` during upload, the file is associated with that Knowledge Graph. However, the `graph_ids` field in the upload response is an empty list. The association will be visible in the `graph_ids` list when you retrieve the file using the file retrieval endpoint.',\n items: {\n type: 'string'\n }\n },\n name: {\n type: 'string',\n description: 'The name of the file.'\n },\n status: {\n type: 'string',\n description: 'The processing status of the file.'\n }\n },\n required: [ 'id',\n 'created_at',\n 'graph_ids',\n 'name',\n 'status'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - content: { - type: 'string', - }, - 'Content-Disposition': { - type: 'string', - }, - graphId: { - type: 'string', - description: - 'The unique identifier of the Knowledge Graph to associate the uploaded file with.\n\nNote: The response from the upload endpoint does not include the `graphId` field, but the association will be visible when you retrieve the file using the file retrieval endpoint.', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['content', 'Content-Disposition'], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.upload(body))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/add-file-to-graph-graphs.ts b/packages/mcp-server/src/tools/graphs/add-file-to-graph-graphs.ts deleted file mode 100644 index a25705e1..00000000 --- a/packages/mcp-server/src/tools/graphs/add-file-to-graph-graphs.ts +++ /dev/null @@ -1,58 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'graphs', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/graphs/{graph_id}/file', - operationId: 'addFileToGraph', -}; - -export const tool: Tool = { - name: 'add_file_to_graph_graphs', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nAdd an uploaded file to a Knowledge Graph to make it queryable. The file must already be uploaded using upload-file. Once added, the file's content becomes searchable when querying the Knowledge Graph. Files are processed asynchronously - check status using get-file-info.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the file.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the file was uploaded.',\n format: 'date-time'\n },\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graph IDs that the file is associated with.\\n\\nIf you provided a `graphId` during upload, the file is associated with that Knowledge Graph. However, the `graph_ids` field in the upload response is an empty list. The association will be visible in the `graph_ids` list when you retrieve the file using the file retrieval endpoint.',\n items: {\n type: 'string'\n }\n },\n name: {\n type: 'string',\n description: 'The name of the file.'\n },\n status: {\n type: 'string',\n description: 'The processing status of the file.'\n }\n },\n required: [ 'id',\n 'created_at',\n 'graph_ids',\n 'name',\n 'status'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - graph_id: { - type: 'string', - }, - file_id: { - type: 'string', - description: 'The unique identifier of the file.', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['graph_id', 'file_id'], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { graph_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.graphs.addFileToGraph(graph_id, body)), - ); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/create-graphs.ts b/packages/mcp-server/src/tools/graphs/create-graphs.ts deleted file mode 100644 index b24f68aa..00000000 --- a/packages/mcp-server/src/tools/graphs/create-graphs.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'graphs', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/graphs', - operationId: 'createGraph', -}; - -export const tool: Tool = { - name: 'create_graphs', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nCreate a new Knowledge Graph to organize and query documents. Knowledge Graphs are containers for files that enable AI-powered search and question answering. After creation, add files to the graph using add-file-to-graph, then query it using query-knowledge-graph.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/graph_create_response',\n $defs: {\n graph_create_response: {\n type: 'object',\n title: 'graph_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the Knowledge Graph.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the Knowledge Graph was created.',\n format: 'date-time'\n },\n name: {\n type: 'string',\n description: 'The name of the Knowledge Graph (max 255 characters).'\n },\n description: {\n type: 'string',\n description: 'A description of the Knowledge Graph (max 255 characters).'\n },\n urls: {\n type: 'array',\n description: 'An array of web connector URLs associated with this Knowledge Graph.',\n items: {\n type: 'object',\n title: 'web_connector_url',\n properties: {\n status: {\n type: 'object',\n title: 'web_connector_url_state',\n description: 'The current status of the URL processing.',\n properties: {\n status: {\n type: 'string',\n title: 'web_connector_url_status',\n description: 'The current status of the URL processing.',\n enum: [ 'validating',\n 'success',\n 'error'\n ]\n },\n error_type: {\n type: 'string',\n title: 'web_connector_url_error_type',\n description: 'The type of error that occurred during processing, if any.',\n enum: [ 'invalid_url',\n 'not_searchable',\n 'not_found',\n 'paywall_or_login_page',\n 'unexpected_error'\n ]\n }\n },\n required: [ 'status'\n ]\n },\n type: {\n type: 'string',\n title: 'web_connector_url_type',\n description: 'The type of web connector processing for this URL.',\n enum: [ 'single_page',\n 'sub_pages'\n ]\n },\n url: {\n type: 'string',\n description: 'The URL to be processed by the web connector.'\n },\n exclude_urls: {\n type: 'array',\n description: 'An array of URLs to exclude from processing within this web connector.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'status',\n 'type',\n 'url'\n ]\n }\n }\n },\n required: [ 'id',\n 'created_at',\n 'name'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - description: { - type: 'string', - description: - 'A description of the Knowledge Graph (max 255 characters). Omitting this field leaves the description unchanged.', - }, - name: { - type: 'string', - description: - 'The name of the Knowledge Graph (max 255 characters). Omitting this field leaves the name unchanged.', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.graphs.create(body))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/delete-graphs.ts b/packages/mcp-server/src/tools/graphs/delete-graphs.ts deleted file mode 100644 index f49d8e37..00000000 --- a/packages/mcp-server/src/tools/graphs/delete-graphs.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'graphs', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/graphs/{graph_id}', - operationId: 'deleteGraph', -}; - -export const tool: Tool = { - name: 'delete_graphs', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nDelete a Knowledge Graph.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/graph_delete_response',\n $defs: {\n graph_delete_response: {\n type: 'object',\n title: 'delete_graph_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the deleted Knowledge Graph.'\n },\n deleted: {\n type: 'boolean',\n description: 'Indicates whether the Knowledge Graph was successfully deleted.'\n }\n },\n required: [ 'id',\n 'deleted'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - graph_id: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['graph_id'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { graph_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.graphs.delete(graph_id))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/list-graphs.ts b/packages/mcp-server/src/tools/graphs/list-graphs.ts deleted file mode 100644 index c9ed0dcc..00000000 --- a/packages/mcp-server/src/tools/graphs/list-graphs.ts +++ /dev/null @@ -1,73 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'graphs', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/graphs', - operationId: 'findGraphsWithFileStatus', -}; - -export const tool: Tool = { - name: 'list_graphs', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet all available Knowledge Graphs in your account. Knowledge Graphs are collections of documents and files that can be queried using AI. Use this to discover which knowledge bases are available before querying them.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'graphs_response',\n properties: {\n data: {\n type: 'array',\n items: {\n $ref: '#/$defs/graph'\n }\n },\n has_more: {\n type: 'boolean',\n description: 'Indicates if there are more Knowledge Graphs available beyond the current page.'\n },\n first_id: {\n type: 'string',\n description: 'The ID of the first Knowledge Graph in the current response.'\n },\n last_id: {\n type: 'string',\n description: 'The ID of the last Knowledge Graph in the current response.'\n }\n },\n required: [ 'data',\n 'has_more'\n ],\n $defs: {\n graph: {\n type: 'object',\n title: 'graph',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier of the Knowledge Graph.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the Knowledge Graph was created.',\n format: 'date-time'\n },\n file_status: {\n type: 'object',\n title: 'graph_file_status',\n description: 'The processing status of files in the Knowledge Graph.',\n properties: {\n completed: {\n type: 'integer',\n description: 'The number of files that have been successfully processed.'\n },\n failed: {\n type: 'integer',\n description: 'The number of files that failed to process.'\n },\n in_progress: {\n type: 'integer',\n description: 'The number of files currently being processed.'\n },\n total: {\n type: 'integer',\n description: 'The total number of files associated with the Knowledge Graph.'\n }\n },\n required: [ 'completed',\n 'failed',\n 'in_progress',\n 'total'\n ]\n },\n name: {\n type: 'string',\n description: 'The name of the Knowledge Graph.'\n },\n type: {\n type: 'string',\n title: 'graph_type',\n description: 'The type of Knowledge Graph.\\n\\n- `manual`: files are uploaded via UI or API\\n- `connector`: files are uploaded via a data connector such as Google Drive or Confluence\\n- `web`: URLs are connected to the Knowledge Graph',\n enum: [ 'manual',\n 'connector',\n 'web'\n ]\n },\n description: {\n type: 'string',\n description: 'A description of the Knowledge Graph.'\n },\n urls: {\n type: 'array',\n description: 'An array of web connector URLs associated with this Knowledge Graph.',\n items: {\n type: 'object',\n title: 'web_connector_url',\n properties: {\n status: {\n type: 'object',\n title: 'web_connector_url_state',\n description: 'The current status of the URL processing.',\n properties: {\n status: {\n type: 'string',\n title: 'web_connector_url_status',\n description: 'The current status of the URL processing.',\n enum: [ 'validating',\n 'success',\n 'error'\n ]\n },\n error_type: {\n type: 'string',\n title: 'web_connector_url_error_type',\n description: 'The type of error that occurred during processing, if any.',\n enum: [ 'invalid_url',\n 'not_searchable',\n 'not_found',\n 'paywall_or_login_page',\n 'unexpected_error'\n ]\n }\n },\n required: [ 'status'\n ]\n },\n type: {\n type: 'string',\n title: 'web_connector_url_type',\n description: 'The type of web connector processing for this URL.',\n enum: [ 'single_page',\n 'sub_pages'\n ]\n },\n url: {\n type: 'string',\n description: 'The URL to be processed by the web connector.'\n },\n exclude_urls: {\n type: 'array',\n description: 'An array of URLs to exclude from processing within this web connector.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'status',\n 'type',\n 'url'\n ]\n }\n }\n },\n required: [ 'id',\n 'created_at',\n 'file_status',\n 'name',\n 'type'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - after: { - type: 'string', - description: - 'The ID of the last object in the previous page. This parameter instructs the API to return the next page of results.', - }, - before: { - type: 'string', - description: - 'The ID of the first object in the previous page. This parameter instructs the API to return the previous page of results.', - }, - limit: { - type: 'integer', - description: - 'Specifies the maximum number of objects returned in a page. The default value is 50. The minimum value is 1, and the maximum value is 100.', - }, - order: { - type: 'string', - description: - 'Specifies the order of the results. Valid values are asc for ascending and desc for descending.', - enum: ['asc', 'desc'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - const response = await client.graphs.list(body).asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/question-graphs.ts b/packages/mcp-server/src/tools/graphs/question-graphs.ts deleted file mode 100644 index b8b11c61..00000000 --- a/packages/mcp-server/src/tools/graphs/question-graphs.ts +++ /dev/null @@ -1,184 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'graphs', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/graphs/question', - operationId: 'question', -}; - -export const tool: Tool = { - name: 'question_graphs', - description: - 'Ask questions and get AI-generated answers based on your Knowledge Graph content. Queries your uploaded documents, PDFs, and files to retrieve accurate, source-cited information. Returns answers with supporting snippets and file references. Ideal for RAG (Retrieval-Augmented Generation) applications and knowledge base queries.', - inputSchema: { - type: 'object', - anyOf: [ - { - type: 'object', - properties: { - graph_ids: { - type: 'array', - description: 'The unique identifiers of the Knowledge Graphs to query.', - items: { - type: 'string', - }, - }, - question: { - type: 'string', - description: 'The question to answer using the Knowledge Graph.', - }, - query_config: { - type: 'object', - title: 'graph_query_config', - description: - 'Configuration options for Knowledge Graph queries, including search parameters and citation settings.', - properties: { - grounding_level: { - type: 'number', - description: - 'Level of grounding required for responses, controlling how closely answers must be tied to source material. Set lower for grounded outputs, higher for creativity. Higher values (closer to 1.0) allow more creative interpretation, while lower values (closer to 0.0) stick more closely to source material. Range: 0.0-1.0, Default: 0.0.', - }, - inline_citations: { - type: 'boolean', - description: - 'Whether to include inline citations in the response, showing which Knowledge Graph sources were used. Default: false.', - }, - keyword_threshold: { - type: 'number', - description: - 'Threshold for keyword-based matching when searching Knowledge Graph content. Set higher for stricter relevance, lower for broader range. Higher values (closer to 1.0) require stronger keyword matches, while lower values (closer to 0.0) allow more lenient matching. Range: 0.0-1.0, Default: 0.7.', - }, - max_snippets: { - type: 'integer', - description: - 'Maximum number of text snippets to retrieve from the Knowledge Graph for context. Works in concert with `search_weight` to control best matches vs broader coverage. While technically supports 1-60, values below 5 may return no results due to RAG implementation. Recommended range: 5-25. Due to RAG system behavior, you may see more snippets than requested. Range: 1-60, Default: 30.', - }, - max_subquestions: { - type: 'integer', - description: - 'Maximum number of subquestions to generate when processing complex queries. Set higher to improve detail, set lower to reduce response time. Range: 1-10, Default: 6.', - }, - max_tokens: { - type: 'integer', - description: - "Maximum number of tokens the model can generate in the response. This controls the length of the AI's answer. Set higher for longer answers, set lower for shorter, faster answers. Range: 100-8000, Default: 4000.", - }, - search_weight: { - type: 'integer', - description: - 'Weight given to search results when ranking and selecting relevant information. Higher values (closer to 100) prioritize keyword-based matching, while lower values (closer to 0) prioritize semantic similarity matching. Use higher values for exact keyword searches, lower values for conceptual similarity searches. Range: 0-100, Default: 50.', - }, - semantic_threshold: { - type: 'number', - description: - 'Threshold for semantic similarity matching when searching Knowledge Graph content. Set higher for stricter relevance, lower for broader range. Higher values (closer to 1.0) require stronger semantic similarity, while lower values (closer to 0.0) allow more lenient semantic matching. Range: 0.0-1.0, Default: 0.7.', - }, - }, - }, - stream: { - type: 'string', - description: - "Determines whether the model's output should be streamed. If true, the output is generated and sent incrementally, which can be useful for real-time applications.", - enum: [false], - }, - subqueries: { - type: 'boolean', - description: 'Specify whether to include subqueries.', - }, - }, - required: ['graph_ids', 'question'], - }, - { - type: 'object', - properties: { - graph_ids: { - type: 'array', - description: 'The unique identifiers of the Knowledge Graphs to query.', - items: { - type: 'string', - }, - }, - question: { - type: 'string', - description: 'The question to answer using the Knowledge Graph.', - }, - stream: { - type: 'string', - description: - "Determines whether the model's output should be streamed. If true, the output is generated and sent incrementally, which can be useful for real-time applications.", - enum: [true], - }, - query_config: { - type: 'object', - title: 'graph_query_config', - description: - 'Configuration options for Knowledge Graph queries, including search parameters and citation settings.', - properties: { - grounding_level: { - type: 'number', - description: - 'Level of grounding required for responses, controlling how closely answers must be tied to source material. Set lower for grounded outputs, higher for creativity. Higher values (closer to 1.0) allow more creative interpretation, while lower values (closer to 0.0) stick more closely to source material. Range: 0.0-1.0, Default: 0.0.', - }, - inline_citations: { - type: 'boolean', - description: - 'Whether to include inline citations in the response, showing which Knowledge Graph sources were used. Default: false.', - }, - keyword_threshold: { - type: 'number', - description: - 'Threshold for keyword-based matching when searching Knowledge Graph content. Set higher for stricter relevance, lower for broader range. Higher values (closer to 1.0) require stronger keyword matches, while lower values (closer to 0.0) allow more lenient matching. Range: 0.0-1.0, Default: 0.7.', - }, - max_snippets: { - type: 'integer', - description: - 'Maximum number of text snippets to retrieve from the Knowledge Graph for context. Works in concert with `search_weight` to control best matches vs broader coverage. While technically supports 1-60, values below 5 may return no results due to RAG implementation. Recommended range: 5-25. Due to RAG system behavior, you may see more snippets than requested. Range: 1-60, Default: 30.', - }, - max_subquestions: { - type: 'integer', - description: - 'Maximum number of subquestions to generate when processing complex queries. Set higher to improve detail, set lower to reduce response time. Range: 1-10, Default: 6.', - }, - max_tokens: { - type: 'integer', - description: - "Maximum number of tokens the model can generate in the response. This controls the length of the AI's answer. Set higher for longer answers, set lower for shorter, faster answers. Range: 100-8000, Default: 4000.", - }, - search_weight: { - type: 'integer', - description: - 'Weight given to search results when ranking and selecting relevant information. Higher values (closer to 100) prioritize keyword-based matching, while lower values (closer to 0) prioritize semantic similarity matching. Use higher values for exact keyword searches, lower values for conceptual similarity searches. Range: 0-100, Default: 50.', - }, - semantic_threshold: { - type: 'number', - description: - 'Threshold for semantic similarity matching when searching Knowledge Graph content. Set higher for stricter relevance, lower for broader range. Higher values (closer to 1.0) require stronger semantic similarity, while lower values (closer to 0.0) allow more lenient semantic matching. Range: 0.0-1.0, Default: 0.7.', - }, - }, - }, - subqueries: { - type: 'boolean', - description: 'Specify whether to include subqueries.', - }, - }, - required: ['graph_ids', 'question', 'stream'], - }, - ], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const body = args as any; - return asTextContentResult(await client.graphs.question(body)); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/remove-file-from-graph-graphs.ts b/packages/mcp-server/src/tools/graphs/remove-file-from-graph-graphs.ts deleted file mode 100644 index 6a7ed9cd..00000000 --- a/packages/mcp-server/src/tools/graphs/remove-file-from-graph-graphs.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'graphs', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/graphs/{graph_id}/file/{file_id}', - operationId: 'removeFileFromGraph', -}; - -export const tool: Tool = { - name: 'remove_file_from_graph_graphs', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRemove a file from a Knowledge Graph.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/graph_remove_file_from_graph_response',\n $defs: {\n graph_remove_file_from_graph_response: {\n type: 'object',\n title: 'delete_file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the deleted file.'\n },\n deleted: {\n type: 'boolean',\n description: 'Indicates whether the file was successfully deleted.'\n }\n },\n required: [ 'id',\n 'deleted'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - graph_id: { - type: 'string', - }, - file_id: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['graph_id', 'file_id'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { file_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.graphs.removeFileFromGraph(file_id, body)), - ); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/retrieve-graphs.ts b/packages/mcp-server/src/tools/graphs/retrieve-graphs.ts deleted file mode 100644 index a9641fe2..00000000 --- a/packages/mcp-server/src/tools/graphs/retrieve-graphs.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'graphs', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/graphs/{graph_id}', - operationId: 'findGraphWithFileStatus', -}; - -export const tool: Tool = { - name: 'retrieve_graphs', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet detailed information about a specific Knowledge Graph by its ID. Returns the graph name, description, creation date, file processing status, and associated URLs (for web-based graphs). Use this to check processing status or get graph metadata.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/graph',\n $defs: {\n graph: {\n type: 'object',\n title: 'graph',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier of the Knowledge Graph.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the Knowledge Graph was created.',\n format: 'date-time'\n },\n file_status: {\n type: 'object',\n title: 'graph_file_status',\n description: 'The processing status of files in the Knowledge Graph.',\n properties: {\n completed: {\n type: 'integer',\n description: 'The number of files that have been successfully processed.'\n },\n failed: {\n type: 'integer',\n description: 'The number of files that failed to process.'\n },\n in_progress: {\n type: 'integer',\n description: 'The number of files currently being processed.'\n },\n total: {\n type: 'integer',\n description: 'The total number of files associated with the Knowledge Graph.'\n }\n },\n required: [ 'completed',\n 'failed',\n 'in_progress',\n 'total'\n ]\n },\n name: {\n type: 'string',\n description: 'The name of the Knowledge Graph.'\n },\n type: {\n type: 'string',\n title: 'graph_type',\n description: 'The type of Knowledge Graph.\\n\\n- `manual`: files are uploaded via UI or API\\n- `connector`: files are uploaded via a data connector such as Google Drive or Confluence\\n- `web`: URLs are connected to the Knowledge Graph',\n enum: [ 'manual',\n 'connector',\n 'web'\n ]\n },\n description: {\n type: 'string',\n description: 'A description of the Knowledge Graph.'\n },\n urls: {\n type: 'array',\n description: 'An array of web connector URLs associated with this Knowledge Graph.',\n items: {\n type: 'object',\n title: 'web_connector_url',\n properties: {\n status: {\n type: 'object',\n title: 'web_connector_url_state',\n description: 'The current status of the URL processing.',\n properties: {\n status: {\n type: 'string',\n title: 'web_connector_url_status',\n description: 'The current status of the URL processing.',\n enum: [ 'validating',\n 'success',\n 'error'\n ]\n },\n error_type: {\n type: 'string',\n title: 'web_connector_url_error_type',\n description: 'The type of error that occurred during processing, if any.',\n enum: [ 'invalid_url',\n 'not_searchable',\n 'not_found',\n 'paywall_or_login_page',\n 'unexpected_error'\n ]\n }\n },\n required: [ 'status'\n ]\n },\n type: {\n type: 'string',\n title: 'web_connector_url_type',\n description: 'The type of web connector processing for this URL.',\n enum: [ 'single_page',\n 'sub_pages'\n ]\n },\n url: {\n type: 'string',\n description: 'The URL to be processed by the web connector.'\n },\n exclude_urls: {\n type: 'array',\n description: 'An array of URLs to exclude from processing within this web connector.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'status',\n 'type',\n 'url'\n ]\n }\n }\n },\n required: [ 'id',\n 'created_at',\n 'file_status',\n 'name',\n 'type'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - graph_id: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['graph_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { graph_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.graphs.retrieve(graph_id))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/update-graphs.ts b/packages/mcp-server/src/tools/graphs/update-graphs.ts deleted file mode 100644 index 52c02654..00000000 --- a/packages/mcp-server/src/tools/graphs/update-graphs.ts +++ /dev/null @@ -1,93 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'graphs', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/v1/graphs/{graph_id}', - operationId: 'updateGraph', -}; - -export const tool: Tool = { - name: 'update_graphs', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nUpdate the name and description of a Knowledge Graph.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/graph_update_response',\n $defs: {\n graph_update_response: {\n type: 'object',\n title: 'graph_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the Knowledge Graph.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the Knowledge Graph was created.',\n format: 'date-time'\n },\n name: {\n type: 'string',\n description: 'The name of the Knowledge Graph (max 255 characters).'\n },\n description: {\n type: 'string',\n description: 'A description of the Knowledge Graph (max 255 characters).'\n },\n urls: {\n type: 'array',\n description: 'An array of web connector URLs associated with this Knowledge Graph.',\n items: {\n type: 'object',\n title: 'web_connector_url',\n properties: {\n status: {\n type: 'object',\n title: 'web_connector_url_state',\n description: 'The current status of the URL processing.',\n properties: {\n status: {\n type: 'string',\n title: 'web_connector_url_status',\n description: 'The current status of the URL processing.',\n enum: [ 'validating',\n 'success',\n 'error'\n ]\n },\n error_type: {\n type: 'string',\n title: 'web_connector_url_error_type',\n description: 'The type of error that occurred during processing, if any.',\n enum: [ 'invalid_url',\n 'not_searchable',\n 'not_found',\n 'paywall_or_login_page',\n 'unexpected_error'\n ]\n }\n },\n required: [ 'status'\n ]\n },\n type: {\n type: 'string',\n title: 'web_connector_url_type',\n description: 'The type of web connector processing for this URL.',\n enum: [ 'single_page',\n 'sub_pages'\n ]\n },\n url: {\n type: 'string',\n description: 'The URL to be processed by the web connector.'\n },\n exclude_urls: {\n type: 'array',\n description: 'An array of URLs to exclude from processing within this web connector.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'status',\n 'type',\n 'url'\n ]\n }\n }\n },\n required: [ 'id',\n 'created_at',\n 'name'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - graph_id: { - type: 'string', - }, - description: { - type: 'string', - description: - 'A description of the Knowledge Graph (max 255 characters). Omitting this field leaves the description unchanged.', - }, - name: { - type: 'string', - description: - 'The name of the Knowledge Graph (max 255 characters). Omitting this field leaves the name unchanged.', - }, - urls: { - type: 'array', - description: - 'An array of web connector URLs to update for this Knowledge Graph. You can only connect URLs to Knowledge Graphs with the type `web`. To clear the list of URLs, set this field to an empty array.', - items: { - type: 'object', - title: 'update_graph_web_url', - properties: { - type: { - type: 'string', - title: 'web_connector_url_type', - description: 'The type of web connector processing for this URL.', - enum: ['single_page', 'sub_pages'], - }, - url: { - type: 'string', - description: 'The URL to be processed by the web connector.', - }, - exclude_urls: { - type: 'array', - description: 'An array of URLs to exclude from processing within this web connector.', - items: { - type: 'string', - }, - }, - }, - required: ['type', 'url'], - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['graph_id'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { graph_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.graphs.update(graph_id, body))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts deleted file mode 100644 index 2900d86e..00000000 --- a/packages/mcp-server/src/tools/index.ts +++ /dev/null @@ -1,135 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, Endpoint, HandlerFunction } from './types'; - -export { Metadata, Endpoint, HandlerFunction }; - -import retrieve_applications from './applications/retrieve-applications'; -import list_applications from './applications/list-applications'; -import generate_content_applications from './applications/generate-content-applications'; -import create_applications_jobs from './applications/jobs/create-applications-jobs'; -import retrieve_applications_jobs from './applications/jobs/retrieve-applications-jobs'; -import list_applications_jobs from './applications/jobs/list-applications-jobs'; -import retry_applications_jobs from './applications/jobs/retry-applications-jobs'; -import update_applications_graphs from './applications/graphs/update-applications-graphs'; -import list_applications_graphs from './applications/graphs/list-applications-graphs'; -import chat_chat from './chat/chat-chat'; -import create_completions from './completions/create-completions'; -import list_models from './models/list-models'; -import create_graphs from './graphs/create-graphs'; -import retrieve_graphs from './graphs/retrieve-graphs'; -import update_graphs from './graphs/update-graphs'; -import list_graphs from './graphs/list-graphs'; -import delete_graphs from './graphs/delete-graphs'; -import add_file_to_graph_graphs from './graphs/add-file-to-graph-graphs'; -import question_graphs from './graphs/question-graphs'; -import remove_file_from_graph_graphs from './graphs/remove-file-from-graph-graphs'; -import retrieve_files from './files/retrieve-files'; -import list_files from './files/list-files'; -import delete_files from './files/delete-files'; -import download_files from './files/download-files'; -import retry_files from './files/retry-files'; -import upload_files from './files/upload-files'; -import ai_detect_tools from './tools/ai-detect-tools'; -import context_aware_splitting_tools from './tools/context-aware-splitting-tools'; -import parse_pdf_tools from './tools/parse-pdf-tools'; -import web_search_tools from './tools/web-search-tools'; -import medical_tools_comprehend from './tools/comprehend/medical-tools-comprehend'; -import translate_translation from './translation/translate-translation'; -import analyze_vision from './vision/analyze-vision'; - -export const endpoints: Endpoint[] = []; - -function addEndpoint(endpoint: Endpoint) { - endpoints.push(endpoint); -} - -addEndpoint(retrieve_applications); -addEndpoint(list_applications); -addEndpoint(generate_content_applications); -addEndpoint(create_applications_jobs); -addEndpoint(retrieve_applications_jobs); -addEndpoint(list_applications_jobs); -addEndpoint(retry_applications_jobs); -addEndpoint(update_applications_graphs); -addEndpoint(list_applications_graphs); -addEndpoint(chat_chat); -addEndpoint(create_completions); -addEndpoint(list_models); -addEndpoint(create_graphs); -addEndpoint(retrieve_graphs); -addEndpoint(update_graphs); -addEndpoint(list_graphs); -addEndpoint(delete_graphs); -addEndpoint(add_file_to_graph_graphs); -addEndpoint(question_graphs); -addEndpoint(remove_file_from_graph_graphs); -addEndpoint(retrieve_files); -addEndpoint(list_files); -addEndpoint(delete_files); -addEndpoint(download_files); -addEndpoint(retry_files); -addEndpoint(upload_files); -addEndpoint(ai_detect_tools); -addEndpoint(context_aware_splitting_tools); -addEndpoint(parse_pdf_tools); -addEndpoint(web_search_tools); -addEndpoint(medical_tools_comprehend); -addEndpoint(translate_translation); -addEndpoint(analyze_vision); - -export type Filter = { - type: 'resource' | 'operation' | 'tag' | 'tool'; - op: 'include' | 'exclude'; - value: string; -}; - -export function query(filters: Filter[], endpoints: Endpoint[]): Endpoint[] { - const allExcludes = filters.length > 0 && filters.every((filter) => filter.op === 'exclude'); - const unmatchedFilters = new Set(filters); - - const filtered = endpoints.filter((endpoint: Endpoint) => { - let included = false || allExcludes; - - for (const filter of filters) { - if (match(filter, endpoint)) { - unmatchedFilters.delete(filter); - included = filter.op === 'include'; - } - } - - return included; - }); - - // Check if any filters didn't match - const unmatched = Array.from(unmatchedFilters).filter((f) => f.type === 'tool' || f.type === 'resource'); - if (unmatched.length > 0) { - throw new Error( - `The following filters did not match any endpoints: ${unmatched - .map((f) => `${f.type}=${f.value}`) - .join(', ')}`, - ); - } - - return filtered; -} - -function match({ type, value }: Filter, endpoint: Endpoint): boolean { - switch (type) { - case 'resource': { - const regexStr = '^' + normalizeResource(value).replace(/\*/g, '.*') + '$'; - const regex = new RegExp(regexStr); - return regex.test(normalizeResource(endpoint.metadata.resource)); - } - case 'operation': - return endpoint.metadata.operation === value; - case 'tag': - return endpoint.metadata.tags.includes(value); - case 'tool': - return endpoint.tool.name === value; - } -} - -function normalizeResource(resource: string): string { - return resource.toLowerCase().replace(/[^a-z.*\-_]*/g, ''); -} diff --git a/packages/mcp-server/src/tools/models/list-models.ts b/packages/mcp-server/src/tools/models/list-models.ts deleted file mode 100644 index 50163670..00000000 --- a/packages/mcp-server/src/tools/models/list-models.ts +++ /dev/null @@ -1,51 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'models', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/models', - operationId: 'models', -}; - -export const tool: Tool = { - name: 'list_models', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRetrieve a list of available models that can be used for text generation, chat completions, and other AI tasks.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/model_list_response',\n $defs: {\n model_list_response: {\n type: 'object',\n properties: {\n models: {\n type: 'array',\n description: 'The [ID of the model](https://dev.writer.com/home/models) to use for processing the request.',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The ID of the particular LLM that you want to use'\n },\n name: {\n type: 'string',\n description: 'The name of the particular LLM that you want to use.'\n }\n },\n required: [ 'id',\n 'name'\n ]\n }\n }\n },\n required: [ 'models'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.models.list())); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tools/ai-detect-tools.ts b/packages/mcp-server/src/tools/tools/ai-detect-tools.ts deleted file mode 100644 index d64dfbc7..00000000 --- a/packages/mcp-server/src/tools/tools/ai-detect-tools.ts +++ /dev/null @@ -1,53 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'tools', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/tools/ai-detect', -}; - -export const tool: Tool = { - name: 'ai_detect_tools', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nDetects if content is AI- or human-generated, with a confidence score. Content must have at least 350 characters\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/tool_ai_detect_response',\n $defs: {\n tool_ai_detect_response: {\n type: 'object',\n title: 'ai_detection_response',\n properties: {\n label: {\n type: 'string',\n enum: [ 'fake',\n 'real'\n ]\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'label',\n 'score'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - input: { - type: 'string', - description: - 'The content to determine if it is AI- or human-generated. Content must have at least 350 characters.', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['input'], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.tools.aiDetect(body))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tools/comprehend/medical-tools-comprehend.ts b/packages/mcp-server/src/tools/tools/comprehend/medical-tools-comprehend.ts deleted file mode 100644 index b5fe3146..00000000 --- a/packages/mcp-server/src/tools/tools/comprehend/medical-tools-comprehend.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'tools.comprehend', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/tools/comprehend/medical', -}; - -export const tool: Tool = { - name: 'medical_tools_comprehend', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nAnalyze unstructured medical text to extract entities labeled with standardized medical codes and confidence scores.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/comprehend_medical_response',\n $defs: {\n comprehend_medical_response: {\n type: 'object',\n title: 'medical_comprehend_response',\n properties: {\n entities: {\n type: 'array',\n description: 'An array of medical entities extracted from the input text.',\n items: {\n type: 'object',\n title: 'medical_comprehend_entity',\n properties: {\n attributes: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_attribute',\n properties: {\n begin_offset: {\n type: 'integer'\n },\n concepts: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_concept',\n properties: {\n code: {\n type: 'string'\n },\n description: {\n type: 'string'\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'code',\n 'description',\n 'score'\n ]\n }\n },\n end_offset: {\n type: 'integer'\n },\n relationship_score: {\n type: 'number'\n },\n score: {\n type: 'number'\n },\n text: {\n type: 'string'\n },\n traits: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_trait',\n properties: {\n name: {\n type: 'string'\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'name',\n 'score'\n ]\n }\n },\n type: {\n type: 'string'\n },\n category: {\n type: 'string'\n },\n relationship_type: {\n type: 'string'\n }\n },\n required: [ 'begin_offset',\n 'concepts',\n 'end_offset',\n 'relationship_score',\n 'score',\n 'text',\n 'traits',\n 'type'\n ]\n }\n },\n begin_offset: {\n type: 'integer'\n },\n category: {\n type: 'string'\n },\n concepts: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_concept',\n properties: {\n code: {\n type: 'string'\n },\n description: {\n type: 'string'\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'code',\n 'description',\n 'score'\n ]\n }\n },\n end_offset: {\n type: 'integer'\n },\n score: {\n type: 'number'\n },\n text: {\n type: 'string'\n },\n traits: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_trait',\n properties: {\n name: {\n type: 'string'\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'name',\n 'score'\n ]\n }\n },\n type: {\n type: 'string'\n }\n },\n required: [ 'attributes',\n 'begin_offset',\n 'category',\n 'concepts',\n 'end_offset',\n 'score',\n 'text',\n 'traits',\n 'type'\n ]\n }\n }\n },\n required: [ 'entities'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - content: { - type: 'string', - description: 'The text to analyze.', - }, - response_type: { - type: 'string', - title: 'comprehend_medical_type', - description: - 'The structure of the response to return. `Entities` returns medical entities, `RxNorm` returns medication information, `ICD-10-CM` returns diagnosis codes, and `SNOMED CT` returns medical concepts.', - enum: ['Entities', 'RxNorm', 'ICD-10-CM', 'SNOMED CT'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['content', 'response_type'], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.tools.comprehend.medical(body))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tools/context-aware-splitting-tools.ts b/packages/mcp-server/src/tools/tools/context-aware-splitting-tools.ts deleted file mode 100644 index 18c7cc96..00000000 --- a/packages/mcp-server/src/tools/tools/context-aware-splitting-tools.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'tools', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/tools/context-aware-splitting', -}; - -export const tool: Tool = { - name: 'context_aware_splitting_tools', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nSplits a long block of text (maximum 4000 words) into smaller chunks while preserving the semantic meaning of the text and context between the chunks.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/tool_context_aware_splitting_response',\n $defs: {\n tool_context_aware_splitting_response: {\n type: 'object',\n title: 'context_aware_splitting_response',\n properties: {\n chunks: {\n type: 'array',\n description: 'An array of text chunks generated by splitting the input text based on the specified strategy.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'chunks'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - strategy: { - type: 'string', - title: 'splitting_strategy', - description: - 'The strategy to use for splitting the text into chunks. `llm_split` uses the language model to split the text, `fast_split` uses a fast heuristic-based approach, and `hybrid_split` combines both strategies.', - enum: ['llm_split', 'fast_split', 'hybrid_split'], - }, - text: { - type: 'string', - description: 'The text to split into chunks.', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['strategy', 'text'], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.tools.contextAwareSplitting(body))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tools/parse-pdf-tools.ts b/packages/mcp-server/src/tools/tools/parse-pdf-tools.ts deleted file mode 100644 index a23ac88d..00000000 --- a/packages/mcp-server/src/tools/tools/parse-pdf-tools.ts +++ /dev/null @@ -1,57 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'tools', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/tools/pdf-parser/{file_id}', -}; - -export const tool: Tool = { - name: 'parse_pdf_tools', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nParse PDF to other formats.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/tool_parse_pdf_response',\n $defs: {\n tool_parse_pdf_response: {\n type: 'object',\n title: 'parse_pdf_response',\n properties: {\n content: {\n type: 'string',\n description: 'The extracted content from the PDF file, converted to the specified format.'\n }\n },\n required: [ 'content'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - file_id: { - type: 'string', - }, - format: { - type: 'string', - title: 'pdf_conversion_format', - description: 'The format into which the PDF content should be converted.', - enum: ['text', 'markdown'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['file_id', 'format'], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { file_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.tools.parsePdf(file_id, body))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tools/web-search-tools.ts b/packages/mcp-server/src/tools/tools/web-search-tools.ts deleted file mode 100644 index d8ae2a66..00000000 --- a/packages/mcp-server/src/tools/tools/web-search-tools.ts +++ /dev/null @@ -1,292 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'tools', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/tools/web-search', -}; - -export const tool: Tool = { - name: 'web_search_tools', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nSearch the web for information about a given query and return relevant results with source URLs.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/tool_web_search_response',\n $defs: {\n tool_web_search_response: {\n type: 'object',\n title: 'web_search_response',\n properties: {\n query: {\n type: 'string',\n description: 'The search query that was submitted.'\n },\n sources: {\n type: 'array',\n description: 'The search results found.',\n items: {\n type: 'object',\n properties: {\n raw_content: {\n type: 'string',\n description: 'Raw content from the source URL. Not included if `include_raw_content` is `false`.'\n },\n url: {\n type: 'string',\n description: 'URL of the search result.'\n }\n }\n }\n },\n answer: {\n type: 'string',\n description: 'Generated answer based on the search results. Not included if `include_answer` is `false`.'\n }\n },\n required: [ 'query',\n 'sources'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - chunks_per_source: { - type: 'integer', - description: - 'Only applies when `search_depth` is `advanced`. Specifies how many text segments to extract from each source. Limited to 3 chunks maximum.', - }, - country: { - type: 'string', - description: - 'Localizes search results to a specific country. Only applies to general topic searches.', - enum: [ - 'afghanistan', - 'albania', - 'algeria', - 'andorra', - 'angola', - 'argentina', - 'armenia', - 'australia', - 'austria', - 'azerbaijan', - 'bahamas', - 'bahrain', - 'bangladesh', - 'barbados', - 'belarus', - 'belgium', - 'belize', - 'benin', - 'bhutan', - 'bolivia', - 'bosnia and herzegovina', - 'botswana', - 'brazil', - 'brunei', - 'bulgaria', - 'burkina faso', - 'burundi', - 'cambodia', - 'cameroon', - 'canada', - 'cape verde', - 'central african republic', - 'chad', - 'chile', - 'china', - 'colombia', - 'comoros', - 'congo', - 'costa rica', - 'croatia', - 'cuba', - 'cyprus', - 'czech republic', - 'denmark', - 'djibouti', - 'dominican republic', - 'ecuador', - 'egypt', - 'el salvador', - 'equatorial guinea', - 'eritrea', - 'estonia', - 'ethiopia', - 'fiji', - 'finland', - 'france', - 'gabon', - 'gambia', - 'georgia', - 'germany', - 'ghana', - 'greece', - 'guatemala', - 'guinea', - 'haiti', - 'honduras', - 'hungary', - 'iceland', - 'india', - 'indonesia', - 'iran', - 'iraq', - 'ireland', - 'israel', - 'italy', - 'jamaica', - 'japan', - 'jordan', - 'kazakhstan', - 'kenya', - 'kuwait', - 'kyrgyzstan', - 'latvia', - 'lebanon', - 'lesotho', - 'liberia', - 'libya', - 'liechtenstein', - 'lithuania', - 'luxembourg', - 'madagascar', - 'malawi', - 'malaysia', - 'maldives', - 'mali', - 'malta', - 'mauritania', - 'mauritius', - 'mexico', - 'moldova', - 'monaco', - 'mongolia', - 'montenegro', - 'morocco', - 'mozambique', - 'myanmar', - 'namibia', - 'nepal', - 'netherlands', - 'new zealand', - 'nicaragua', - 'niger', - 'nigeria', - 'north korea', - 'north macedonia', - 'norway', - 'oman', - 'pakistan', - 'panama', - 'papua new guinea', - 'paraguay', - 'peru', - 'philippines', - 'poland', - 'portugal', - 'qatar', - 'romania', - 'russia', - 'rwanda', - 'saudi arabia', - 'senegal', - 'serbia', - 'singapore', - 'slovakia', - 'slovenia', - 'somalia', - 'south africa', - 'south korea', - 'south sudan', - 'spain', - 'sri lanka', - 'sudan', - 'sweden', - 'switzerland', - 'syria', - 'taiwan', - 'tajikistan', - 'tanzania', - 'thailand', - 'togo', - 'trinidad and tobago', - 'tunisia', - 'turkey', - 'turkmenistan', - 'uganda', - 'ukraine', - 'united arab emirates', - 'united kingdom', - 'united states', - 'uruguay', - 'uzbekistan', - 'venezuela', - 'vietnam', - 'yemen', - 'zambia', - 'zimbabwe', - ], - }, - days: { - type: 'integer', - description: 'For news topic searches, specifies how many days of news coverage to include.', - }, - exclude_domains: { - type: 'array', - description: 'Domains to exclude from the search. If unset, the search includes all domains.', - items: { - type: 'string', - }, - }, - include_answer: { - type: 'boolean', - description: - 'Whether to include a generated answer to the query in the response. If `false`, only search results are returned.', - }, - include_domains: { - type: 'array', - description: 'Domains to include in the search. If unset, the search includes all domains.', - items: { - type: 'string', - }, - }, - include_raw_content: { - anyOf: [ - { - type: 'string', - enum: ['text', 'markdown'], - }, - { - type: 'boolean', - }, - ], - description: - 'Controls how raw content is included in search results:\n\n- `text`: Returns plain text without formatting markup\n- `markdown`: Returns structured content with markdown formatting (headers, links, bold text)\n- `true`: Same as `markdown`\n- `false`: Raw content is not included (default if unset)', - }, - max_results: { - type: 'integer', - description: 'Limits the number of search results returned. Cannot exceed 20 sources.', - }, - query: { - type: 'string', - description: 'The search query.', - }, - search_depth: { - type: 'string', - description: - 'Controls search comprehensiveness:\n\n- `basic`: Returns fewer but highly relevant results\n- `advanced`: Performs a deeper search with more results', - enum: ['basic', 'advanced'], - }, - stream: { - type: 'boolean', - description: 'Enables streaming of search results as they become available.', - }, - time_range: { - type: 'string', - description: - 'Filters results to content published within the specified time range back from the current date. For example, `week` or `w` returns results from the past 7 days.', - enum: ['day', 'week', 'month', 'year', 'd', 'w', 'm', 'y'], - }, - topic: { - type: 'string', - description: - 'The search topic category. Use `news` for current events and news articles, or `general` for broader web search.', - enum: ['general', 'news'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.tools.webSearch(body))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/translation/translate-translation.ts b/packages/mcp-server/src/tools/translation/translate-translation.ts deleted file mode 100644 index 353055c8..00000000 --- a/packages/mcp-server/src/tools/translation/translate-translation.ts +++ /dev/null @@ -1,90 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'translation', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/translation', -}; - -export const tool: Tool = { - name: 'translate_translation', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nTranslate text from one language to another.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/translation_response',\n $defs: {\n translation_response: {\n type: 'object',\n title: 'Translation Response',\n properties: {\n data: {\n type: 'string',\n description: 'The result of the translation.'\n }\n },\n required: [ 'data'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - formality: { - type: 'boolean', - description: - 'Whether to use formal or informal language in the translation. See the [list of languages that support formality](https://dev.writer.com/api-reference/translation-api/language-support#formality). If the language does not support formality, this parameter is ignored.', - }, - length_control: { - type: 'boolean', - description: - 'Whether to control the length of the translated text. See the [list of languages that support length control](https://dev.writer.com/api-reference/translation-api/language-support#length-control). If the language does not support length control, this parameter is ignored.', - }, - mask_profanity: { - type: 'boolean', - description: - 'Whether to mask profane words in the translated text. See the [list of languages that do not support profanity masking](https://dev.writer.com/api-reference/translation-api/language-support#profanity-masking). If the language does not support profanity masking, this parameter is ignored.', - }, - model: { - type: 'string', - description: 'The model to use for translation.', - enum: ['palmyra-translate'], - }, - source_language_code: { - type: 'string', - description: - 'The [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) language code of the original text to translate. For example, `en` for English, `zh` for Chinese, `fr` for French, `es` for Spanish. If the language has a variant, the code appends the two-digit [ISO-3166 country code](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes). For example, Mexican Spanish is `es-MX`. See the [list of supported languages and language codes](https://dev.writer.com/api-reference/translation-api/language-support).', - }, - target_language_code: { - type: 'string', - description: - 'The [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) language code of the target language for the translation. For example, `en` for English, `zh` for Chinese, `fr` for French, `es` for Spanish. If the language has a variant, the code appends the two-digit [ISO-3166 country code](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes). For example, Mexican Spanish is `es-MX`. See the [list of supported languages and language codes](https://dev.writer.com/api-reference/translation-api/language-support).', - }, - text: { - type: 'string', - description: 'The text to translate. Maximum of 100,000 words.', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [ - 'formality', - 'length_control', - 'mask_profanity', - 'model', - 'source_language_code', - 'target_language_code', - 'text', - ], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.translation.translate(body))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/vision/analyze-vision.ts b/packages/mcp-server/src/tools/vision/analyze-vision.ts deleted file mode 100644 index 73f65385..00000000 --- a/packages/mcp-server/src/tools/vision/analyze-vision.ts +++ /dev/null @@ -1,80 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Writer from 'writer-sdk'; - -export const metadata: Metadata = { - resource: 'vision', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/vision', -}; - -export const tool: Tool = { - name: 'analyze_vision', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nSubmit images and documents with a prompt to generate an analysis. Supports JPG, PNG, PDF, and TXT files up to 7MB each.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/vision_response',\n $defs: {\n vision_response: {\n type: 'object',\n title: 'Vision Response',\n properties: {\n data: {\n type: 'string',\n description: 'The result of the image analysis.'\n }\n },\n required: [ 'data'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - model: { - type: 'string', - description: 'The model to use for image analysis.', - enum: ['palmyra-vision'], - }, - prompt: { - type: 'string', - description: - 'The prompt to use for the image analysis. The prompt must include the name of each image variable, surrounded by double curly braces (`{{}}`). For example, `Describe the difference between the image {{image_1}} and the image {{image_2}}`.', - }, - variables: { - type: 'array', - items: { - type: 'object', - title: 'Vision Request File Variable', - description: - 'An array of file variables required for the analysis. The files must be uploaded to the Writer platform before they can be used in a vision request. Learn how to upload files using the [Files API](https://dev.writer.com/api-reference/file-api/upload-files).\n\nSupported file types: JPG, PNG, PDF, TXT. The maximum allowed file size for each file is 7MB.', - properties: { - file_id: { - type: 'string', - description: - 'The File ID of the file to analyze. The file must be uploaded to the Writer platform before it can be used in a vision request. Supported file types: JPG, PNG, PDF, TXT (max 7MB each).', - }, - name: { - type: 'string', - description: - 'The name of the file variable. You must reference this name in the prompt with double curly braces (`{{}}`). For example, `Describe the difference between the image {{image_1}} and the image {{image_2}}`.', - }, - }, - required: ['file_id', 'name'], - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['model', 'prompt', 'variables'], - }, - annotations: {}, -}; - -export const handler = async (client: Writer, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.vision.analyze(body))); - } catch (error) { - if (isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/types.ts b/packages/mcp-server/src/types.ts similarity index 83% rename from packages/mcp-server/src/tools/types.ts rename to packages/mcp-server/src/types.ts index 9993753a..2c730de7 100644 --- a/packages/mcp-server/src/tools/types.ts +++ b/packages/mcp-server/src/types.ts @@ -42,10 +42,21 @@ export type ToolCallResult = { isError?: boolean; }; -export type HandlerFunction = ( - client: Writer, - args: Record | undefined, -) => Promise; +export type McpRequestContext = { + client: Writer; + stainlessApiKey?: string | undefined; + upstreamClientEnvs?: Record | undefined; + mcpSessionId?: string | undefined; + mcpClientInfo?: { name: string; version: string } | undefined; +}; + +export type HandlerFunction = ({ + reqContext, + args, +}: { + reqContext: McpRequestContext; + args: Record | undefined; +}) => Promise; export function asTextContentResult(result: unknown): ToolCallResult { return { @@ -108,7 +119,7 @@ export type Metadata = { operationId?: string; }; -export type Endpoint = { +export type McpTool = { metadata: Metadata; tool: Tool; handler: HandlerFunction; diff --git a/packages/mcp-server/src/util.ts b/packages/mcp-server/src/util.ts new file mode 100644 index 00000000..40ed5501 --- /dev/null +++ b/packages/mcp-server/src/util.ts @@ -0,0 +1,25 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export const readEnv = (env: string): string | undefined => { + if (typeof (globalThis as any).process !== 'undefined') { + return (globalThis as any).process.env?.[env]?.trim(); + } else if (typeof (globalThis as any).Deno !== 'undefined') { + return (globalThis as any).Deno.env?.get?.(env)?.trim(); + } + return; +}; + +export const readEnvOrError = (env: string): string => { + let envValue = readEnv(env); + if (envValue === undefined) { + throw new Error(`Environment variable ${env} is not set`); + } + return envValue; +}; + +export const requireValue = (value: T | undefined, description: string): T => { + if (value === undefined) { + throw new Error(`Missing required value: ${description}`); + } + return value; +}; diff --git a/packages/mcp-server/tests/compat.test.ts b/packages/mcp-server/tests/compat.test.ts deleted file mode 100644 index d6272f6c..00000000 --- a/packages/mcp-server/tests/compat.test.ts +++ /dev/null @@ -1,1166 +0,0 @@ -import { - truncateToolNames, - removeTopLevelUnions, - removeAnyOf, - inlineRefs, - applyCompatibilityTransformations, - removeFormats, - findUsedDefs, -} from '../src/compat'; -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import { JSONSchema } from '../src/compat'; -import { Endpoint } from '../src/tools'; - -describe('truncateToolNames', () => { - it('should return original names when maxLength is 0 or negative', () => { - const names = ['tool1', 'tool2', 'tool3']; - expect(truncateToolNames(names, 0)).toEqual(new Map()); - expect(truncateToolNames(names, -1)).toEqual(new Map()); - }); - - it('should return original names when all names are shorter than maxLength', () => { - const names = ['tool1', 'tool2', 'tool3']; - expect(truncateToolNames(names, 10)).toEqual(new Map()); - }); - - it('should truncate names longer than maxLength', () => { - const names = ['very-long-tool-name', 'another-long-tool-name', 'short']; - expect(truncateToolNames(names, 10)).toEqual( - new Map([ - ['very-long-tool-name', 'very-long-'], - ['another-long-tool-name', 'another-lo'], - ]), - ); - }); - - it('should handle duplicate truncated names by appending numbers', () => { - const names = ['tool-name-a', 'tool-name-b', 'tool-name-c']; - expect(truncateToolNames(names, 8)).toEqual( - new Map([ - ['tool-name-a', 'tool-na1'], - ['tool-name-b', 'tool-na2'], - ['tool-name-c', 'tool-na3'], - ]), - ); - }); -}); - -describe('removeTopLevelUnions', () => { - const createTestTool = (overrides = {}): Tool => ({ - name: 'test-tool', - description: 'Test tool', - inputSchema: { - type: 'object', - properties: {}, - }, - ...overrides, - }); - - it('should return the original tool if it has no anyOf at the top level', () => { - const tool = createTestTool({ - inputSchema: { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - }, - }); - - expect(removeTopLevelUnions(tool)).toEqual([tool]); - }); - - it('should split a tool with top-level anyOf into multiple tools', () => { - const tool = createTestTool({ - name: 'union-tool', - description: 'A tool with unions', - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - title: 'first variant', - description: 'Its the first variant', - properties: { - variant1: { type: 'string' }, - }, - required: ['variant1'], - }, - { - title: 'second variant', - properties: { - variant2: { type: 'number' }, - }, - required: ['variant2'], - }, - ], - }, - }); - - const result = removeTopLevelUnions(tool); - - expect(result).toEqual([ - { - name: 'union-tool_first_variant', - description: 'Its the first variant', - inputSchema: { - type: 'object', - title: 'first variant', - description: 'Its the first variant', - properties: { - common: { type: 'string' }, - variant1: { type: 'string' }, - }, - required: ['variant1'], - }, - }, - { - name: 'union-tool_second_variant', - description: 'A tool with unions', - inputSchema: { - type: 'object', - title: 'second variant', - description: 'A tool with unions', - properties: { - common: { type: 'string' }, - variant2: { type: 'number' }, - }, - required: ['variant2'], - }, - }, - ]); - }); - - it('should handle $defs and only include those used by the variant', () => { - const tool = createTestTool({ - name: 'defs-tool', - description: 'A tool with $defs', - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - $defs: { - def1: { type: 'string', format: 'email' }, - def2: { type: 'number', minimum: 0 }, - unused: { type: 'boolean' }, - }, - anyOf: [ - { - properties: { - email: { $ref: '#/$defs/def1' }, - }, - }, - { - properties: { - count: { $ref: '#/$defs/def2' }, - }, - }, - ], - }, - }); - - const result = removeTopLevelUnions(tool); - - expect(result).toEqual([ - { - name: 'defs-tool_variant1', - description: 'A tool with $defs', - inputSchema: { - type: 'object', - description: 'A tool with $defs', - properties: { - common: { type: 'string' }, - email: { $ref: '#/$defs/def1' }, - }, - $defs: { - def1: { type: 'string', format: 'email' }, - }, - }, - }, - { - name: 'defs-tool_variant2', - description: 'A tool with $defs', - inputSchema: { - type: 'object', - description: 'A tool with $defs', - properties: { - common: { type: 'string' }, - count: { $ref: '#/$defs/def2' }, - }, - $defs: { - def2: { type: 'number', minimum: 0 }, - }, - }, - }, - ]); - }); -}); - -describe('removeAnyOf', () => { - it('should return original schema if it has no anyOf', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'number' }, - }, - }; - - expect(removeAnyOf(schema)).toEqual(schema); - }); - - it('should remove anyOf field and use the first variant', () => { - const schema = { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - properties: { - variant1: { type: 'string' }, - }, - required: ['variant1'], - }, - { - properties: { - variant2: { type: 'number' }, - }, - required: ['variant2'], - }, - ], - }; - - const expected = { - type: 'object', - properties: { - common: { type: 'string' }, - variant1: { type: 'string' }, - }, - required: ['variant1'], - }; - - expect(removeAnyOf(schema)).toEqual(expected); - }); - - it('should recursively remove anyOf fields from nested properties', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - nested: { - type: 'object', - properties: { - bar: { type: 'number' }, - }, - anyOf: [ - { - properties: { - option1: { type: 'boolean' }, - }, - }, - { - properties: { - option2: { type: 'array' }, - }, - }, - ], - }, - }, - }; - - const expected = { - type: 'object', - properties: { - foo: { type: 'string' }, - nested: { - type: 'object', - properties: { - bar: { type: 'number' }, - option1: { type: 'boolean' }, - }, - }, - }, - }; - - expect(removeAnyOf(schema)).toEqual(expected); - }); - - it('should handle arrays', () => { - const schema = { - type: 'object', - properties: { - items: { - type: 'array', - items: { - anyOf: [{ type: 'string' }, { type: 'number' }], - }, - }, - }, - }; - - const expected = { - type: 'object', - properties: { - items: { - type: 'array', - items: { - type: 'string', - }, - }, - }, - }; - - expect(removeAnyOf(schema)).toEqual(expected); - }); -}); - -describe('findUsedDefs', () => { - it('should handle circular references without stack overflow', () => { - const defs = { - person: { - type: 'object', - properties: { - name: { type: 'string' }, - friend: { $ref: '#/$defs/person' }, // Circular reference - }, - }, - }; - - const schema = { - type: 'object', - properties: { - user: { $ref: '#/$defs/person' }, - }, - }; - - // This should not throw a stack overflow error - expect(() => { - const result = findUsedDefs(schema, defs); - expect(result).toHaveProperty('person'); - }).not.toThrow(); - }); - - it('should handle indirect circular references without stack overflow', () => { - const defs = { - node: { - type: 'object', - properties: { - value: { type: 'string' }, - child: { $ref: '#/$defs/childNode' }, - }, - }, - childNode: { - type: 'object', - properties: { - value: { type: 'string' }, - parent: { $ref: '#/$defs/node' }, // Indirect circular reference - }, - }, - }; - - const schema = { - type: 'object', - properties: { - root: { $ref: '#/$defs/node' }, - }, - }; - - // This should not throw a stack overflow error - expect(() => { - const result = findUsedDefs(schema, defs); - expect(result).toHaveProperty('node'); - expect(result).toHaveProperty('childNode'); - }).not.toThrow(); - }); - - it('should find all used definitions in non-circular schemas', () => { - const defs = { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - address: { $ref: '#/$defs/address' }, - }, - }, - address: { - type: 'object', - properties: { - street: { type: 'string' }, - city: { type: 'string' }, - }, - }, - unused: { - type: 'object', - properties: { - data: { type: 'string' }, - }, - }, - }; - - const schema = { - type: 'object', - properties: { - person: { $ref: '#/$defs/user' }, - }, - }; - - const result = findUsedDefs(schema, defs); - expect(result).toHaveProperty('user'); - expect(result).toHaveProperty('address'); - expect(result).not.toHaveProperty('unused'); - }); -}); - -describe('inlineRefs', () => { - it('should return the original schema if it does not contain $refs', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - name: { type: 'string' }, - age: { type: 'number' }, - }, - }; - - expect(inlineRefs(schema)).toEqual(schema); - }); - - it('should inline simple $refs', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - user: { $ref: '#/$defs/user' }, - }, - $defs: { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should inline nested $refs', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - order: { $ref: '#/$defs/order' }, - }, - $defs: { - order: { - type: 'object', - properties: { - id: { type: 'string' }, - items: { type: 'array', items: { $ref: '#/$defs/item' } }, - }, - }, - item: { - type: 'object', - properties: { - product: { type: 'string' }, - quantity: { type: 'integer' }, - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - order: { - type: 'object', - properties: { - id: { type: 'string' }, - items: { - type: 'array', - items: { - type: 'object', - properties: { - product: { type: 'string' }, - quantity: { type: 'integer' }, - }, - }, - }, - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should handle circular references by removing the circular part', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - person: { $ref: '#/$defs/person' }, - }, - $defs: { - person: { - type: 'object', - properties: { - name: { type: 'string' }, - friend: { $ref: '#/$defs/person' }, // Circular reference - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - person: { - type: 'object', - properties: { - name: { type: 'string' }, - // friend property is removed to break the circular reference - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should handle indirect circular references', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - node: { $ref: '#/$defs/node' }, - }, - $defs: { - node: { - type: 'object', - properties: { - value: { type: 'string' }, - child: { $ref: '#/$defs/childNode' }, - }, - }, - childNode: { - type: 'object', - properties: { - value: { type: 'string' }, - parent: { $ref: '#/$defs/node' }, // Circular reference through childNode - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - node: { - type: 'object', - properties: { - value: { type: 'string' }, - child: { - type: 'object', - properties: { - value: { type: 'string' }, - // parent property is removed to break the circular reference - }, - }, - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should preserve other properties when inlining references', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - address: { $ref: '#/$defs/address', description: 'User address' }, - }, - $defs: { - address: { - type: 'object', - properties: { - street: { type: 'string' }, - city: { type: 'string' }, - }, - required: ['street'], - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - address: { - type: 'object', - description: 'User address', - properties: { - street: { type: 'string' }, - city: { type: 'string' }, - }, - required: ['street'], - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); -}); - -describe('removeFormats', () => { - it('should return original schema if formats capability is true', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field', format: 'date' }, - email: { type: 'string', description: 'An email field', format: 'email' }, - }, - }; - - expect(removeFormats(schema, true)).toEqual(schema); - }); - - it('should move format to description when formats capability is false', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field', format: 'date' }, - email: { type: 'string', description: 'An email field', format: 'email' }, - }, - }; - - const expected = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field (format: "date")' }, - email: { type: 'string', description: 'An email field (format: "email")' }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle properties without description', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', format: 'date' }, - }, - }; - - const expected = { - type: 'object', - properties: { - date: { type: 'string', description: '(format: "date")' }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle nested properties', () => { - const schema = { - type: 'object', - properties: { - user: { - type: 'object', - properties: { - created_at: { type: 'string', description: 'Creation date', format: 'date-time' }, - }, - }, - }, - }; - - const expected = { - type: 'object', - properties: { - user: { - type: 'object', - properties: { - created_at: { type: 'string', description: 'Creation date (format: "date-time")' }, - }, - }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle arrays of objects', () => { - const schema = { - type: 'object', - properties: { - dates: { - type: 'array', - items: { - type: 'object', - properties: { - start: { type: 'string', description: 'Start date', format: 'date' }, - end: { type: 'string', description: 'End date', format: 'date' }, - }, - }, - }, - }, - }; - - const expected = { - type: 'object', - properties: { - dates: { - type: 'array', - items: { - type: 'object', - properties: { - start: { type: 'string', description: 'Start date (format: "date")' }, - end: { type: 'string', description: 'End date (format: "date")' }, - }, - }, - }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle schemas with $defs', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field', format: 'date' }, - }, - $defs: { - timestamp: { - type: 'string', - description: 'A timestamp field', - format: 'date-time', - }, - }, - }; - - const expected = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field (format: "date")' }, - }, - $defs: { - timestamp: { - type: 'string', - description: 'A timestamp field (format: "date-time")', - }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); -}); - -describe('applyCompatibilityTransformations', () => { - const createTestTool = (name: string, overrides = {}): Tool => ({ - name, - description: 'Test tool', - inputSchema: { - type: 'object', - properties: {}, - }, - ...overrides, - }); - - const createTestEndpoint = (tool: Tool): Endpoint => ({ - tool, - handler: jest.fn(), - metadata: { - resource: 'test', - operation: 'read' as const, - tags: [], - }, - }); - - it('should not modify endpoints when all capabilities are enabled', () => { - const tool = createTestTool('test-tool'); - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed).toEqual(endpoints); - }); - - it('should split tools with top-level unions when topLevelUnions is disabled', () => { - const tool = createTestTool('union-tool', { - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - title: 'first variant', - properties: { - variant1: { type: 'string' }, - }, - }, - { - title: 'second variant', - properties: { - variant2: { type: 'number' }, - }, - }, - ], - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed.length).toBe(2); - expect(transformed[0]!.tool.name).toBe('union-tool_first_variant'); - expect(transformed[1]!.tool.name).toBe('union-tool_second_variant'); - }); - - it('should handle variants without titles in removeTopLevelUnions', () => { - const tool = createTestTool('union-tool', { - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - properties: { - variant1: { type: 'string' }, - }, - }, - { - properties: { - variant2: { type: 'number' }, - }, - }, - ], - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed.length).toBe(2); - expect(transformed[0]!.tool.name).toBe('union-tool_variant1'); - expect(transformed[1]!.tool.name).toBe('union-tool_variant2'); - }); - - it('should truncate tool names when toolNameLength is set', () => { - const tools = [ - createTestTool('very-long-tool-name-that-exceeds-limit'), - createTestTool('another-long-tool-name-to-truncate'), - createTestTool('short-name'), - ]; - - const endpoints = tools.map(createTestEndpoint); - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: 20, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed[0]!.tool.name).toBe('very-long-tool-name-'); - expect(transformed[1]!.tool.name).toBe('another-long-tool-na'); - expect(transformed[2]!.tool.name).toBe('short-name'); - }); - - it('should inline refs when refs capability is disabled', () => { - const tool = createTestTool('ref-tool', { - inputSchema: { - type: 'object', - properties: { - user: { $ref: '#/$defs/user' }, - }, - $defs: { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: false, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - expect(schema.$defs).toBeUndefined(); - - if (schema.properties) { - expect(schema.properties['user']).toEqual({ - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }); - } - }); - - it('should preserve external refs when inlining', () => { - const tool = createTestTool('ref-tool', { - inputSchema: { - type: 'object', - properties: { - internal: { $ref: '#/$defs/internal' }, - external: { $ref: 'https://example.com/schemas/external.json' }, - }, - $defs: { - internal: { - type: 'object', - properties: { - name: { type: 'string' }, - }, - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: false, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - if (schema.properties) { - expect(schema.properties['internal']).toEqual({ - type: 'object', - properties: { - name: { type: 'string' }, - }, - }); - expect(schema.properties['external']).toEqual({ - $ref: 'https://example.com/schemas/external.json', - }); - } - }); - - it('should remove anyOf fields when unions capability is disabled', () => { - const tool = createTestTool('union-tool', { - inputSchema: { - type: 'object', - properties: { - field: { - anyOf: [{ type: 'string' }, { type: 'number' }], - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: false, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - if (schema.properties && schema.properties['field']) { - const field = schema.properties['field']; - expect(field.anyOf).toBeUndefined(); - expect(field.type).toBe('string'); - } - }); - - it('should correctly combine topLevelUnions and toolNameLength transformations', () => { - const tool = createTestTool('very-long-union-tool-name', { - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - title: 'first variant', - properties: { - variant1: { type: 'string' }, - }, - }, - { - title: 'second variant', - properties: { - variant2: { type: 'number' }, - }, - }, - ], - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: 20, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed.length).toBe(2); - - // Both names should be truncated because they exceed 20 characters - expect(transformed[0]!.tool.name).toBe('very-long-union-too1'); - expect(transformed[1]!.tool.name).toBe('very-long-union-too2'); - }); - - it('should correctly combine refs and unions transformations', () => { - const tool = createTestTool('complex-tool', { - inputSchema: { - type: 'object', - properties: { - user: { $ref: '#/$defs/user' }, - }, - $defs: { - user: { - type: 'object', - properties: { - preference: { - anyOf: [{ type: 'string' }, { type: 'number' }], - }, - }, - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: false, - unions: false, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - // Refs should be inlined - expect(schema.$defs).toBeUndefined(); - - // Safely access nested properties - if (schema.properties && schema.properties['user']) { - const user = schema.properties['user']; - // User should be inlined - expect(user.type).toBe('object'); - - // AnyOf in the inlined user.preference should be removed - if (user.properties && user.properties['preference']) { - const preference = user.properties['preference']; - expect(preference.anyOf).toBeUndefined(); - expect(preference.type).toBe('string'); - } - } - }); - - it('should handle formats capability being false', () => { - const tool = createTestTool('format-tool', { - inputSchema: { - type: 'object', - properties: { - date: { type: 'string', description: 'A date', format: 'date' }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: false, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - if (schema.properties && schema.properties['date']) { - const dateField = schema.properties['date']; - expect(dateField['format']).toBeUndefined(); - expect(dateField['description']).toBe('A date (format: "date")'); - } - }); -}); diff --git a/packages/mcp-server/tests/dynamic-tools.test.ts b/packages/mcp-server/tests/dynamic-tools.test.ts deleted file mode 100644 index 08963af8..00000000 --- a/packages/mcp-server/tests/dynamic-tools.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { dynamicTools } from '../src/dynamic-tools'; -import { Endpoint } from '../src/tools'; - -describe('dynamicTools', () => { - const fakeClient = {} as any; - - const endpoints: Endpoint[] = [ - makeEndpoint('test_read_endpoint', 'test_resource', 'read', ['test']), - makeEndpoint('test_write_endpoint', 'test_resource', 'write', ['test']), - makeEndpoint('user_endpoint', 'user', 'read', ['user', 'admin']), - makeEndpoint('admin_endpoint', 'admin', 'write', ['admin']), - ]; - - const tools = dynamicTools(endpoints); - - const toolsMap = { - list_api_endpoints: toolOrError('list_api_endpoints'), - get_api_endpoint_schema: toolOrError('get_api_endpoint_schema'), - invoke_api_endpoint: toolOrError('invoke_api_endpoint'), - }; - - describe('list_api_endpoints', () => { - it('should return all endpoints when no search query is provided', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, {}); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(endpoints.length); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_read_endpoint'); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_write_endpoint'); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('user_endpoint'); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('admin_endpoint'); - }); - - it('should filter endpoints by name', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'user' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(1); - expect(result.tools[0].name).toBe('user_endpoint'); - }); - - it('should filter endpoints by resource', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools.some((t: { resource: string }) => t.resource === 'admin')).toBeTruthy(); - }); - - it('should filter endpoints by tag', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools.some((t: { tags: string[] }) => t.tags.includes('admin'))).toBeTruthy(); - }); - - it('should be case insensitive in search', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'ADMIN' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools.length).toBe(2); - result.tools.forEach((tool: { name: string; resource: string; tags: string[] }) => { - expect( - tool.name.toLowerCase().includes('admin') || - tool.resource.toLowerCase().includes('admin') || - tool.tags.some((tag: string) => tag.toLowerCase().includes('admin')), - ).toBeTruthy(); - }); - }); - - it('should filter endpoints by description', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { - search_query: 'Test endpoint for user_endpoint', - }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(1); - expect(result.tools[0].name).toBe('user_endpoint'); - expect(result.tools[0].description).toBe('Test endpoint for user_endpoint'); - }); - - it('should filter endpoints by partial description match', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { - search_query: 'endpoint for user', - }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(1); - expect(result.tools[0].name).toBe('user_endpoint'); - }); - }); - - describe('get_api_endpoint_schema', () => { - it('should return schema for existing endpoint', async () => { - const content = await toolsMap.get_api_endpoint_schema.handler(fakeClient, { - endpoint: 'test_read_endpoint', - }); - const result = JSON.parse(content.content[0].text); - - expect(result).toEqual(endpoints[0]?.tool); - }); - - it('should throw error for non-existent endpoint', async () => { - await expect( - toolsMap.get_api_endpoint_schema.handler(fakeClient, { endpoint: 'non_existent_endpoint' }), - ).rejects.toThrow('Endpoint non_existent_endpoint not found'); - }); - - it('should throw error when no endpoint provided', async () => { - await expect(toolsMap.get_api_endpoint_schema.handler(fakeClient, undefined)).rejects.toThrow( - 'No endpoint provided', - ); - }); - }); - - describe('invoke_api_endpoint', () => { - it('should successfully invoke endpoint with valid arguments', async () => { - const mockHandler = endpoints[0]?.handler as jest.Mock; - mockHandler.mockClear(); - - await toolsMap.invoke_api_endpoint.handler(fakeClient, { - endpoint_name: 'test_read_endpoint', - args: { testParam: 'test value' }, - }); - - expect(mockHandler).toHaveBeenCalledWith(fakeClient, { testParam: 'test value' }); - }); - - it('should throw error for non-existent endpoint', async () => { - await expect( - toolsMap.invoke_api_endpoint.handler(fakeClient, { - endpoint_name: 'non_existent_endpoint', - args: { testParam: 'test value' }, - }), - ).rejects.toThrow(/Endpoint non_existent_endpoint not found/); - }); - - it('should throw error when no arguments provided', async () => { - await expect(toolsMap.invoke_api_endpoint.handler(fakeClient, undefined)).rejects.toThrow( - 'No endpoint provided', - ); - }); - - it('should throw error for invalid argument schema', async () => { - await expect( - toolsMap.invoke_api_endpoint.handler(fakeClient, { - endpoint_name: 'test_read_endpoint', - args: { wrongParam: 'test value' }, // Missing required testParam - }), - ).rejects.toThrow(/Invalid arguments for endpoint/); - }); - }); - - function toolOrError(name: string) { - const tool = tools.find((tool) => tool.tool.name === name); - if (!tool) throw new Error(`Tool ${name} not found`); - return tool; - } -}); - -function makeEndpoint( - name: string, - resource: string, - operation: 'read' | 'write', - tags: string[] = [], -): Endpoint { - return { - metadata: { - resource, - operation, - tags, - }, - tool: { - name, - description: `Test endpoint for ${name}`, - inputSchema: { - type: 'object', - properties: { - testParam: { type: 'string' }, - }, - required: ['testParam'], - }, - }, - handler: jest.fn().mockResolvedValue({ success: true }), - }; -} diff --git a/packages/mcp-server/tests/options.test.ts b/packages/mcp-server/tests/options.test.ts index 4d9b60ca..17306295 100644 --- a/packages/mcp-server/tests/options.test.ts +++ b/packages/mcp-server/tests/options.test.ts @@ -1,6 +1,4 @@ -import { parseCLIOptions, parseQueryOptions } from '../src/options'; -import { Filter } from '../src/tools'; -import { parseEmbeddedJSON } from '../src/compat'; +import { parseCLIOptions } from '../src/options'; // Mock process.argv const mockArgv = (args: string[]) => { @@ -12,529 +10,23 @@ const mockArgv = (args: string[]) => { }; describe('parseCLIOptions', () => { - it('should parse basic filter options', () => { - const cleanup = mockArgv([ - '--tool=test-tool', - '--resource=test-resource', - '--operation=read', - '--tag=test-tag', - ]); + it('default parsing should be stdio', () => { + const cleanup = mockArgv([]); const result = parseCLIOptions(); - expect(result.filters).toEqual([ - { type: 'tag', op: 'include', value: 'test-tag' }, - { type: 'resource', op: 'include', value: 'test-resource' }, - { type: 'tool', op: 'include', value: 'test-tool' }, - { type: 'operation', op: 'include', value: 'read' }, - ] as Filter[]); - - expect(result.capabilities).toEqual({}); - - expect(result.list).toBe(false); - - cleanup(); - }); - - it('should parse exclusion filters', () => { - const cleanup = mockArgv([ - '--no-tool=exclude-tool', - '--no-resource=exclude-resource', - '--no-operation=write', - '--no-tag=exclude-tag', - ]); - - const result = parseCLIOptions(); - - expect(result.filters).toEqual([ - { type: 'tag', op: 'exclude', value: 'exclude-tag' }, - { type: 'resource', op: 'exclude', value: 'exclude-resource' }, - { type: 'tool', op: 'exclude', value: 'exclude-tool' }, - { type: 'operation', op: 'exclude', value: 'write' }, - ] as Filter[]); - - expect(result.capabilities).toEqual({}); - - cleanup(); - }); - - it('should parse client presets', () => { - const cleanup = mockArgv(['--client=openai-agents']); - - const result = parseCLIOptions(); - - expect(result.client).toEqual('openai-agents'); - - cleanup(); - }); - - it('should parse individual capabilities', () => { - const cleanup = mockArgv([ - '--capability=top-level-unions', - '--capability=valid-json', - '--capability=refs', - '--capability=unions', - '--capability=tool-name-length=40', - ]); - - const result = parseCLIOptions(); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - toolNameLength: 40, - }); - - cleanup(); - }); - - it('should handle list option', () => { - const cleanup = mockArgv(['--list']); - - const result = parseCLIOptions(); - - expect(result.list).toBe(true); - - cleanup(); - }); - - it('should handle multiple filters of the same type', () => { - const cleanup = mockArgv(['--tool=tool1', '--tool=tool2', '--resource=res1', '--resource=res2']); - - const result = parseCLIOptions(); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'res1' }, - { type: 'resource', op: 'include', value: 'res2' }, - { type: 'tool', op: 'include', value: 'tool1' }, - { type: 'tool', op: 'include', value: 'tool2' }, - ] as Filter[]); + expect(result.transport).toBe('stdio'); cleanup(); }); - it('should handle comma-separated values in array options', () => { - const cleanup = mockArgv([ - '--tool=tool1,tool2', - '--resource=res1,res2', - '--capability=top-level-unions,valid-json,unions', - ]); + it('using http transport with a port', () => { + const cleanup = mockArgv(['--transport=http', '--port=2222']); const result = parseCLIOptions(); - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'res1' }, - { type: 'resource', op: 'include', value: 'res2' }, - { type: 'tool', op: 'include', value: 'tool1' }, - { type: 'tool', op: 'include', value: 'tool2' }, - ] as Filter[]); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - unions: true, - }); - - cleanup(); - }); - - it('should handle invalid tool-name-length format', () => { - const cleanup = mockArgv(['--capability=tool-name-length=invalid']); - - // Mock console.error to prevent output during test - const originalError = console.error; - console.error = jest.fn(); - - expect(() => parseCLIOptions()).toThrow(); - - console.error = originalError; + expect(result.transport).toBe('http'); + expect(result.port).toBe(2222); cleanup(); }); - - it('should handle unknown capability', () => { - const cleanup = mockArgv(['--capability=unknown-capability']); - - // Mock console.error to prevent output during test - const originalError = console.error; - console.error = jest.fn(); - - expect(() => parseCLIOptions()).toThrow(); - - console.error = originalError; - cleanup(); - }); -}); - -describe('parseQueryOptions', () => { - const defaultOptions = { - client: undefined, - includeDynamicTools: undefined, - includeCodeTools: undefined, - includeAllTools: undefined, - filters: [], - capabilities: { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - }; - - it('should parse basic filter options from query string', () => { - const query = 'tool=test-tool&resource=test-resource&operation=read&tag=test-tag'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'test-resource' }, - { type: 'operation', op: 'include', value: 'read' }, - { type: 'tag', op: 'include', value: 'test-tag' }, - { type: 'tool', op: 'include', value: 'test-tool' }, - ]); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }); - }); - - it('should parse exclusion filters from query string', () => { - const query = 'no_tool=exclude-tool&no_resource=exclude-resource&no_operation=write&no_tag=exclude-tag'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'exclude', value: 'exclude-resource' }, - { type: 'operation', op: 'exclude', value: 'write' }, - { type: 'tag', op: 'exclude', value: 'exclude-tag' }, - { type: 'tool', op: 'exclude', value: 'exclude-tool' }, - ]); - }); - - it('should parse client option from query string', () => { - const query = 'client=openai-agents'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.client).toBe('openai-agents'); - }); - - it('should parse client capabilities from query string', () => { - const query = 'capability=top-level-unions&capability=valid-json&capability=tool-name-length%3D40'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: 40, - }); - }); - - it('should parse no-capability options from query string', () => { - const query = 'no_capability=top-level-unions&no_capability=refs&no_capability=formats'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.capabilities).toEqual({ - topLevelUnions: false, - validJson: true, - refs: false, - unions: true, - formats: false, - toolNameLength: undefined, - }); - }); - - it('should parse tools options from query string', () => { - const query = 'tools=dynamic&tools=all'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.includeDynamicTools).toBe(true); - expect(result.includeAllTools).toBe(true); - }); - - it('should parse no-tools options from query string', () => { - const query = 'tools=dynamic&tools=all&no_tools=dynamic'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.includeDynamicTools).toBe(false); - expect(result.includeAllTools).toBe(true); - }); - - it('should handle array values in query string', () => { - const query = 'tool[]=tool1&tool[]=tool2&resource[]=res1&resource[]=res2'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'res1' }, - { type: 'resource', op: 'include', value: 'res2' }, - { type: 'tool', op: 'include', value: 'tool1' }, - { type: 'tool', op: 'include', value: 'tool2' }, - ]); - }); - - it('should merge with default options', () => { - const defaultWithFilters = { - ...defaultOptions, - filters: [{ type: 'tag' as const, op: 'include' as const, value: 'existing-tag' }], - client: 'cursor' as const, - includeDynamicTools: true, - }; - - const query = 'tool=new-tool&resource=new-resource'; - const result = parseQueryOptions(defaultWithFilters, query); - - expect(result.filters).toEqual([ - { type: 'tag', op: 'include', value: 'existing-tag' }, - { type: 'resource', op: 'include', value: 'new-resource' }, - { type: 'tool', op: 'include', value: 'new-tool' }, - ]); - - expect(result.client).toBe('cursor'); - expect(result.includeDynamicTools).toBe(true); - }); - - it('should override client from default options', () => { - const defaultWithClient = { - ...defaultOptions, - client: 'cursor' as const, - }; - - const query = 'client=openai-agents'; - const result = parseQueryOptions(defaultWithClient, query); - - expect(result.client).toBe('openai-agents'); - }); - - it('should merge capabilities with default options', () => { - const defaultWithCapabilities = { - ...defaultOptions, - capabilities: { - topLevelUnions: false, - validJson: false, - refs: true, - unions: true, - formats: true, - toolNameLength: 30, - }, - }; - - const query = 'capability=top-level-unions&no_capability=refs'; - const result = parseQueryOptions(defaultWithCapabilities, query); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: false, - refs: false, - unions: true, - formats: true, - toolNameLength: 30, - }); - }); - - it('should handle empty query string', () => { - const query = ''; - const result = parseQueryOptions(defaultOptions, query); - - expect(result).toEqual(defaultOptions); - }); - - it('should handle invalid query string gracefully', () => { - const query = 'invalid=value&operation=invalid-operation'; - - // Should throw due to Zod validation for invalid operation - expect(() => parseQueryOptions(defaultOptions, query)).toThrow(); - }); - - it('should preserve default undefined values when not specified', () => { - const defaultWithUndefined = { - ...defaultOptions, - client: undefined, - includeDynamicTools: undefined, - includeAllTools: undefined, - }; - - const query = 'tool=test-tool'; - const result = parseQueryOptions(defaultWithUndefined, query); - - expect(result.client).toBeUndefined(); - expect(result.includeDynamicTools).toBeFalsy(); - expect(result.includeAllTools).toBeFalsy(); - }); - - it('should handle complex query with mixed include and exclude filters', () => { - const query = - 'tool=include-tool&no_tool=exclude-tool&resource=include-res&no_resource=exclude-res&operation=read&tag=include-tag&no_tag=exclude-tag'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'include-res' }, - { type: 'operation', op: 'include', value: 'read' }, - { type: 'tag', op: 'include', value: 'include-tag' }, - { type: 'tool', op: 'include', value: 'include-tool' }, - { type: 'resource', op: 'exclude', value: 'exclude-res' }, - { type: 'tag', op: 'exclude', value: 'exclude-tag' }, - { type: 'tool', op: 'exclude', value: 'exclude-tool' }, - ]); - }); - - it('code tools are enabled on http servers with default option set', () => { - const query = 'tools=code'; - const result = parseQueryOptions({ ...defaultOptions, includeCodeTools: true }, query); - - expect(result.includeCodeTools).toBe(true); - }); - - it('code tools are prevented on http servers when no default option set', () => { - const query = 'tools=code'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.includeCodeTools).toBe(undefined); - }); - - it('code tools are prevented on http servers when default option is explicitly false', () => { - const query = 'tools=code'; - const result = parseQueryOptions({ ...defaultOptions, includeCodeTools: false }, query); - - expect(result.includeCodeTools).toBe(false); - }); -}); - -describe('parseEmbeddedJSON', () => { - it('should not change non-string values', () => { - const args = { - numberProp: 42, - booleanProp: true, - objectProp: { nested: 'value' }, - arrayProp: [1, 2, 3], - nullProp: null, - undefinedProp: undefined, - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - expect(result['numberProp']).toBe(42); - expect(result['booleanProp']).toBe(true); - expect(result['objectProp']).toEqual({ nested: 'value' }); - expect(result['arrayProp']).toEqual([1, 2, 3]); - expect(result['nullProp']).toBe(null); - expect(result['undefinedProp']).toBe(undefined); - }); - - it('should parse valid JSON objects in string properties', () => { - const args = { - jsonObjectString: '{"key": "value", "number": 123}', - regularString: 'not json', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).not.toBe(args); // Should return new object since changes were made - expect(result['jsonObjectString']).toEqual({ key: 'value', number: 123 }); - expect(result['regularString']).toBe('not json'); - }); - - it('should leave invalid JSON in string properties unchanged', () => { - const args = { - invalidJson1: '{"key": value}', // Missing quotes around value - invalidJson2: '{key: "value"}', // Missing quotes around key - invalidJson3: '{"key": "value",}', // Trailing comma - invalidJson4: 'just a regular string', - emptyString: '', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - expect(result['invalidJson1']).toBe('{"key": value}'); - expect(result['invalidJson2']).toBe('{key: "value"}'); - expect(result['invalidJson3']).toBe('{"key": "value",}'); - expect(result['invalidJson4']).toBe('just a regular string'); - expect(result['emptyString']).toBe(''); - }); - - it('should not parse JSON primitives in string properties', () => { - const args = { - numberString: '123', - floatString: '45.67', - negativeNumberString: '-89', - booleanTrueString: 'true', - booleanFalseString: 'false', - nullString: 'null', - jsonArrayString: '[1, 2, 3, "test"]', - regularString: 'not json', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - expect(result['numberString']).toBe('123'); - expect(result['floatString']).toBe('45.67'); - expect(result['negativeNumberString']).toBe('-89'); - expect(result['booleanTrueString']).toBe('true'); - expect(result['booleanFalseString']).toBe('false'); - expect(result['nullString']).toBe('null'); - expect(result['jsonArrayString']).toBe('[1, 2, 3, "test"]'); - expect(result['regularString']).toBe('not json'); - }); - - it('should handle mixed valid objects and other JSON types', () => { - const args = { - validObject: '{"success": true}', - invalidObject: '{"missing": quote}', - validNumber: '42', - validArray: '[1, 2, 3]', - keepAsString: 'hello world', - nonString: 123, - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).not.toBe(args); // Should return new object since some changes were made - expect(result['validObject']).toEqual({ success: true }); - expect(result['invalidObject']).toBe('{"missing": quote}'); - expect(result['validNumber']).toBe('42'); // Not parsed, remains string - expect(result['validArray']).toBe('[1, 2, 3]'); // Not parsed, remains string - expect(result['keepAsString']).toBe('hello world'); - expect(result['nonString']).toBe(123); - }); - - it('should return original object when no strings are present', () => { - const args = { - number: 42, - boolean: true, - object: { key: 'value' }, - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - }); - - it('should return original object when all strings are invalid JSON', () => { - const args = { - string1: 'hello', - string2: 'world', - string3: 'not json at all', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - }); }); diff --git a/packages/mcp-server/tests/tools.test.ts b/packages/mcp-server/tests/tools.test.ts deleted file mode 100644 index cfff24a2..00000000 --- a/packages/mcp-server/tests/tools.test.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { Endpoint, Filter, Metadata, query } from '../src/tools'; - -describe('Endpoint filtering', () => { - const endpoints: Endpoint[] = [ - endpoint({ - resource: 'user', - operation: 'read', - tags: ['admin'], - toolName: 'retrieve_user', - }), - endpoint({ - resource: 'user.profile', - operation: 'write', - tags: [], - toolName: 'create_user_profile', - }), - endpoint({ - resource: 'user.profile', - operation: 'read', - tags: [], - toolName: 'get_user_profile', - }), - endpoint({ - resource: 'user.roles.permissions', - operation: 'write', - tags: ['admin', 'security'], - toolName: 'update_user_role_permissions', - }), - endpoint({ - resource: 'documents.metadata.tags', - operation: 'write', - tags: ['taxonomy', 'metadata'], - toolName: 'create_document_metadata_tags', - }), - endpoint({ - resource: 'organization.settings', - operation: 'read', - tags: ['admin', 'configuration'], - toolName: 'get_organization_settings', - }), - ]; - - const tests: { name: string; filters: Filter[]; expected: string[] }[] = [ - { - name: 'match none', - filters: [], - expected: [], - }, - - // Resource tests - { - name: 'simple resource', - filters: [{ type: 'resource', op: 'include', value: 'user' }], - expected: ['retrieve_user'], - }, - { - name: 'exclude resource', - filters: [{ type: 'resource', op: 'exclude', value: 'user' }], - expected: [ - 'create_user_profile', - 'get_user_profile', - 'update_user_role_permissions', - 'create_document_metadata_tags', - 'get_organization_settings', - ], - }, - { - name: 'resource and subresources', - filters: [{ type: 'resource', op: 'include', value: 'user*' }], - expected: ['retrieve_user', 'create_user_profile', 'get_user_profile', 'update_user_role_permissions'], - }, - { - name: 'just subresources', - filters: [{ type: 'resource', op: 'include', value: 'user.*' }], - expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], - }, - { - name: 'specific subresource', - filters: [{ type: 'resource', op: 'include', value: 'user.roles.permissions' }], - expected: ['update_user_role_permissions'], - }, - { - name: 'deep wildcard match', - filters: [{ type: 'resource', op: 'include', value: '*.*.tags' }], - expected: ['create_document_metadata_tags'], - }, - - // Operation tests - { - name: 'read operation', - filters: [{ type: 'operation', op: 'include', value: 'read' }], - expected: ['retrieve_user', 'get_user_profile', 'get_organization_settings'], - }, - { - name: 'write operation', - filters: [{ type: 'operation', op: 'include', value: 'write' }], - expected: ['create_user_profile', 'update_user_role_permissions', 'create_document_metadata_tags'], - }, - { - name: 'resource and operation combined', - filters: [ - { type: 'resource', op: 'include', value: 'user.profile' }, - { type: 'operation', op: 'exclude', value: 'write' }, - ], - expected: ['get_user_profile'], - }, - - // Tag tests - { - name: 'admin tag', - filters: [{ type: 'tag', op: 'include', value: 'admin' }], - expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], - }, - { - name: 'taxonomy tag', - filters: [{ type: 'tag', op: 'include', value: 'taxonomy' }], - expected: ['create_document_metadata_tags'], - }, - { - name: 'multiple tags (OR logic)', - filters: [ - { type: 'tag', op: 'include', value: 'admin' }, - { type: 'tag', op: 'include', value: 'security' }, - ], - expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], - }, - { - name: 'excluding a tag', - filters: [ - { type: 'tag', op: 'include', value: 'admin' }, - { type: 'tag', op: 'exclude', value: 'security' }, - ], - expected: ['retrieve_user', 'get_organization_settings'], - }, - - // Tool name tests - { - name: 'tool name match', - filters: [{ type: 'tool', op: 'include', value: 'get_organization_settings' }], - expected: ['get_organization_settings'], - }, - { - name: 'two tools match', - filters: [ - { type: 'tool', op: 'include', value: 'get_organization_settings' }, - { type: 'tool', op: 'include', value: 'create_user_profile' }, - ], - expected: ['create_user_profile', 'get_organization_settings'], - }, - { - name: 'excluding tool by name', - filters: [ - { type: 'resource', op: 'include', value: 'user*' }, - { type: 'tool', op: 'exclude', value: 'retrieve_user' }, - ], - expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], - }, - - // Complex combinations - { - name: 'complex filter: read operations with admin tag', - filters: [ - { type: 'operation', op: 'include', value: 'read' }, - { type: 'tag', op: 'include', value: 'admin' }, - ], - expected: [ - 'retrieve_user', - 'get_user_profile', - 'update_user_role_permissions', - 'get_organization_settings', - ], - }, - { - name: 'complex filter: user resources with no tags', - filters: [ - { type: 'resource', op: 'include', value: 'user.profile' }, - { type: 'tag', op: 'exclude', value: 'admin' }, - ], - expected: ['create_user_profile', 'get_user_profile'], - }, - { - name: 'complex filter: user resources and tags', - filters: [ - { type: 'resource', op: 'include', value: 'user.profile' }, - { type: 'tag', op: 'include', value: 'admin' }, - ], - expected: [ - 'retrieve_user', - 'create_user_profile', - 'get_user_profile', - 'update_user_role_permissions', - 'get_organization_settings', - ], - }, - ]; - - tests.forEach((test) => { - it(`filters by ${test.name}`, () => { - const filtered = query(test.filters, endpoints); - expect(filtered.map((e) => e.tool.name)).toEqual(test.expected); - }); - }); -}); - -function endpoint({ - resource, - operation, - tags, - toolName, -}: { - resource: string; - operation: Metadata['operation']; - tags: string[]; - toolName: string; -}): Endpoint { - return { - metadata: { - resource, - operation, - tags, - }, - tool: { name: toolName, inputSchema: { type: 'object', properties: {} } }, - handler: jest.fn(), - }; -} diff --git a/packages/mcp-server/tsconfig.json b/packages/mcp-server/tsconfig.json index 8109c2fa..694dec86 100644 --- a/packages/mcp-server/tsconfig.json +++ b/packages/mcp-server/tsconfig.json @@ -2,8 +2,8 @@ "include": ["src", "tests", "examples"], "exclude": [], "compilerOptions": { - "target": "es2020", - "lib": ["es2020"], + "target": "es2022", + "lib": ["es2022"], "module": "commonjs", "moduleResolution": "node", "esModuleInterop": true, diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock index 2bb21c66..c7e37692 100644 --- a/packages/mcp-server/yarn.lock +++ b/packages/mcp-server/yarn.lock @@ -2,122 +2,134 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== - dependencies: - "@babel/helper-validator-identifier" "^7.27.1" +"@anthropic-ai/mcpb@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@anthropic-ai/mcpb/-/mcpb-2.1.2.tgz#cf02801929734b8810961f22e2eb72758c27d527" + integrity sha512-goRbBC8ySo7SWb7tRzr+tL6FxDc4JPTRCdgfD2omba7freofvjq5rom1lBnYHZHo6Mizs1jAHJeN53aZbDoy8A== + dependencies: + "@inquirer/prompts" "^6.0.1" + commander "^13.1.0" + fflate "^0.8.2" + galactus "^1.0.0" + ignore "^7.0.5" + node-forge "^1.3.2" + pretty-bytes "^5.6.0" + zod "^3.25.67" + zod-to-json-schema "^3.24.6" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.28.6.tgz#72499312ec58b1e2245ba4a4f550c132be4982f7" + integrity sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" js-tokens "^4.0.0" picocolors "^1.1.1" -"@babel/compat-data@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.2.tgz#4183f9e642fd84e74e3eea7ffa93a412e3b102c9" - integrity sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ== +"@babel/compat-data@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.6.tgz#103f466803fa0f059e82ccac271475470570d74c" + integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg== "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.1.tgz#89de51e86bd12246003e3524704c49541b16c3e6" - integrity sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.27.1" - "@babel/helper-compilation-targets" "^7.27.1" - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helpers" "^7.27.1" - "@babel/parser" "^7.27.1" - "@babel/template" "^7.27.1" - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.6.tgz#531bf883a1126e53501ba46eb3bb414047af507f" + integrity sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/generator" "^7.28.6" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + "@jridgewell/remapping" "^2.3.5" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.27.1", "@babel/generator@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.1.tgz#862d4fad858f7208edd487c28b58144036b76230" - integrity sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w== +"@babel/generator@^7.28.6", "@babel/generator@^7.7.2": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.6.tgz#48dcc65d98fcc8626a48f72b62e263d25fc3c3f1" + integrity sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw== dependencies: - "@babel/parser" "^7.27.1" - "@babel/types" "^7.27.1" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" -"@babel/helper-compilation-targets@^7.27.1": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" - integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== +"@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== dependencies: - "@babel/compat-data" "^7.27.2" + "@babel/compat-data" "^7.28.6" "@babel/helper-validator-option" "^7.27.1" browserslist "^4.24.0" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-module-imports@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" - integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" -"@babel/helper-module-transforms@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz#e1663b8b71d2de948da5c4fb2a20ca4f3ec27a6f" - integrity sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g== +"@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.27.1" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" - integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.28.6", "@babel/helper-plugin-utils@^7.8.0": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" + integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== "@babel/helper-string-parser@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== -"@babel/helper-validator-identifier@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" - integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== +"@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== "@babel/helper-validator-option@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== -"@babel/helpers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.1.tgz#ffc27013038607cdba3288e692c3611c06a18aa4" - integrity sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ== +"@babel/helpers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.6.tgz#fca903a313ae675617936e8998b814c415cbf5d7" + integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw== dependencies: - "@babel/template" "^7.27.1" - "@babel/types" "^7.27.1" + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.1", "@babel/parser@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127" - integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd" + integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ== dependencies: - "@babel/types" "^7.27.1" + "@babel/types" "^7.28.6" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -148,11 +160,11 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-import-attributes@^7.24.7": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" - integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503" + integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" @@ -169,11 +181,11 @@ "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-jsx@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" - integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee" + integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" @@ -232,41 +244,41 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" - integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/template@^7.27.1", "@babel/template@^7.3.3": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" - integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/parser" "^7.27.2" - "@babel/types" "^7.27.1" - -"@babel/traverse@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.1.tgz#4db772902b133bbddd1c4f7a7ee47761c1b9f291" - integrity sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.27.1" - "@babel/parser" "^7.27.1" - "@babel/template" "^7.27.1" - "@babel/types" "^7.27.1" + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz#c7b2ddf1d0a811145b1de800d1abd146af92e3a2" + integrity sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/template@^7.28.6", "@babel/template@^7.3.3": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/traverse@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.6.tgz#871ddc79a80599a5030c53b1cc48cbe3a5583c2e" + integrity sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/generator" "^7.28.6" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.6" + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" debug "^4.3.1" - globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.3.3": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" - integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.3.3": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df" + integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg== dependencies: "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" "@bcoe/v8-coverage@^0.2.3": version "0.2.3" @@ -285,56 +297,239 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": - version "4.7.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" - integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== +"@eslint-community/eslint-utils@^4.4.0", "@eslint-community/eslint-utils@^4.8.0": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== dependencies: eslint-visitor-keys "^3.4.3" -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + +"@eslint/config-array@^0.21.2": + version "0.21.2" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.2.tgz#f29e22057ad5316cf23836cee9a34c81fffcb7e6" + integrity sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw== + dependencies: + "@eslint/object-schema" "^2.1.7" + debug "^4.3.1" + minimatch "^3.1.5" + +"@eslint/config-helpers@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda" + integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== + dependencies: + "@eslint/core" "^0.17.0" + +"@eslint/core@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.17.0.tgz#77225820413d9617509da9342190a2019e78761c" + integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== + dependencies: + "@types/json-schema" "^7.0.15" -"@eslint/eslintrc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== +"@eslint/eslintrc@^3.3.5": + version "3.3.5" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.5.tgz#c131793cfc1a7b96f24a83e0a8bbd4b881558c60" + integrity sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg== dependencies: - ajv "^6.12.4" + ajv "^6.14.0" debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" + espree "^10.0.1" + globals "^14.0.0" ignore "^5.2.0" import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" + js-yaml "^4.1.1" + minimatch "^3.1.5" strip-json-comments "^3.1.1" -"@eslint/js@8.57.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" - integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== +"@eslint/js@9.39.4": + version "9.39.4" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.4.tgz#a3f83bfc6fd9bf33a853dfacd0b49b398eb596c1" + integrity sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw== -"@humanwhocodes/config-array@^0.13.0": - version "0.13.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" - integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== +"@eslint/object-schema@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" + integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== + +"@eslint/plugin-kit@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz#9779e3fd9b7ee33571a57435cf4335a1794a6cb2" + integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== dependencies: - "@humanwhocodes/object-schema" "^2.0.3" - debug "^4.3.1" - minimatch "^3.0.5" + "@eslint/core" "^0.17.0" + levn "^0.4.1" + +"@hono/node-server@^1.19.10", "@hono/node-server@^1.19.9": + version "1.19.11" + resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.19.11.tgz#dc419f0826dd2504e9fc86ad289d5636a0444e2f" + integrity sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g== + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.7" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.7.tgz#822cb7b3a12c5a240a24f621b5a2413e27a45f26" + integrity sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.4.0" "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" - integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== + +"@inquirer/checkbox@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/checkbox/-/checkbox-3.0.1.tgz#0a57f704265f78c36e17f07e421b98efb4b9867b" + integrity sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/confirm@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-4.0.1.tgz#9106d6bffa0b2fdd0e4f60319b6f04f2e06e6e25" + integrity sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/core@^9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-9.2.1.tgz#677c49dee399c9063f31e0c93f0f37bddc67add1" + integrity sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg== + dependencies: + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + "@types/mute-stream" "^0.0.4" + "@types/node" "^22.5.5" + "@types/wrap-ansi" "^3.0.0" + ansi-escapes "^4.3.2" + cli-width "^4.1.0" + mute-stream "^1.0.0" + signal-exit "^4.1.0" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/editor@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/editor/-/editor-3.0.1.tgz#d109f21e050af6b960725388cb1c04214ed7c7bc" + integrity sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + external-editor "^3.1.0" + +"@inquirer/expand@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/expand/-/expand-3.0.1.tgz#aed9183cac4d12811be47a4a895ea8e82a17e22c" + integrity sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/figures@^1.0.6": + version "1.0.15" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.15.tgz#dbb49ed80df11df74268023b496ac5d9acd22b3a" + integrity sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g== + +"@inquirer/input@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/input/-/input-3.0.1.tgz#de63d49e516487388508d42049deb70f2cb5f28e" + integrity sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/number@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/number/-/number-2.0.1.tgz#b9863080d02ab7dc2e56e16433d83abea0f2a980" + integrity sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/password@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-3.0.1.tgz#2a9a9143591088336bbd573bcb05d5bf080dbf87" + integrity sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + +"@inquirer/prompts@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/prompts/-/prompts-6.0.1.tgz#43f5c0ed35c5ebfe52f1d43d46da2d363d950071" + integrity sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A== + dependencies: + "@inquirer/checkbox" "^3.0.1" + "@inquirer/confirm" "^4.0.1" + "@inquirer/editor" "^3.0.1" + "@inquirer/expand" "^3.0.1" + "@inquirer/input" "^3.0.1" + "@inquirer/number" "^2.0.1" + "@inquirer/password" "^3.0.1" + "@inquirer/rawlist" "^3.0.1" + "@inquirer/search" "^2.0.1" + "@inquirer/select" "^3.0.1" + +"@inquirer/rawlist@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/rawlist/-/rawlist-3.0.1.tgz#729def358419cc929045f264131878ed379e0af3" + integrity sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/search@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/search/-/search-2.0.1.tgz#69b774a0a826de2e27b48981d01bc5ad81e73721" + integrity sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/select@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-3.0.1.tgz#1df9ed27fb85a5f526d559ac5ce7cc4e9dc4e7ec" + integrity sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/type@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-2.0.0.tgz#08fa513dca2cb6264fe1b0a2fabade051444e3f6" + integrity sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag== + dependencies: + mute-stream "^1.0.0" "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" @@ -544,13 +739,20 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" "@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": @@ -558,15 +760,10 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" @@ -576,31 +773,36 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@modelcontextprotocol/sdk@^1.11.5": - version "1.17.3" - resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz#cf92354220f0183d28179e96a9bf3a8f6d3211ae" - integrity sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg== +"@modelcontextprotocol/sdk@^1.27.1": + version "1.27.1" + resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz#a602cf823bf8a68e13e7112f50aeb02b09fb83b9" + integrity sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA== dependencies: - ajv "^6.12.6" + "@hono/node-server" "^1.19.9" + ajv "^8.17.1" + ajv-formats "^3.0.1" content-type "^1.0.5" cors "^2.8.5" cross-spawn "^7.0.5" eventsource "^3.0.2" eventsource-parser "^3.0.0" - express "^5.0.1" - express-rate-limit "^7.5.0" + express "^5.2.1" + express-rate-limit "^8.2.1" + hono "^4.11.4" + jose "^6.1.3" + json-schema-typed "^8.0.2" pkce-challenge "^5.0.0" raw-body "^3.0.0" - zod "^3.23.8" - zod-to-json-schema "^3.24.1" + zod "^3.25 || ^4.0" + zod-to-json-schema "^3.25.1" "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -615,7 +817,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": +"@nodelib/fs.walk@^1.2.3": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -623,10 +825,15 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@pkgr/core@^0.2.3": - version "0.2.4" - resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.4.tgz#d897170a2b0ba51f78a099edccd968f7b103387c" - integrity sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw== +"@pinojs/redact@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@pinojs/redact/-/redact-0.4.0.tgz#c3de060dd12640dcc838516aa2a6803cc7b2e9d6" + integrity sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg== + +"@pkgr/core@^0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b" + integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== "@sinclair/typebox@^0.27.8": version "0.27.8" @@ -658,9 +865,9 @@ path-browserify "^1.0.1" "@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + version "1.0.12" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.12.tgz#be57ceac1e4692b41be9de6be8c32a106636dba4" + integrity sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ== "@tsconfig/node12@^1.0.7": version "1.0.11" @@ -704,11 +911,11 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.7.tgz#968cdc2366ec3da159f61166428ee40f370e56c2" - integrity sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng== + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== dependencies: - "@babel/types" "^7.20.7" + "@babel/types" "^7.28.2" "@types/body-parser@*": version "1.19.6" @@ -725,6 +932,11 @@ dependencies: "@types/node" "*" +"@types/cookie-parser@^1.4.10": + version "1.4.10" + resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.10.tgz#a045272a383a30597a01955d4f9c790018f214e4" + integrity sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg== + "@types/cors@^2.8.19": version "2.8.19" resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342" @@ -732,10 +944,15 @@ dependencies: "@types/node" "*" +"@types/estree@^1.0.6": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + "@types/express-serve-static-core@^5.0.0": - version "5.0.7" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz#2fa94879c9d46b11a5df4c74ac75befd6b283de6" - integrity sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ== + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz#1a77faffee9572d39124933259be2523837d7eaa" + integrity sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A== dependencies: "@types/node" "*" "@types/qs" "*" @@ -743,13 +960,13 @@ "@types/send" "*" "@types/express@^5.0.3": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.3.tgz#6c4bc6acddc2e2a587142e1d8be0bce20757e956" - integrity sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw== + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.6.tgz#2d724b2c990dcb8c8444063f3580a903f6d500cc" + integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^5.0.0" - "@types/serve-static" "*" + "@types/serve-static" "^2" "@types/graceful-fs@^4.1.3": version "4.1.9" @@ -790,15 +1007,29 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== +"@types/json-schema@^7.0.15": + 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/mute-stream@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@types/mute-stream/-/mute-stream-0.0.4.tgz#77208e56a08767af6c5e1237be8888e2f255c478" + integrity sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow== + dependencies: + "@types/node" "*" "@types/node@*": - version "22.15.17" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.17.tgz#355ccec95f705b664e4332bb64a7f07db30b7055" - integrity sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw== + version "25.0.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.8.tgz#e54e00f94fe1db2497b3e42d292b8376a2678c8d" + integrity sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg== + dependencies: + undici-types "~7.16.0" + +"@types/node@^22.5.5": + version "22.19.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.6.tgz#0e9d80ebcd2dfce03265768c17a1212d4eb07e82" + integrity sha512-qm+G8HuG6hOHQigsi7VGuLjUVu6TtBo/F05zvX04Mw2uCg9Dv0Qxy3Qw7j41SidlTcl5D/5yg0SEZqOB+EqZnQ== dependencies: undici-types "~6.21.0" @@ -813,36 +1044,39 @@ integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== "@types/send@*": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" - integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" + integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== dependencies: - "@types/mime" "^1" "@types/node" "*" -"@types/serve-static@*": - version "1.15.8" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877" - integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg== +"@types/serve-static@^2": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-2.2.0.tgz#d4a447503ead0d1671132d1ab6bd58b805d8de6a" + integrity sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== dependencies: "@types/http-errors" "*" "@types/node" "*" - "@types/send" "*" "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/wrap-ansi@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" + integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== "@types/yargs@^17.0.8": - version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" - integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + version "17.0.35" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.35.tgz#07013e46aa4d7d7d50a49e15604c1c5340d4eb24" + integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== dependencies: "@types/yargs-parser" "*" @@ -927,11 +1161,6 @@ "@typescript-eslint/types" "8.31.1" eslint-visitor-keys "^4.2.0" -"@ungap/structured-clone@^1.2.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" - integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== - "@valtown/deno-http-worker@^0.0.21": version "0.0.21" resolved "https://registry.yarnpkg.com/@valtown/deno-http-worker/-/deno-http-worker-0.0.21.tgz#9ce3b5c1d0db211fe7ea8297881fe551838474ad" @@ -957,10 +1186,15 @@ acorn-walk@^8.1.1: dependencies: acorn "^8.11.0" -acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: - version "8.14.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" - integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== +acorn@^8.11.0, acorn@^8.4.1: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +acorn@^8.15.0: + version "8.16.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== aggregate-error@^3.0.0: version "3.1.0" @@ -970,17 +1204,44 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.12.4, ajv@^6.12.6: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + +ajv@^6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a" + integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-escapes@^4.2.1: +ajv@^8.0.0, ajv@^8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ajv@^8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc" + integrity sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -1029,10 +1290,10 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -async@^3.2.3: - version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== babel-jest@^29.7.0: version "29.7.0" @@ -1069,9 +1330,9 @@ babel-plugin-jest-hoist@^29.6.3: "@types/babel__traverse" "^7.0.6" babel-preset-current-node-syntax@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" - integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-bigint" "^7.8.3" @@ -1102,33 +1363,38 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -body-parser@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.0.tgz#f7a9656de305249a715b549b7b8fd1ab9dfddcfa" - integrity sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg== +baseline-browser-mapping@^2.9.0: + version "2.9.14" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz#3b6af0bc032445bca04de58caa9a87cfe921cbb3" + integrity sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg== + +body-parser@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.2.tgz#1a32cdb966beaf68de50a9dfbe5b58f83cb8890c" + integrity sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA== dependencies: bytes "^3.1.2" content-type "^1.0.5" - debug "^4.4.0" + debug "^4.4.3" http-errors "^2.0.0" - iconv-lite "^0.6.3" + iconv-lite "^0.7.0" on-finished "^2.4.1" - qs "^6.14.0" - raw-body "^3.0.0" - type-is "^2.0.0" + qs "^6.14.1" + raw-body "^3.0.1" + type-is "^2.0.1" brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== dependencies: balanced-match "^1.0.0" @@ -1140,14 +1406,15 @@ braces@^3.0.3: fill-range "^7.1.1" browserslist@^4.24.0: - version "4.24.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.5.tgz#aa0f5b8560fe81fde84c6dcb38f759bafba0e11b" - integrity sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw== + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== dependencies: - caniuse-lite "^1.0.30001716" - electron-to-chromium "^1.5.149" - node-releases "^2.0.19" - update-browserslist-db "^1.1.3" + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" + node-releases "^2.0.27" + update-browserslist-db "^1.2.0" bs-logger@^0.2.6: version "0.2.6" @@ -1168,7 +1435,7 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -bytes@3.1.2, bytes@^3.1.2: +bytes@^3.1.2, bytes@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== @@ -1204,12 +1471,12 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001716: - version "1.0.30001717" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz#5d9fec5ce09796a1893013825510678928aca129" - integrity sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw== +caniuse-lite@^1.0.30001759: + version "1.0.30001764" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz#03206c56469f236103b90f9ae10bcb8b9e1f6005" + integrity sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g== -chalk@^4.0.0, chalk@^4.0.2: +chalk@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1222,6 +1489,11 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + ci-info@^3.2.0: version "3.9.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" @@ -1237,6 +1509,11 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -1257,9 +1534,9 @@ code-block-writer@^12.0.0: integrity sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w== collect-v8-coverage@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" - integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + version "1.0.3" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz#cc1f01eb8d02298cbc9a437c74c70ab4e5210b80" + integrity sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw== color-convert@^2.0.1: version "2.0.1" @@ -1273,17 +1550,25 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +colorette@^2.0.7: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +commander@^13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46" + integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw== + 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== content-disposition@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2" - integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg== - dependencies: - safe-buffer "5.2.1" + version "1.0.1" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.1.tgz#a8b7bbeb2904befdfb6787e5c0c086959f605f9b" + integrity sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q== content-type@^1.0.5: version "1.0.5" @@ -1295,12 +1580,25 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie-parser@^1.4.6: + version "1.4.7" + resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.7.tgz#e2125635dfd766888ffe90d60c286404fa0e7b26" + integrity sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw== + dependencies: + cookie "0.7.2" + cookie-signature "1.0.6" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + cookie-signature@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== -cookie@^0.7.1: +cookie@0.7.2, cookie@^0.7.1: version "0.7.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== @@ -1331,7 +1629,7 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.5: +cross-spawn@^7.0.3, cross-spawn@^7.0.5, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -1340,17 +1638,22 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.5: shebang-command "^2.0.0" which "^2.0.1" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7, debug@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== +dateformat@^4.6.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" + integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7, debug@^4.4.0, debug@^4.4.3: + 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" dedent@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2" - integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA== + version "1.7.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.1.tgz#364661eea3d73f3faba7089214420ec2f8f13e15" + integrity sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg== deep-is@^0.1.3: version "0.1.4" @@ -1362,7 +1665,7 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -depd@2.0.0, depd@^2.0.0: +depd@^2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -1382,13 +1685,6 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" @@ -1403,17 +1699,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -ejs@^3.1.10: - version "3.1.10" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" - integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== - dependencies: - jake "^10.8.5" - -electron-to-chromium@^1.5.149: - version "1.5.151" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz#5edd6c17e1b2f14b4662c41b9379f96cc8c2bb7c" - integrity sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA== +electron-to-chromium@^1.5.263: + version "1.5.267" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz#5d84f2df8cdb6bfe7e873706bb21bd4bfb574dc7" + integrity sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw== emittery@^0.13.1: version "0.13.1" @@ -1430,10 +1719,17 @@ encodeurl@^2.0.0: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== +end-of-stream@^1.1.0: + version "1.4.5" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" + integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== + dependencies: + once "^1.4.0" + error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + version "1.3.4" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== dependencies: is-arrayish "^0.2.1" @@ -1474,106 +1770,95 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-plugin-prettier@^5.0.1: - version "5.4.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz#54d4748904e58eaf1ffe26c4bffa4986ca7f952b" - integrity sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA== +eslint-plugin-prettier@^5.4.1: + version "5.5.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz#9eae11593faa108859c26f9a9c367d619a0769c0" + integrity sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw== dependencies: - prettier-linter-helpers "^1.0.0" - synckit "^0.11.0" + prettier-linter-helpers "^1.0.1" + synckit "^0.11.12" -eslint-plugin-unused-imports@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.2.0.tgz#63a98c9ad5f622cd9f830f70bc77739f25ccfe0d" - integrity sha512-6uXyn6xdINEpxE1MtDjxQsyXB37lfyO2yKGVVgtD7WEWQGORSOZjgrD6hBhvGv4/SO+TOlS+UnC6JppRqbuwGQ== - dependencies: - eslint-rule-composer "^0.3.0" - -eslint-rule-composer@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" - integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== +eslint-plugin-unused-imports@^4.1.4: + version "4.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.4.1.tgz#a831f0a2937d7631eba30cb87091ab7d3a5da0e1" + integrity sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ== -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== +eslint-scope@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: +eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint-visitor-keys@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" - integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== - -eslint@^8.49.0: - version "8.57.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" - integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.57.1" - "@humanwhocodes/config-array" "^0.13.0" +eslint-visitor-keys@^4.2.0, eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + +eslint@^9.39.1: + version "9.39.4" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.4.tgz#855da1b2e2ad66dc5991195f35e262bcec8117b5" + integrity sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ== + dependencies: + "@eslint-community/eslint-utils" "^4.8.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.21.2" + "@eslint/config-helpers" "^0.4.2" + "@eslint/core" "^0.17.0" + "@eslint/eslintrc" "^3.3.5" + "@eslint/js" "9.39.4" + "@eslint/plugin-kit" "^0.4.1" + "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" - ajv "^6.12.4" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + ajv "^6.14.0" chalk "^4.0.0" - cross-spawn "^7.0.2" + cross-spawn "^7.0.6" debug "^4.3.2" - doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" + eslint-scope "^8.4.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" + esquery "^1.5.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" + file-entry-cache "^8.0.0" find-up "^5.0.0" glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" ignore "^5.2.0" imurmurhash "^0.1.4" is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" lodash.merge "^4.6.2" - minimatch "^3.1.2" + minimatch "^3.1.5" natural-compare "^1.4.0" optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== +espree@^10.0.1, espree@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== dependencies: - acorn "^8.9.0" + acorn "^8.15.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" + eslint-visitor-keys "^4.2.1" esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== +esquery@^1.5.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== dependencies: estraverse "^5.1.0" @@ -1599,15 +1884,10 @@ etag@^1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== -eventsource-parser@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.3.tgz#e9af1d40b77e6268cdcbc767321e8b9f066adea8" - integrity sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA== - -eventsource-parser@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.1.tgz#5e358dba9a55ba64ca90da883c4ca35bd82467bd" - integrity sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA== +eventsource-parser@^3.0.0, eventsource-parser@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz#292e165e34cacbc936c3c92719ef326d4aeb4e90" + integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== eventsource@^3.0.2: version "3.0.7" @@ -1647,23 +1927,26 @@ expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -express-rate-limit@^7.5.0: - version "7.5.0" - resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.5.0.tgz#6a67990a724b4fbbc69119419feef50c51e8b28f" - integrity sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg== +express-rate-limit@^8.2.1: + version "8.3.1" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-8.3.1.tgz#0aaba098eadd40f6737f30a98e6b16fa1a29edfb" + integrity sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw== + dependencies: + ip-address "10.1.0" -express@^5.0.1, express@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/express/-/express-5.1.0.tgz#d31beaf715a0016f0d53f47d3b4d7acf28c75cc9" - integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA== +express@^5.1.0, express@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04" + integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== dependencies: accepts "^2.0.0" - body-parser "^2.2.0" + body-parser "^2.2.1" content-disposition "^1.0.0" content-type "^1.0.5" cookie "^0.7.1" cookie-signature "^1.2.1" debug "^4.4.0" + depd "^2.0.0" encodeurl "^2.0.0" escape-html "^1.0.3" etag "^1.8.1" @@ -1685,6 +1968,20 @@ express@^5.0.1, express@^5.1.0: type-is "^2.0.1" vary "^1.1.2" +external-editor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-copy@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-4.0.2.tgz#57f14115e1edbec274f69090072a480aa29cbedd" + integrity sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw== + fast-deep-equal@^3.1.1, 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" @@ -1716,10 +2013,20 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-safe-stringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + fastq@^1.6.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" - integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + version "1.20.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== dependencies: reusify "^1.0.4" @@ -1730,19 +2037,17 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== -filelist@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" - integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== dependencies: - minimatch "^5.0.1" + flat-cache "^4.0.0" fill-range@^7.1.1: version "7.1.1" @@ -1752,9 +2057,9 @@ fill-range@^7.1.1: to-regex-range "^5.0.1" finalhandler@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.0.tgz#72306373aa89d05a8242ed569ed86a1bff7c561f" - integrity sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q== + version "2.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.1.tgz#a2c517a6559852bcdb06d1f8bd7f51b68fad8099" + integrity sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA== dependencies: debug "^4.4.0" encodeurl "^2.0.0" @@ -1779,20 +2084,27 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -flat-cache@^3.0.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" - integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== dependencies: flatted "^3.2.9" - keyv "^4.5.3" - rimraf "^3.0.2" + keyv "^4.5.4" flatted@^3.2.9: version "3.3.3" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== +flora-colossus@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/flora-colossus/-/flora-colossus-2.0.0.tgz#af1e85db0a8256ef05f3fb531c1235236c97220a" + integrity sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA== + dependencies: + debug "^4.3.4" + fs-extra "^10.1.0" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -1803,6 +2115,15 @@ fresh@^2.0.0: resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== +fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1818,6 +2139,20 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +fuse.js@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-7.1.0.tgz#306228b4befeee11e05b027087c2744158527d09" + integrity sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ== + +galactus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/galactus/-/galactus-1.0.0.tgz#c2615182afa0c6d0859b92e56ae36d052827db7e" + integrity sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ== + dependencies: + debug "^4.3.4" + flora-colossus "^2.0.0" + fs-extra "^10.1.0" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -1893,24 +2228,17 @@ glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^13.19.0: - version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== - dependencies: - type-fest "^0.20.2" +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== 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== -graceful-fs@^4.2.9: +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -1920,6 +2248,18 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +handlebars@^4.7.8: + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -1937,31 +2277,48 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" +help-me@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/help-me/-/help-me-5.0.0.tgz#b1ebe63b967b74060027c2ac61f9be12d354a6f6" + integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg== + +hono@^4.11.4, hono@^4.12.4: + version "4.12.7" + resolved "https://registry.yarnpkg.com/hono/-/hono-4.12.7.tgz#ca000956e965c2b3d791e43540498e616d6c6442" + integrity sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw== + html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -http-errors@2.0.0, http-errors@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== +http-errors@^2.0.0, http-errors@^2.0.1, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -iconv-lite@0.6.3, iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.7.0, iconv-lite@~0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e" + integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" @@ -1970,6 +2327,11 @@ ignore@^5.2.0, ignore@^5.3.1: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== +ignore@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + import-fresh@^3.2.1: version "3.3.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" @@ -2004,11 +2366,16 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3: +inherits@2, inherits@^2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ip-address@10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.1.0.tgz#d8dcffb34d0e02eb241427444a6e23f5b0595aa4" + integrity sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q== + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -2019,7 +2386,7 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-core-module@^2.16.0: +is-core-module@^2.16.1: version "2.16.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== @@ -2053,11 +2420,6 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - is-promise@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" @@ -2119,23 +2481,13 @@ istanbul-lib-source-maps@^4.0.0: source-map "^0.6.1" istanbul-reports@^3.1.3: - version "3.1.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" - integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jake@^10.8.5: - version "10.9.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" - integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== - dependencies: - async "^3.2.3" - chalk "^4.0.2" - filelist "^1.0.4" - minimatch "^3.1.2" - jest-changed-files@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" @@ -2436,7 +2788,7 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" -jest-util@^29.0.0, jest-util@^29.7.0: +jest-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== @@ -2494,6 +2846,16 @@ jest@^29.4.0: import-local "^3.0.2" jest-cli "^29.7.0" +jose@^6.1.3: + version "6.2.1" + resolved "https://registry.yarnpkg.com/jose/-/jose-6.2.1.tgz#7a6b1de83816deaee9055a558e1278a7b2b9ea1b" + integrity sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw== + +joycon@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" + integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== + "jq-web@https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz": version "0.8.8" resolved "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz#7849ef64bdfc28f70cbfc9888f886860e96da10d" @@ -2504,17 +2866,17 @@ js-tokens@^4.0.0: integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + version "3.14.2" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0" + integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== dependencies: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== +js-yaml@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== dependencies: argparse "^2.0.1" @@ -2538,6 +2900,16 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-schema-typed@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-8.0.2.tgz#e98ee7b1899ff4a184534d1f167c288c66bbeff4" + integrity sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -2548,7 +2920,16 @@ json5@^2.2.2, json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -keyv@^4.5.3: +jsonfile@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" + integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== @@ -2666,10 +3047,10 @@ mime-db@^1.54.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -mime-types@^3.0.0, mime-types@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" - integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== +mime-types@^3.0.0, mime-types@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== dependencies: mime-db "^1.54.0" @@ -2678,20 +3059,13 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.5: + 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" -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - minimatch@^7.4.3: version "7.4.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.6.tgz#845d6f254d8f4a5e4fd6baf44d5f10c8448365fb" @@ -2706,7 +3080,7 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.6: +minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -2721,6 +3095,11 @@ ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mute-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" + integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -2731,15 +3110,25 @@ negotiator@^1.0.0: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-forge@^1.3.2: + version "1.3.3" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.3.tgz#0ad80f6333b3a0045e827ac20b7f735f93716751" + integrity sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== +node-releases@^2.0.27: + version "2.0.27" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" + integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== normalize-path@^3.0.0: version "3.0.0" @@ -2763,6 +3152,11 @@ object-inspect@^1.13.3: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== +on-exit-leak-free@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" + integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== + on-finished@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -2770,7 +3164,7 @@ on-finished@^2.4.1: dependencies: ee-first "1.1.1" -once@^1.3.0, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -2796,6 +3190,11 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + p-all@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-all/-/p-all-3.0.0.tgz#077c023c37e75e760193badab2bad3ccd5782bfb" @@ -2891,9 +3290,9 @@ path-parse@^1.0.7: integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" - integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + version "8.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz#aa818a6981f99321003a08987d3cec9c3474cd1f" + integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA== picocolors@^1.1.1: version "1.1.1" @@ -2905,15 +3304,73 @@ picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pino-abstract-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz#b21e5f33a297e8c4c915c62b3ce5dd4a87a52c23" + integrity sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg== + dependencies: + split2 "^4.0.0" + +pino-http@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/pino-http/-/pino-http-11.0.0.tgz#ebadef4694fc59aadab9be7e5939aea625b4615f" + integrity sha512-wqg5XIAGRRIWtTk8qPGxkbrfiwEWz1lgedVLvhLALudKXvg1/L2lTFgTGPJ4Z2e3qcRmxoFxDuSdMdMGNM6I1g== + dependencies: + get-caller-file "^2.0.5" + pino "^10.0.0" + pino-std-serializers "^7.0.0" + process-warning "^5.0.0" + +pino-pretty@^13.1.3: + version "13.1.3" + resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-13.1.3.tgz#2274cccda925dd355c104079a5029f6598d0381b" + integrity sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg== + dependencies: + colorette "^2.0.7" + dateformat "^4.6.3" + fast-copy "^4.0.0" + fast-safe-stringify "^2.1.1" + help-me "^5.0.0" + joycon "^3.1.1" + minimist "^1.2.6" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^3.0.0" + pump "^3.0.0" + secure-json-parse "^4.0.0" + sonic-boom "^4.0.1" + strip-json-comments "^5.0.2" + +pino-std-serializers@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz#a7b0cd65225f29e92540e7853bd73b07479893fc" + integrity sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw== + +pino@^10.0.0, pino@^10.3.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/pino/-/pino-10.3.1.tgz#6552c8f8d8481844c9e452e7bf0be90bff1939ce" + integrity sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg== + dependencies: + "@pinojs/redact" "^0.4.0" + atomic-sleep "^1.0.0" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^3.0.0" + pino-std-serializers "^7.0.0" + process-warning "^5.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^4.0.1" + thread-stream "^4.0.0" + pirates@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== pkce-challenge@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pkce-challenge/-/pkce-challenge-5.0.0.tgz#c3a405cb49e272094a38e890a2b51da0228c4d97" - integrity sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ== + version "5.0.1" + resolved "https://registry.yarnpkg.com/pkce-challenge/-/pkce-challenge-5.0.1.tgz#3b4446865b17b1745e9ace2016a31f48ddf6230d" + integrity sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ== pkg-dir@^4.2.0: version "4.2.0" @@ -2927,17 +3384,22 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== +prettier-linter-helpers@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz#6a31f88a4bad6c7adda253de12ba4edaea80ebcd" + integrity sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg== dependencies: fast-diff "^1.1.2" prettier@^3.0.0: - version "3.5.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5" - integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== + version "3.7.4" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.7.4.tgz#d2f8335d4b1cec47e1c8098645411b0c9dff9c0f" + integrity sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA== + +pretty-bytes@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" @@ -2948,6 +3410,11 @@ pretty-format@^29.0.0, pretty-format@^29.7.0: ansi-styles "^5.0.0" react-is "^18.0.0" +process-warning@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-5.0.0.tgz#566e0bf79d1dff30a72d8bbbe9e8ecefe8d378d7" + integrity sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA== + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -2964,6 +3431,14 @@ proxy-addr@^2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +pump@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.4.tgz#1f313430527fa8b905622ebd22fe1444e757ab3c" + integrity sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -2974,10 +3449,10 @@ pure-rand@^6.0.0: resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== -qs@^6.14.0: - version "6.14.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" - integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== +qs@^6.14.0, qs@^6.14.1: + version "6.14.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.1.tgz#a41d85b9d3902f31d27861790506294881871159" + integrity sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ== dependencies: side-channel "^1.1.0" @@ -2986,20 +3461,25 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + range-parser@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f" - integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g== +raw-body@^3.0.0, raw-body@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.2.tgz#3e3ada5ae5568f9095d84376fd3a49b8fb000a51" + integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.6.3" - unpipe "1.0.0" + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.7.0" + unpipe "~1.0.0" react-is@^18.0.0: version "18.3.1" @@ -3015,11 +3495,21 @@ readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +real-require@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -3043,11 +3533,11 @@ resolve.exports@^2.0.0: integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== resolve@^1.20.0: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== dependencies: - is-core-module "^2.16.0" + is-core-module "^2.16.1" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -3056,13 +3546,6 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - router@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" @@ -3081,54 +3564,64 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@5.2.1, safe-buffer@~5.2.0: +safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -"safer-buffer@>= 2.1.2 < 3.0.0": +safe-stable-stringify@^2.3.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" + integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== + +"safer-buffer@>= 2.1.2 < 3", "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" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +secure-json-parse@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-4.1.0.tgz#4f1ab41c67a13497ea1b9131bb4183a22865477c" + integrity sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA== + semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.1: - version "7.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" - integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== +semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.3: + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== send@^1.1.0, send@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212" - integrity sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw== + version "1.2.1" + resolved "https://registry.yarnpkg.com/send/-/send-1.2.1.tgz#9eab743b874f3550f40a26867bf286ad60d3f3ed" + integrity sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ== dependencies: - debug "^4.3.5" + debug "^4.4.3" encodeurl "^2.0.0" escape-html "^1.0.3" etag "^1.8.1" fresh "^2.0.0" - http-errors "^2.0.0" - mime-types "^3.0.1" + http-errors "^2.0.1" + mime-types "^3.0.2" ms "^2.1.3" on-finished "^2.4.1" range-parser "^1.2.1" - statuses "^2.0.1" + statuses "^2.0.2" serve-static@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.0.tgz#9c02564ee259bdd2251b82d659a2e7e1938d66f9" - integrity sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ== + version "2.2.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.1.tgz#7f186a4a4e5f5b663ad7a4294ff1bf37cf0e98a9" + integrity sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw== dependencies: encodeurl "^2.0.0" escape-html "^1.0.3" parseurl "^1.3.3" send "^1.2.0" -setprototypeof@1.2.0: +setprototypeof@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== @@ -3190,6 +3683,11 @@ signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -3200,6 +3698,13 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +sonic-boom@^4.0.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-4.2.1.tgz#28598250df4899c0ac572d7e2f0460690ba6a030" + integrity sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q== + dependencies: + atomic-sleep "^1.0.0" + source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" @@ -3213,6 +3718,11 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +split2@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -3225,10 +3735,10 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" -statuses@2.0.1, statuses@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== string-length@^4.0.1: version "4.0.2" @@ -3288,6 +3798,11 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@^5.0.2: + version "5.0.3" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-5.0.3.tgz#b7304249dd402ee67fd518ada993ab3593458bcf" + integrity sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw== + superstruct@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" @@ -3312,13 +3827,12 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -synckit@^0.11.0: - version "0.11.4" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.4.tgz#48972326b59723fc15b8d159803cf8302b545d59" - integrity sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ== +synckit@^0.11.12: + version "0.11.12" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.12.tgz#abe74124264fbc00a48011b0d98bdc1cffb64a7b" + integrity sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ== dependencies: - "@pkgr/core" "^0.2.3" - tslib "^2.8.1" + "@pkgr/core" "^0.2.9" test-exclude@^6.0.0: version "6.0.0" @@ -3329,10 +3843,19 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thread-stream@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-4.0.0.tgz#732f007c24da7084f729d6e3a7e3f5934a7380b7" + integrity sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA== + dependencies: + real-require "^0.2.0" + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" tmpl@1.0.5: version "1.0.5" @@ -3346,30 +3869,29 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toidentifier@1.0.1: +toidentifier@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== ts-api-utils@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" - integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== + version "2.4.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8" + integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== ts-jest@^29.1.0: - version "29.3.2" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.3.2.tgz#0576cdf0a507f811fe73dcd16d135ce89f8156cb" - integrity sha512-bJJkrWc6PjFVz5g2DGCNUo8z7oFEYaz1xP1NpeDU7KNLMWPpEyV8Chbpkn8xjzgRDpQhnGMyvyldoL7h8JXyug== + version "29.4.6" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.6.tgz#51cb7c133f227396818b71297ad7409bb77106e9" + integrity sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA== dependencies: bs-logger "^0.2.6" - ejs "^3.1.10" fast-json-stable-stringify "^2.1.0" - jest-util "^29.0.0" + handlebars "^4.7.8" json5 "^2.2.3" lodash.memoize "^4.1.2" make-error "^1.3.6" - semver "^7.7.1" - type-fest "^4.39.1" + semver "^7.7.3" + type-fest "^4.41.0" yargs-parser "^21.1.1" ts-morph@^19.0.0: @@ -3440,22 +3962,17 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type-fest@^4.39.1: +type-fest@^4.41.0: version "4.41.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== -type-is@^2.0.0, type-is@^2.0.1: +type-is@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== @@ -3469,20 +3986,35 @@ typescript@5.8.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== +uglify-js@^3.1.4: + version "3.19.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + undici-types@~6.21.0: version "6.21.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== -unpipe@1.0.0: +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" - integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== +update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== dependencies: escalade "^3.2.0" picocolors "^1.1.1" @@ -3537,6 +4069,20 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -3597,22 +4143,27 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zod-to-json-schema@^3.24.1, zod-to-json-schema@^3.24.5: - version "3.24.5" - resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3" - integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== +yoctocolors-cjs@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz#7e4964ea8ec422b7a40ac917d3a344cfd2304baa" + integrity sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw== + +zod-to-json-schema@^3.24.5, zod-to-json-schema@^3.24.6, zod-to-json-schema@^3.25.1: + version "3.25.1" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz#7f24962101a439ddade2bf1aeab3c3bfec7d84ba" + integrity sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA== zod-validation-error@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.1.tgz#a105723eb40299578a6a38cb86647068f6d005b1" - integrity sha512-F3rdaCOHs5ViJ5YTz5zzRtfkQdMdIeKudJAoxy7yB/2ZMEHw73lmCAcQw11r7++20MyGl4WV59EVh7A9rNAyog== + version "4.0.2" + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.2.tgz#bc605eba49ce0fcd598c127fee1c236be3f22918" + integrity sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ== -zod@^3.23.8: - version "3.24.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.4.tgz#e2e2cca5faaa012d76e527d0d36622e0a90c315f" - integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg== +"zod@^3.25 || ^4.0": + version "4.3.5" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.5.tgz#aeb269a6f9fc259b1212c348c7c5432aaa474d2a" + integrity sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g== -zod@^3.25.20: +zod@^3.25.20, zod@^3.25.67: version "3.25.76" resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== diff --git a/release-please-config.json b/release-please-config.json index b1909804..9b042792 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -68,6 +68,11 @@ "type": "json", "path": "packages/mcp-server/package.json", "jsonpath": "$.version" + }, + { + "type": "json", + "path": "packages/mcp-server/manifest.json", + "jsonpath": "$.version" } ] } diff --git a/scripts/mock b/scripts/mock index 0b28f6ea..5cd7c157 100755 --- a/scripts/mock +++ b/scripts/mock @@ -19,23 +19,34 @@ fi echo "==> Starting mock server with URL ${URL}" -# Run prism mock on the given spec +# Run steady mock on the given spec if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & + # Pre-install the package so the download doesn't eat into the startup timeout + npm exec --package=@stdy/cli@0.20.2 -- steady --version - # Wait for server to come online + npm exec --package=@stdy/cli@0.20.2 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" &> .stdy.log & + + # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + attempts=0 + while ! curl --silent --fail "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1; do + if ! kill -0 $! 2>/dev/null; then + echo + cat .stdy.log + exit 1 + fi + attempts=$((attempts + 1)) + if [ "$attempts" -ge 300 ]; then + echo + echo "Timed out waiting for Steady server to start" + cat .stdy.log + exit 1 + fi echo -n "." sleep 0.1 done - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - echo else - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" + npm exec --package=@stdy/cli@0.20.2 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" fi diff --git a/scripts/test b/scripts/test index 7bce0516..a9d718cf 100755 --- a/scripts/test +++ b/scripts/test @@ -9,8 +9,8 @@ GREEN='\033[0;32m' YELLOW='\033[0;33m' NC='\033[0m' # No Color -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 +function steady_is_running() { + curl --silent "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1 } kill_server_on_port() { @@ -25,7 +25,7 @@ function is_overriding_api_base_url() { [ -n "$TEST_API_BASE_URL" ] } -if ! is_overriding_api_base_url && ! prism_is_running ; then +if ! is_overriding_api_base_url && ! steady_is_running ; then # When we exit this script, make sure to kill the background mock server process trap 'kill_server_on_port 4010' EXIT @@ -36,19 +36,19 @@ fi if is_overriding_api_base_url ; then echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" +elif ! steady_is_running ; then + echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Steady server" echo -e "running against your OpenAPI spec." echo echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" + echo -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.20.2 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets${NC}" echo exit 1 else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" + echo -e "${GREEN}✔ Mock steady server is running with your OpenAPI spec${NC}" echo fi diff --git a/src/client.ts b/src/client.ts index 6cfe26e2..22acb46a 100644 --- a/src/client.ts +++ b/src/client.ts @@ -11,6 +11,7 @@ import type { APIResponseProps } from './internal/parse'; import { getPlatformHeaders } from './internal/detect-platform'; import * as Shims from './internal/shims'; import * as Opts from './internal/request-options'; +import { stringifyQuery } from './internal/utils/query'; import { VERSION } from './version'; import * as Errors from './core/error'; import * as Pagination from './core/pagination'; @@ -158,7 +159,7 @@ export interface ClientOptions { * The maximum number of times that the client will retry a request in case of a * temporary failure, like a network error or a 5XX error from the server. * - * @default 2 + * @default 7 */ maxRetries?: number | undefined; @@ -219,7 +220,7 @@ export class Writer { * @param {number} [opts.timeout=3 minutes] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. * @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls. * @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. - * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. + * @param {number} [opts.maxRetries=7] - The maximum number of times the client will retry a request. * @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API. * @param {Record} opts.defaultQuery - Default query parameters to include with every request to the API. */ @@ -251,7 +252,7 @@ export class Writer { parseLogLevel(readEnv('WRITER_LOG'), "process.env['WRITER_LOG']", this) ?? defaultLogLevel; this.fetchOptions = options.fetchOptions; - this.maxRetries = options.maxRetries ?? 2; + this.maxRetries = options.maxRetries ?? 7; this.fetch = options.fetch ?? Shims.getDefaultFetch(); this.#encoder = Opts.FallbackEncoder; @@ -301,21 +302,8 @@ export class Writer { /** * Basic re-implementation of `qs.stringify` for primitive types. */ - protected stringifyQuery(query: Record): string { - return Object.entries(query) - .filter(([_, value]) => typeof value !== 'undefined') - .map(([key, value]) => { - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { - return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; - } - if (value === null) { - return `${encodeURIComponent(key)}=`; - } - throw new Errors.WriterError( - `Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`, - ); - }) - .join('&'); + protected stringifyQuery(query: object | Record): string { + return stringifyQuery(query); } private getUserAgent(): string { @@ -347,12 +335,13 @@ export class Writer { : new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); const defaultQuery = this.defaultQuery(); - if (!isEmptyObj(defaultQuery)) { - query = { ...defaultQuery, ...query }; + const pathQuery = Object.fromEntries(url.searchParams); + if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) { + query = { ...pathQuery, ...defaultQuery, ...query }; } if (typeof query === 'object' && query && !Array.isArray(query)) { - url.search = this.stringifyQuery(query as Record); + url.search = this.stringifyQuery(query); } return url.toString(); @@ -536,7 +525,7 @@ export class Writer { loggerFor(this).info(`${responseInfo} - ${retryMessage}`); const errText = await response.text().catch((err: any) => castToError(err).message); - const errJSON = safeJSON(errText); + const errJSON = safeJSON(errText) as any; const errMessage = errJSON ? undefined : errText; loggerFor(this).debug( @@ -573,9 +562,14 @@ export class Writer { getAPIList = Pagination.AbstractPage>( path: string, Page: new (...args: any[]) => PageClass, - opts?: RequestOptions, + opts?: PromiseOrValue, ): Pagination.PagePromise { - return this.requestAPIList(Page, { method: 'get', path, ...opts }); + return this.requestAPIList( + Page, + opts && 'then' in opts ? + opts.then((opts) => ({ method: 'get', path, ...opts })) + : { method: 'get', path, ...opts }, + ); } requestAPIList< @@ -583,7 +577,7 @@ export class Writer { PageClass extends Pagination.AbstractPage = Pagination.AbstractPage, >( Page: new (...args: ConstructorParameters) => PageClass, - options: FinalRequestOptions, + options: PromiseOrValue, ): Pagination.PagePromise { const request = this.makeRequest(options, null, undefined); return new Pagination.PagePromise(this as any as Writer, request, Page); @@ -596,9 +590,10 @@ export class Writer { controller: AbortController, ): Promise { const { signal, method, ...options } = init || {}; - if (signal) signal.addEventListener('abort', () => controller.abort()); + const abort = this._makeAbort(controller); + if (signal) signal.addEventListener('abort', abort, { once: true }); - const timeout = setTimeout(() => controller.abort(), ms); + const timeout = setTimeout(abort, ms); const isReadableBody = ((globalThis as any).ReadableStream && options.body instanceof (globalThis as any).ReadableStream) || @@ -675,9 +670,9 @@ export class Writer { } } - // If the API asks us to wait a certain amount of time (and it's a reasonable amount), - // just do what it says, but otherwise calculate a default - if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) { + // If the API asks us to wait a certain amount of time, just do what it + // says, but otherwise calculate a default + if (timeoutMillis === undefined) { const maxRetries = options.maxRetries ?? this.maxRetries; timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries); } @@ -687,8 +682,8 @@ export class Writer { } private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number { - const initialRetryDelay = 0.5; - const maxRetryDelay = 8.0; + const initialRetryDelay = 1.0; + const maxRetryDelay = 60.0; const numRetries = maxRetries - retriesRemaining; @@ -765,6 +760,12 @@ export class Writer { return headers.values; } + private _makeAbort(controller: AbortController) { + // note: we can't just inline this method inside `fetchWithTimeout()` because then the closure + // would capture all request options, and cause a memory leak. + return () => controller.abort(); + } + private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): { bodyHeaders: HeadersLike; body: BodyInit | undefined; @@ -797,6 +798,14 @@ export class Writer { (Symbol.iterator in body && 'next' in body && typeof body.next === 'function')) ) { return { bodyHeaders: undefined, body: Shims.ReadableStreamFrom(body as AsyncIterable) }; + } else if ( + typeof body === 'object' && + headers.values.get('content-type') === 'application/x-www-form-urlencoded' + ) { + return { + bodyHeaders: { 'content-type': 'application/x-www-form-urlencoded' }, + body: this.stringifyQuery(body), + }; } else { return this.#encoder({ body, headers }); } diff --git a/src/core/streaming.ts b/src/core/streaming.ts index 7ab14171..82383c28 100644 --- a/src/core/streaming.ts +++ b/src/core/streaming.ts @@ -57,7 +57,7 @@ export class Stream implements AsyncIterable { if (sse.event === null) { try { - yield JSON.parse(sse.data); + yield JSON.parse(sse.data) as Item; } catch (e) { logger.error(`Could not parse message into JSON:`, sse.data); logger.error(`From chunk:`, sse.raw); @@ -118,7 +118,7 @@ export class Stream implements AsyncIterable { try { for await (const line of iterLines()) { if (done) continue; - if (line) yield JSON.parse(line); + if (line) yield JSON.parse(line) as Item; } done = true; } catch (e) { diff --git a/src/internal/parse.ts b/src/internal/parse.ts index d408f6ca..7c8eb86c 100644 --- a/src/internal/parse.ts +++ b/src/internal/parse.ts @@ -43,6 +43,12 @@ export async function defaultParseResponse(client: Writer, props: APIResponse const mediaType = contentType?.split(';')[0]?.trim(); const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json'); if (isJSON) { + const contentLength = response.headers.get('content-length'); + if (contentLength === '0') { + // if there is no content we can't do anything + return undefined as T; + } + const json = await response.json(); return json as T; } diff --git a/src/internal/request-options.ts b/src/internal/request-options.ts index 56765e5a..493c1ce3 100644 --- a/src/internal/request-options.ts +++ b/src/internal/request-options.ts @@ -41,7 +41,7 @@ export type RequestOptions = { * The maximum number of times that the client will retry a request in case of a * temporary failure, like a network error or a 5XX error from the server. * - * @default 2 + * @default 7 */ maxRetries?: number; diff --git a/src/internal/utils.ts b/src/internal/utils.ts index 3cbfacce..c591353b 100644 --- a/src/internal/utils.ts +++ b/src/internal/utils.ts @@ -6,3 +6,4 @@ export * from './utils/env'; export * from './utils/log'; export * from './utils/uuid'; export * from './utils/sleep'; +export * from './utils/query'; diff --git a/src/internal/utils/query.ts b/src/internal/utils/query.ts new file mode 100644 index 00000000..31de1ae5 --- /dev/null +++ b/src/internal/utils/query.ts @@ -0,0 +1,23 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { WriterError } from '../../core/error'; + +/** + * Basic re-implementation of `qs.stringify` for primitive types. + */ +export function stringifyQuery(query: object | Record) { + return Object.entries(query) + .filter(([_, value]) => typeof value !== 'undefined') + .map(([key, value]) => { + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; + } + if (value === null) { + return `${encodeURIComponent(key)}=`; + } + throw new WriterError( + `Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`, + ); + }) + .join('&'); +} diff --git a/src/resources/files.ts b/src/resources/files.ts index 3f22a12a..4c2a2690 100644 --- a/src/resources/files.ts +++ b/src/resources/files.ts @@ -172,7 +172,7 @@ export interface FileRetryParams { export interface FileUploadParams { /** - * Body param: + * Body param */ content: Uploadable; diff --git a/src/version.ts b/src/version.ts index 539014d7..d3824b5d 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '2.3.3-rc.1'; // x-release-please-version +export const VERSION = '2.4.0-rc.1'; // x-release-please-version diff --git a/tests/api-resources/applications/jobs.test.ts b/tests/api-resources/applications/jobs.test.ts index 1c9e950a..1b53c41e 100644 --- a/tests/api-resources/applications/jobs.test.ts +++ b/tests/api-resources/applications/jobs.test.ts @@ -54,7 +54,11 @@ describe('resource jobs', () => { await expect( client.applications.jobs.list( 'application_id', - { limit: 0, offset: 0, status: 'in_progress' }, + { + limit: 0, + offset: 0, + status: 'in_progress', + }, { path: '/_stainless_unknown_path' }, ), ).rejects.toThrow(Writer.NotFoundError); diff --git a/tests/api-resources/chat.test.ts b/tests/api-resources/chat.test.ts index e55b3c10..5094a0d3 100644 --- a/tests/api-resources/chat.test.ts +++ b/tests/api-resources/chat.test.ts @@ -27,20 +27,44 @@ describe('resource chat', () => { content: 'string', graph_data: { references: { - files: [{ fileId: 'fileId', score: 0, text: 'text', cite: 'cite', page: 0 }], - web: [{ score: 0, text: 'text', title: 'title', url: 'https://example.com' }], + files: [ + { + fileId: 'fileId', + score: 0, + text: 'text', + cite: 'cite', + page: 0, + }, + ], + web: [ + { + score: 0, + text: 'text', + title: 'title', + url: 'https://example.com', + }, + ], }, sources: [{ file_id: 'file_id', snippet: 'snippet' }], status: 'processing', subqueries: [ - { answer: 'answer', query: 'query', sources: [{ file_id: 'file_id', snippet: 'snippet' }] }, + { + answer: 'answer', + query: 'query', + sources: [{ file_id: 'file_id', snippet: 'snippet' }], + }, ], }, name: 'name', refusal: 'refusal', tool_call_id: 'tool_call_id', tool_calls: [ - { id: 'id', function: { arguments: 'arguments', name: 'name' }, type: 'function', index: 0 }, + { + id: 'id', + function: { arguments: 'arguments', name: 'name' }, + type: 'function', + index: 0, + }, ], }, ], @@ -48,7 +72,10 @@ describe('resource chat', () => { logprobs: true, max_tokens: 0, n: 0, - response_format: { type: 'text', json_schema: {} }, + response_format: { + type: 'text', + json_schema: {}, + }, stop: ['string'], stream: false, stream_options: { include_usage: true }, @@ -56,7 +83,11 @@ describe('resource chat', () => { tool_choice: { value: 'none' }, tools: [ { - function: { name: 'name', description: 'description', parameters: { foo: 'bar' } }, + function: { + name: 'name', + description: 'description', + parameters: { foo: 'bar' }, + }, type: 'function', }, ], diff --git a/tests/api-resources/files.test.ts b/tests/api-resources/files.test.ts index fd4b19c2..895c0ee6 100644 --- a/tests/api-resources/files.test.ts +++ b/tests/api-resources/files.test.ts @@ -77,7 +77,7 @@ describe('resource files', () => { // requests with binary data not yet supported in test environment test.skip('upload: only required params', async () => { const responsePromise = client.files.upload({ - content: await toFile(Buffer.from('# my file contents'), 'README.md'), + content: await toFile(Buffer.from('Example data'), 'README.md'), 'Content-Disposition': 'Content-Disposition', 'Content-Type': 'Content-Type', }); @@ -93,7 +93,7 @@ describe('resource files', () => { // requests with binary data not yet supported in test environment test.skip('upload: required and optional params', async () => { const response = await client.files.upload({ - content: await toFile(Buffer.from('# my file contents'), 'README.md'), + content: await toFile(Buffer.from('Example data'), 'README.md'), 'Content-Disposition': 'Content-Disposition', 'Content-Type': 'Content-Type', graphId: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', diff --git a/tests/index.test.ts b/tests/index.test.ts index 62212fa8..ab852e55 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -87,7 +87,11 @@ describe('instantiate client', () => { error: jest.fn(), }; - const client = new Writer({ logger: logger, logLevel: 'debug', apiKey: 'My API Key' }); + const client = new Writer({ + logger: logger, + logLevel: 'debug', + apiKey: 'My API Key', + }); await forceAPIResponseForClient(client); expect(debugMock).toHaveBeenCalled(); @@ -107,7 +111,11 @@ describe('instantiate client', () => { error: jest.fn(), }; - const client = new Writer({ logger: logger, logLevel: 'info', apiKey: 'My API Key' }); + const client = new Writer({ + logger: logger, + logLevel: 'info', + apiKey: 'My API Key', + }); await forceAPIResponseForClient(client); expect(debugMock).not.toHaveBeenCalled(); @@ -157,7 +165,11 @@ describe('instantiate client', () => { }; process.env['WRITER_LOG'] = 'debug'; - const client = new Writer({ logger: logger, logLevel: 'off', apiKey: 'My API Key' }); + const client = new Writer({ + logger: logger, + logLevel: 'off', + apiKey: 'My API Key', + }); await forceAPIResponseForClient(client); expect(debugMock).not.toHaveBeenCalled(); @@ -173,7 +185,11 @@ describe('instantiate client', () => { }; process.env['WRITER_LOG'] = 'not a log level'; - const client = new Writer({ logger: logger, logLevel: 'debug', apiKey: 'My API Key' }); + const client = new Writer({ + logger: logger, + logLevel: 'debug', + apiKey: 'My API Key', + }); expect(client.logLevel).toBe('debug'); expect(warnMock).not.toHaveBeenCalled(); }); @@ -267,7 +283,11 @@ describe('instantiate client', () => { return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new Writer({ baseURL: 'http://localhost:5000/', apiKey: 'My API Key', fetch: testFetch }); + const client = new Writer({ + baseURL: 'http://localhost:5000/', + apiKey: 'My API Key', + fetch: testFetch, + }); await client.patch('/foo'); expect(capturedRequest?.method).toEqual('PATCH'); @@ -335,17 +355,21 @@ describe('instantiate client', () => { }); test('maxRetries option is correctly set', () => { - const client = new Writer({ maxRetries: 4, apiKey: 'My API Key' }); - expect(client.maxRetries).toEqual(4); + const client = new Writer({ maxRetries: 14, apiKey: 'My API Key' }); + expect(client.maxRetries).toEqual(14); // default const client2 = new Writer({ apiKey: 'My API Key' }); - expect(client2.maxRetries).toEqual(2); + expect(client2.maxRetries).toEqual(7); }); describe('withOptions', () => { test('creates a new client with overridden options', async () => { - const client = new Writer({ baseURL: 'http://localhost:5000/', maxRetries: 3, apiKey: 'My API Key' }); + const client = new Writer({ + baseURL: 'http://localhost:5000/', + maxRetries: 3, + apiKey: 'My API Key', + }); const newClient = client.withOptions({ maxRetries: 5, @@ -385,7 +409,11 @@ describe('instantiate client', () => { }); test('respects runtime property changes when creating new client', () => { - const client = new Writer({ baseURL: 'http://localhost:5000/', timeout: 1000, apiKey: 'My API Key' }); + const client = new Writer({ + baseURL: 'http://localhost:5000/', + timeout: 1000, + apiKey: 'My API Key', + }); // Modify the client properties directly after creation client.baseURL = 'http://localhost:6000/'; @@ -531,7 +559,11 @@ describe('retries', () => { return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new Writer({ apiKey: 'My API Key', timeout: 10, fetch: testFetch }); + const client = new Writer({ + apiKey: 'My API Key', + timeout: 10, + fetch: testFetch, + }); expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); expect(count).toEqual(2); @@ -561,7 +593,11 @@ describe('retries', () => { return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new Writer({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); + const client = new Writer({ + apiKey: 'My API Key', + fetch: testFetch, + maxRetries: 4, + }); expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); @@ -585,7 +621,11 @@ describe('retries', () => { capturedRequest = init; return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new Writer({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); + const client = new Writer({ + apiKey: 'My API Key', + fetch: testFetch, + maxRetries: 4, + }); expect( await client.request({ @@ -647,7 +687,11 @@ describe('retries', () => { capturedRequest = init; return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new Writer({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); + const client = new Writer({ + apiKey: 'My API Key', + fetch: testFetch, + maxRetries: 4, + }); expect( await client.request({ diff --git a/tests/stringifyQuery.test.ts b/tests/stringifyQuery.test.ts index 3bfbae8b..90cf3c26 100644 --- a/tests/stringifyQuery.test.ts +++ b/tests/stringifyQuery.test.ts @@ -1,8 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Writer } from 'writer-sdk'; - -const { stringifyQuery } = Writer.prototype as any; +import { stringifyQuery } from 'writer-sdk/internal/utils/query'; describe(stringifyQuery, () => { for (const [input, expected] of [ @@ -15,7 +13,7 @@ describe(stringifyQuery, () => { 'e=f', )}=${encodeURIComponent('g&h')}`, ], - ]) { + ] as const) { it(`${JSON.stringify(input)} -> ${expected}`, () => { expect(stringifyQuery(input)).toEqual(expected); }); diff --git a/yarn.lock b/yarn.lock index 963c6184..1595408e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,14 +7,6 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== -"@ampproject/remapping@^2.2.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" - integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - "@andrewbranch/untar.js@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz#ba9494f85eb83017c5c855763969caf1d0adea00" @@ -46,155 +38,119 @@ typescript "5.6.1-rc" validate-npm-package-name "^5.0.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" - integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.28.6.tgz#72499312ec58b1e2245ba4a4f550c132be4982f7" + integrity sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q== dependencies: - "@babel/highlight" "^7.23.4" - chalk "^2.4.2" + "@babel/helper-validator-identifier" "^7.28.5" + js-tokens "^4.0.0" + picocolors "^1.1.1" -"@babel/compat-data@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" - integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== +"@babel/compat-data@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.6.tgz#103f466803fa0f059e82ccac271475470570d74c" + integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg== "@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.6.tgz#8be77cd77c55baadcc1eae1c33df90ab6d2151d4" - integrity sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.23.5" - "@babel/generator" "^7.23.6" - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-module-transforms" "^7.23.3" - "@babel/helpers" "^7.23.6" - "@babel/parser" "^7.23.6" - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.6" - "@babel/types" "^7.23.6" + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.6.tgz#531bf883a1126e53501ba46eb3bb414047af507f" + integrity sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/generator" "^7.28.6" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + "@jridgewell/remapping" "^2.3.5" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.23.6", "@babel/generator@^7.7.2": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" - integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== +"@babel/generator@^7.28.6", "@babel/generator@^7.7.2": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.6.tgz#48dcc65d98fcc8626a48f72b62e263d25fc3c3f1" + integrity sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw== + dependencies: + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== dependencies: - "@babel/types" "^7.23.6" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" -"@babel/helper-compilation-targets@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" - integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== +"@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== dependencies: - "@babel/compat-data" "^7.23.5" - "@babel/helper-validator-option" "^7.23.5" - browserslist "^4.22.2" - lru-cache "^5.1.1" - semver "^6.3.1" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-imports@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" - integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== - dependencies: - "@babel/types" "^7.22.15" - -"@babel/helper-module-transforms@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" - integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.20" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" - integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== - -"@babel/helper-simple-access@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" - integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" - integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== - -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helper-validator-option@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" - integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== - -"@babel/helpers@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.6.tgz#d03af2ee5fb34691eec0cda90f5ecbb4d4da145a" - integrity sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA== - dependencies: - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.6" - "@babel/types" "^7.23.6" - -"@babel/highlight@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" - integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.28.6", "@babel/helper-plugin-utils@^7.8.0": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" + integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" - integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.6.tgz#fca903a313ae675617936e8998b814c415cbf5d7" + integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw== + dependencies: + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd" + integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ== + dependencies: + "@babel/types" "^7.28.6" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -210,14 +166,28 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-class-properties@^7.8.3": +"@babel/plugin-syntax-class-properties@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" -"@babel/plugin-syntax-import-meta@^7.8.3": +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503" + integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== @@ -232,13 +202,13 @@ "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-jsx@^7.7.2": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz#8f2e4f8a9b5f9aa16067e142c1ac9cd9f810f473" - integrity sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee" + integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== @@ -252,7 +222,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-numeric-separator@^7.8.3": +"@babel/plugin-syntax-numeric-separator@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== @@ -280,7 +250,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-top-level-await@^7.8.3": +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== @@ -288,45 +265,41 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.7.2": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz#24f460c85dbbc983cd2b9c4994178bcc01df958f" - integrity sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/template@^7.22.15", "@babel/template@^7.3.3": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/traverse@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.6.tgz#b53526a2367a0dd6edc423637f3d2d0f2521abc5" - integrity sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ== - dependencies: - "@babel/code-frame" "^7.23.5" - "@babel/generator" "^7.23.6" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.6" - "@babel/types" "^7.23.6" + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz#c7b2ddf1d0a811145b1de800d1abd146af92e3a2" + integrity sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/template@^7.28.6", "@babel/template@^7.3.3": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/traverse@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.6.tgz#871ddc79a80599a5030c53b1cc48cbe3a5583c2e" + integrity sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/generator" "^7.28.6" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.6" + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" debug "^4.3.1" - globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.3.3": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" - integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.6", "@babel/types@^7.3.3": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df" + integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg== dependencies: - "@babel/helper-string-parser" "^7.23.4" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" "@bcoe/v8-coverage@^0.2.3": version "0.2.3" @@ -350,45 +323,52 @@ dependencies: "@cspotcode/source-map-consumer" "0.8.0" -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": +"@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" +"@eslint-community/eslint-utils@^4.8.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" + integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== + dependencies: + eslint-visitor-keys "^3.4.3" + "@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": version "4.12.1" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@eslint/config-array@^0.19.0": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" - integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== +"@eslint/config-array@^0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713" + integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA== dependencies: - "@eslint/object-schema" "^2.1.6" + "@eslint/object-schema" "^2.1.7" debug "^4.3.1" minimatch "^3.1.2" -"@eslint/core@^0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.10.0.tgz#23727063c21b335f752dbb3a16450f6f9cbc9091" - integrity sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw== +"@eslint/config-helpers@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda" + integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== dependencies: - "@types/json-schema" "^7.0.15" + "@eslint/core" "^0.17.0" -"@eslint/core@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.11.0.tgz#7a9226e850922e42cbd2ba71361eacbe74352a12" - integrity sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA== +"@eslint/core@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.17.0.tgz#77225820413d9617509da9342190a2019e78761c" + integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== dependencies: "@types/json-schema" "^7.0.15" -"@eslint/eslintrc@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" - integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== +"@eslint/eslintrc@^3.3.1": + version "3.3.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz#26393a0806501b5e2b6a43aa588a4d8df67880ac" + integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -396,26 +376,26 @@ globals "^14.0.0" ignore "^5.2.0" import-fresh "^3.2.1" - js-yaml "^4.1.0" + js-yaml "^4.1.1" minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.20.0": - version "9.20.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.20.0.tgz#7421bcbe74889fcd65d1be59f00130c289856eb4" - integrity sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ== +"@eslint/js@9.39.1": + version "9.39.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.1.tgz#0dd59c3a9f40e3f1882975c321470969243e0164" + integrity sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw== -"@eslint/object-schema@^2.1.6": - version "2.1.6" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" - integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== +"@eslint/object-schema@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" + integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== -"@eslint/plugin-kit@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz#ee07372035539e7847ef834e3f5e7b79f09e3a81" - integrity sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A== +"@eslint/plugin-kit@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz#9779e3fd9b7ee33571a57435cf4335a1794a6cb2" + integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== dependencies: - "@eslint/core" "^0.10.0" + "@eslint/core" "^0.17.0" levn "^0.4.1" "@humanfs/core@^0.19.1": @@ -441,10 +421,10 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== -"@humanwhocodes/retry@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" - integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== +"@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" @@ -661,31 +641,38 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" "@jridgewell/resolve-uri@^3.1.0": version "3.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": +"@jridgewell/sourcemap-codec@^1.4.14": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18": version "0.3.20" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== @@ -693,6 +680,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@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" @@ -1057,6 +1052,11 @@ acorn@^8.14.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== +acorn@^8.15.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + acorn@^8.4.1: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" @@ -1104,13 +1104,6 @@ ansi-regex@^6.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -1188,22 +1181,25 @@ babel-plugin-jest-hoist@^29.6.3: "@types/babel__traverse" "^7.0.6" babel-preset-current-node-syntax@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" - integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" babel-preset-jest@^29.6.3: version "29.6.3" @@ -1218,18 +1214,15 @@ balanced-match@^1.0.0: 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.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" +baseline-browser-mapping@^2.9.0: + version "2.9.14" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz#3b6af0bc032445bca04de58caa9a87cfe921cbb3" + integrity sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg== -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== +brace-expansion@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.3.tgz#0493338bdd58e319b1039c67cf7ee439892c01d9" + integrity sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA== dependencies: balanced-match "^1.0.0" @@ -1240,15 +1233,16 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browserslist@^4.22.2: - version "4.22.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" - integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== +browserslist@^4.24.0: + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== dependencies: - caniuse-lite "^1.0.30001565" - electron-to-chromium "^1.4.601" - node-releases "^2.0.14" - update-browserslist-db "^1.0.13" + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" + node-releases "^2.0.27" + update-browserslist-db "^1.2.0" bs-logger@0.x: version "0.2.6" @@ -1284,19 +1278,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001565: - version "1.0.30001570" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz#b4e5c1fa786f733ab78fc70f592df6b3f23244ca" - integrity sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw== - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" +caniuse-lite@^1.0.30001759: + version "1.0.30001764" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz#03206c56469f236103b90f9ae10bcb8b9e1f6005" + integrity sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g== chalk@^4.0.0, chalk@^4.1.2: version "4.1.2" @@ -1385,13 +1370,6 @@ collect-v8-coverage@^1.0.0: resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -1399,11 +1377,6 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" @@ -1414,11 +1387,6 @@ commander@^10.0.1: resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== -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== - convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" @@ -1495,10 +1463,10 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -electron-to-chromium@^1.4.601: - version "1.4.614" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.614.tgz#2fe789d61fa09cb875569f37c309d0c2701f91c0" - integrity sha512-X4ze/9Sc3QWs6h92yerwqv7aB/uU8vCjZcrMjA8N9R1pjMFRe44dLsck5FzLilOYvcXuDn93B+bpGYyufc70gQ== +electron-to-chromium@^1.5.263: + version "1.5.267" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz#5d84f2df8cdb6bfe7e873706bb21bd4bfb574dc7" + integrity sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw== emittery@^0.13.1: version "0.13.1" @@ -1532,10 +1500,10 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== escape-string-regexp@^2.0.0: version "2.0.0" @@ -1560,15 +1528,15 @@ eslint-plugin-unused-imports@^4.1.4: resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz#62ddc7446ccbf9aa7b6f1f0b00a980423cda2738" integrity sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ== -eslint-scope@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" - integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== +eslint-scope@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0: +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== @@ -1578,31 +1546,36 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -eslint@^9.20.1: - version "9.20.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.1.tgz#923924c078f5226832449bac86662dd7e53c91d6" - integrity sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g== +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + +eslint@^9.39.1: + version "9.39.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.1.tgz#be8bf7c6de77dcc4252b5a8dcb31c2efff74a6e5" + integrity sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g== dependencies: - "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/eslint-utils" "^4.8.0" "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.19.0" - "@eslint/core" "^0.11.0" - "@eslint/eslintrc" "^3.2.0" - "@eslint/js" "9.20.0" - "@eslint/plugin-kit" "^0.2.5" + "@eslint/config-array" "^0.21.1" + "@eslint/config-helpers" "^0.4.2" + "@eslint/core" "^0.17.0" + "@eslint/eslintrc" "^3.3.1" + "@eslint/js" "9.39.1" + "@eslint/plugin-kit" "^0.4.1" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.1" + "@humanwhocodes/retry" "^0.4.2" "@types/estree" "^1.0.6" - "@types/json-schema" "^7.0.15" ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.6" debug "^4.3.2" escape-string-regexp "^4.0.0" - eslint-scope "^8.2.0" - eslint-visitor-keys "^4.2.0" - espree "^10.3.0" + eslint-scope "^8.4.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" esquery "^1.5.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -1618,7 +1591,7 @@ eslint@^9.20.1: natural-compare "^1.4.0" optionator "^0.9.3" -espree@^10.0.1, espree@^10.3.0: +espree@^10.0.1: version "10.3.0" resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== @@ -1627,6 +1600,15 @@ espree@^10.0.1, espree@^10.3.0: acorn-jsx "^5.3.2" eslint-visitor-keys "^4.2.0" +espree@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + dependencies: + acorn "^8.15.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.1" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -1864,11 +1846,6 @@ glob@^8.0.1: minimatch "^5.0.1" once "^1.3.0" -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - globals@^14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" @@ -1884,11 +1861,6 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -2440,24 +2412,24 @@ js-tokens@^4.0.0: integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + version "3.14.2" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0" + integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== dependencies: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== +js-yaml@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== dependencies: argparse "^2.0.1" -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== json-buffer@3.0.1: version "3.0.1" @@ -2622,26 +2594,12 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2, minimatch@^5.0.1, minimatch@^9.0.4, minimatch@^9.0.5: + version "9.0.9" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" + integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" + brace-expansion "^2.0.2" minimist@^1.2.6: version "1.2.6" @@ -2692,10 +2650,10 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" - integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +node-releases@^2.0.27: + version "2.0.27" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" + integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== normalize-path@^3.0.0: version "3.0.0" @@ -2863,11 +2821,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -3181,13 +3134,6 @@ superstruct@^1.0.4: resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -3250,11 +3196,6 @@ tmpl@1.0.5: resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - 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" @@ -3380,13 +3321,13 @@ unicode-emoji-modifier-base@^1.0.0: resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459" integrity sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g== -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== +update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.2.0" + picocolors "^1.1.1" uri-js@^4.2.2: version "4.4.1"