diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5595bd6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint-and-test: + name: Lint + Test (Python ${{ matrix.python }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python: ["3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + + - name: Setup Node (for @archastro/channel-harness + prism) + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - run: npm ci + + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + + - name: Install project + dev deps + run: uv sync --all-extras + + - name: Ruff lint + run: uv run ruff check + + - name: Ruff format check + run: uv run ruff format --check + + - name: Unit tests + run: uv run pytest tests/test_http_client.py src/phx_channel/tests/test_unit.py + + - name: Harness-client integration tests + run: uv run pytest tests/harness + + - name: Contract tests (REST + channels over real harness subprocess) + run: uv run pytest tests/contract + env: + ARCHASTRO_RUN_CHANNEL_CONTRACT_TESTS: "1" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85d1552 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Python +__pycache__/ +*.pyc +*.pyo +*.egg-info/ +dist/ +build/ +.pytest_cache/ +.ruff_cache/ +.venv/ + +# Node tooling (for @archastro/channel-harness + prism) +node_modules/ + +# OS / editor +.DS_Store +.vscode/ +.idea/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f3157a --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# archastro-python + +Python SDK for the ArchAstro Platform API. + +```bash +uv add archastro-platform-sdk # or: pip install archastro-platform-sdk +``` + +```python +from archastro import ArchAstro + +client = ArchAstro(api_key="pk_...") +teams = client.v1.teams.list() +``` + +## Packages + +- **`archastro`** — typed REST + channel SDK generated from the canonical + OpenAPI spec at [`ArchAstro/archastro-openapi`](https://github.com/ArchAstro/archastro-openapi). + Pydantic models, async channel classes, auth helpers. +- **`phx_channel`** — the hand-written Phoenix Channels client the + generated channel classes run on top of. WebSocket transport, join / + reply / push / leave, heartbeat, reconnect, and a `HarnessServiceClient` + for driving the + [`@archastro/channel-harness`](https://www.npmjs.com/package/@archastro/channel-harness) + service from Python tests. + +## Development + +This repo contains: + +- Python SDK (`src/archastro/`, `src/phx_channel/`) installed via `uv` +- JS tooling (`package.json`) — the channel-harness subprocess that + powers the channel contract tests, plus the Prism mock server that + backs the REST contract tests. Installed via `npm ci`. + +### Setup + +```bash +npm ci # channel-harness + prism (for contract tests) +uv sync # Python deps + dev deps (pytest, ruff) +``` + +### Running tests + +```bash +# Unit tests only (no external services needed) +uv run pytest tests/test_http_client.py tests/test_synthetic.py src/phx_channel/tests/unit.test.py + +# REST contract tests (spawns Prism mock server) +uv run pytest tests/contract + +# REST + channel contract tests (also spawns channel-harness subprocess) +ARCHASTRO_RUN_CHANNEL_CONTRACT_TESTS=1 uv run pytest tests/contract +``` + +### Regenerating the SDK + +The typed SDK — `src/archastro/platform/` and `tests/contract/` — is +regenerated from the canonical OpenAPI spec by +[`@archastro/sdk-generator`](https://www.npmjs.com/package/@archastro/sdk-generator). +Don't hand-edit files with the `auto-generated by @archastro/sdk-generator` +header; they'll be overwritten. + +```bash +./scripts/regenerate_sdk.sh +``` + +The script fetches the spec from `ArchAstro/archastro-openapi@main` and +runs the generator via `npx`. Knobs: + +- `ARCHASTRO_OPENAPI_REF=some-branch ./scripts/regenerate_sdk.sh` — pull + the spec from a non-default ref (useful when a spec change is on a + branch awaiting merge). +- `ARCHASTRO_SDK_GENERATOR=@archastro/sdk-generator@0.1.0 ./scripts/regenerate_sdk.sh` + — pin the generator version for a release branch. + +After regenerating, review the diff, run the full test suite, and commit. + +## Release + +```bash +# bump version in pyproject.toml, then: +uv build +uv publish +``` diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..10dc08f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2243 @@ +{ + "name": "archastro-python-tooling", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "archastro-python-tooling", + "version": "0.0.0", + "devDependencies": { + "@archastro/channel-harness": "^0.1.0", + "@stoplight/prism-cli": "5.14.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@archastro/channel-harness": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@archastro/channel-harness/-/channel-harness-0.1.0.tgz", + "integrity": "sha512-dIhhOoO22c4UCudOs/zETQ6if1Ip/8NCjyjCGtCVtJKoM9tDZQQ54KXPhCg2wETMtUScZ++6wqj47M4gTuyb1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@archastro/sdk-generator": "^0.1.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "ws": "^8.18.0", + "yaml": "^2.4.0" + }, + "bin": { + "channel-harness": "dist/bin.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@archastro/sdk-generator": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@archastro/sdk-generator/-/sdk-generator-0.1.0.tgz", + "integrity": "sha512-4TOG30Fwvzlv7huhXKFnXq3kyS8Dc0d9nJrmei9cSYL7Fx6S+hyfQVp+B9KVOBHtXcqg26bDzPDQD4k3CPRL5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "yaml": "^2.4.0" + }, + "bin": { + "sdk-generator": "dist/index.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@faker-js/faker": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-6.3.1.tgz", + "integrity": "sha512-8YXBE2ZcU/pImVOHX7MWrSR/X5up7t6rPWZlk34RwZEcdr3ua6X+32pSd6XuOQRN+vbuvYNfA6iey8NbrjuMFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@stoplight/http-spec": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@stoplight/http-spec/-/http-spec-7.1.0.tgz", + "integrity": "sha512-Z2XqKX2SV8a1rrgSzFqccX2TolfcblT+l4pNvUU+THaLl50tKDoeidwWWZTzYUzqU0+UV97ponvqEbWWN3PaXg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@stoplight/json": "^3.18.1", + "@stoplight/json-schema-generator": "1.0.2", + "@stoplight/types": "14.1.0", + "@types/json-schema": "7.0.11", + "@types/swagger-schema-official": "~2.0.22", + "@types/type-is": "^1.6.3", + "fnv-plus": "^1.3.1", + "lodash": "^4.17.21", + "openapi3-ts": "^2.0.2", + "postman-collection": "^4.1.3", + "tslib": "^2.6.2", + "type-is": "^1.6.18" + }, + "engines": { + "node": ">=14.13" + } + }, + "node_modules/@stoplight/http-spec/node_modules/@stoplight/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-14.1.0.tgz", + "integrity": "sha512-fL8Nzw03+diALw91xHEHA5Q0WCGeW9WpPgZQjodNUWogAgJ56aJs03P9YzsQ1J6fT7/XjDqHMgn7/RlsBzB/SQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + } + }, + "node_modules/@stoplight/json": { + "version": "3.21.7", + "resolved": "https://registry.npmjs.org/@stoplight/json/-/json-3.21.7.tgz", + "integrity": "sha512-xcJXgKFqv/uCEgtGlPxy3tPA+4I+ZI4vAuMJ885+ThkTHFVkC+0Fm58lA9NlsyjnkpxFh4YiQWpH+KefHdbA0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@stoplight/ordered-object-literal": "^1.0.3", + "@stoplight/path": "^1.3.2", + "@stoplight/types": "^13.6.0", + "jsonc-parser": "~2.2.1", + "lodash": "^4.17.21", + "safe-stable-stringify": "^1.1" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/@stoplight/json-schema-generator": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@stoplight/json-schema-generator/-/json-schema-generator-1.0.2.tgz", + "integrity": "sha512-FzSLFoIZc6Lmw3oRE7kU6YUrl5gBmUs//rY59jdFipBoSyTPv5NyqeyTg5mvT6rY1F3qTLU3xgzRi/9Pb9eZpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-fetch": "^3.1.5", + "json-promise": "1.1.x", + "minimist": "1.2.6", + "mkdirp": "0.5.x", + "pretty-data": "0.40.x" + }, + "bin": { + "json-schema-generator": "bin/cli.js" + } + }, + "node_modules/@stoplight/json-schema-merge-allof": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@stoplight/json-schema-merge-allof/-/json-schema-merge-allof-0.7.8.tgz", + "integrity": "sha512-JTDt6GYpCWQSb7+UW1P91IAp/pcLWis0mmEzWVFcLsrNgtUYK7JLtYYz0ZPSR4QVL0fJ0YQejM+MPq5iNDFO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "compute-lcm": "^1.1.0", + "json-schema-compare": "^0.2.2", + "lodash": "^4.17.4" + } + }, + "node_modules/@stoplight/json-schema-ref-parser": { + "version": "9.2.7", + "resolved": "https://registry.npmjs.org/@stoplight/json-schema-ref-parser/-/json-schema-ref-parser-9.2.7.tgz", + "integrity": "sha512-1vNzJ7iSrFTAFNbZHPyhI6GiJJw74+WaV61bARUQEDR4Jm80f9s0Tq9uCvGoMYwIFmWDJAoTiyegnUs6SvVxDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@stoplight/path": "^1.3.2", + "@stoplight/yaml": "^4.0.2", + "call-me-maybe": "^1.0.1", + "fastestsmallesttextencoderdecoder": "^1.0.22", + "isomorphic-fetch": "^3.0.0", + "node-abort-controller": "^3.0.1" + } + }, + "node_modules/@stoplight/json-schema-sampler": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@stoplight/json-schema-sampler/-/json-schema-sampler-0.3.0.tgz", + "integrity": "sha512-G7QImi2xr9+8iPEg0D9YUi1BWhIiiEm19aMb91oWBSdxuhezOAqqRP3XNY6wczHV9jLWW18f+KkghTy9AG0BQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.7", + "json-pointer": "^0.6.1" + } + }, + "node_modules/@stoplight/json/node_modules/@stoplight/types": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-13.20.0.tgz", + "integrity": "sha512-2FNTv05If7ib79VPDA/r9eUet76jewXFH2y2K5vuge6SXbRHtWBhcaRmu+6QpF4/WRNoJj5XYRSwLGXDxysBGA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + } + }, + "node_modules/@stoplight/ordered-object-literal": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.5.tgz", + "integrity": "sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/@stoplight/path": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@stoplight/path/-/path-1.3.2.tgz", + "integrity": "sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/@stoplight/prism-cli": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/@stoplight/prism-cli/-/prism-cli-5.14.2.tgz", + "integrity": "sha512-S/x47zQa7NgoGAD0Q1JlijV7GRDd1zP/FcIpxSh+cJRUUImfALJJm1R3ONLweP97oG/b9BrwRyC+0GNYuzrviw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@stoplight/json": "3.21.7", + "@stoplight/json-schema-ref-parser": "9.2.7", + "@stoplight/prism-core": "^5.8.0", + "@stoplight/prism-http": "5.12.0", + "@stoplight/prism-http-server": "^5.12.0", + "@stoplight/types": "^14.1.0", + "chalk": "^4.1.2", + "chokidar": "^3.5.2", + "fp-ts": "^2.11.5", + "json-schema-faker": "0.5.8", + "jsonrepair": "^3.12.0", + "lodash": "^4.17.21", + "node-fetch": "^2.6.5", + "pino": "^6.13.3", + "signale": "^1.4.0", + "split2": "^4.2.0", + "tslib": "^2.3.1", + "uri-template-lite": "^22.9.0", + "urijs": "^1.19.11", + "yargs": "^16.2.0" + }, + "bin": { + "prism": "dist/index.js" + }, + "engines": { + "node": ">=18.20.1" + } + }, + "node_modules/@stoplight/prism-core": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@stoplight/prism-core/-/prism-core-5.8.0.tgz", + "integrity": "sha512-fmH7n6e0thzOGcD5uZBu/Xx1iFNfpc9ACTxPie+lFD54SJ214M2FIFXD7kV+NCFlC+w5OFw+lJRaYM859uMnAg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "fp-ts": "^2.11.5", + "lodash": "^4.17.21", + "pino": "^6.13.3", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=18.20.1" + } + }, + "node_modules/@stoplight/prism-http": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/@stoplight/prism-http/-/prism-http-5.12.0.tgz", + "integrity": "sha512-H+B/SO4SgQ6DT3CHIDCMQFGOe48Yecj0Eu+6rXwrs5m1JFyA2nlDwz+r73QJLGQanN4Biod2s0V9pZRcs2JnPA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@faker-js/faker": "^6.0.0", + "@stoplight/http-spec": "^7.0.3", + "@stoplight/json": "3.21.7", + "@stoplight/json-schema-merge-allof": "0.7.8", + "@stoplight/json-schema-ref-parser": "9.2.7", + "@stoplight/json-schema-sampler": "0.3.0", + "@stoplight/prism-core": "^5.8.0", + "@stoplight/types": "^14.1.0", + "@stoplight/yaml": "^4.2.3", + "abstract-logging": "^2.0.1", + "accepts": "^1.3.7", + "ajv": "^8.4.0", + "ajv-formats": "^2.1.1", + "caseless": "^0.12.0", + "chalk": "^4.1.2", + "content-type": "^1.0.4", + "fp-ts": "^2.11.5", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "json-schema-faker": "0.5.8", + "lodash": "^4.17.21", + "node-fetch": "^2.6.5", + "parse-multipart-data": "^1.5.0", + "pino": "^6.13.3", + "seedrandom": "^3.0.5", + "tslib": "^2.3.1", + "type-is": "^1.6.18", + "uri-template-lite": "^22.9.0", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=18.20.1" + } + }, + "node_modules/@stoplight/prism-http-server": { + "version": "5.12.2", + "resolved": "https://registry.npmjs.org/@stoplight/prism-http-server/-/prism-http-server-5.12.2.tgz", + "integrity": "sha512-h7MpOuv/WPvf4MhQmXw3CygAZp64Ts0SOM4BdoafcgAOJZyvRAOjUNJeelGJsHYdPK0aB9NZsqsaKBtNfkYj+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@stoplight/prism-core": "^5.8.0", + "@stoplight/prism-http": "^5.12.0", + "@stoplight/types": "^14.1.0", + "fast-xml-parser": "^4.2.0", + "fp-ts": "^2.11.5", + "io-ts": "^2.2.16", + "lodash": "^4.17.21", + "micri": "^4.3.0", + "node-fetch": "^2.6.5", + "parse-prefer-header": "1.0.0", + "tslib": "^2.3.1", + "type-is": "^1.6.18" + }, + "engines": { + "node": ">=18.20.1" + } + }, + "node_modules/@stoplight/prism-http/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@stoplight/types": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-14.1.1.tgz", + "integrity": "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + } + }, + "node_modules/@stoplight/yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@stoplight/yaml/-/yaml-4.3.0.tgz", + "integrity": "sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@stoplight/ordered-object-literal": "^1.0.5", + "@stoplight/types": "^14.1.1", + "@stoplight/yaml-ast-parser": "0.0.50", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=10.8" + } + }, + "node_modules/@stoplight/yaml-ast-parser": { + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@stoplight/yaml-ast-parser/-/yaml-ast-parser-0.0.50.tgz", + "integrity": "sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/swagger-schema-official": { + "version": "2.0.25", + "resolved": "https://registry.npmjs.org/@types/swagger-schema-official/-/swagger-schema-official-2.0.25.tgz", + "integrity": "sha512-T92Xav+Gf/Ik1uPW581nA+JftmjWPgskw/WBf4TJzxRG/SJ+DfNnNE+WuZ4mrXuzflQMqMkm1LSYjzYW7MB1Cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/type-is": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@types/type-is/-/type-is-1.6.7.tgz", + "integrity": "sha512-gEsh7n8824nusZ2Sidh6POxNsIdTSvIAl5gXbeFj+TUaD1CO2r4i7MQYNMfEQkChU42s2bVWAda6x6BzIhtFbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/charset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz", + "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/compute-gcd": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz", + "integrity": "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==", + "dev": true, + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-function": "^1.0.2", + "validate.io-integer-array": "^1.0.0" + } + }, + "node_modules/compute-lcm": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz", + "integrity": "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==", + "dev": true, + "dependencies": { + "compute-gcd": "^1.2.1", + "validate.io-array": "^1.0.3", + "validate.io-function": "^1.0.2", + "validate.io-integer-array": "^1.0.0" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-parser": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.6.tgz", + "integrity": "sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastestsmallesttextencoderdecoder": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", + "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flatstr": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", + "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fnv-plus": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fnv-plus/-/fnv-plus-1.3.1.tgz", + "integrity": "sha512-Gz1EvfOneuFfk4yG458dJ3TLJ7gV19q3OM/vVvvHf7eT02Hm1DleB4edsia6ahbKgAYxO9gvyQ1ioWZR+a00Yw==", + "dev": true, + "license": "MIT" + }, + "node_modules/foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/format-util": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", + "integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fp-ts": { + "version": "2.16.11", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.11.tgz", + "integrity": "sha512-LaI+KaX2NFkfn1ZGHoKCmcfv7yrZsC3b8NtWsTVQeHkq4F27vI5igUuO53sxqDEa2gNQMHFPmpojDw/1zmUK7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handler-agent": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/handler-agent/-/handler-agent-0.2.0.tgz", + "integrity": "sha512-cUduQxa5p3TFtGmb55mrRbkk/3EJCsLSeFrCIuTakQHQlYVWXeW2L9IUQUHyoHLI4UgpBNaN2JrZ0He1jPu+vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-reasons": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/http-reasons/-/http-reasons-0.1.0.tgz", + "integrity": "sha512-P6kYh0lKZ+y29T2Gqz+RlC9WBLhKe8kDmcJ+A+611jFfxdPsbMRQ5aNmFRM3lENqFkK+HTTL+tlQviAiv0AbLQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/io-ts": { + "version": "2.2.22", + "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.22.tgz", + "integrity": "sha512-FHCCztTkHoV9mdBsHpocLpdTAfh956ZQcIkWQxxS0U5HT53vtrcuYdQneEJKH6xILaLNzXVl2Cvwtoy8XNN0AA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "fp-ts": "^2.5.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "foreach": "^2.0.4" + } + }, + "node_modules/json-promise": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/json-promise/-/json-promise-1.1.8.tgz", + "integrity": "sha512-rz31P/7VfYnjQFrF60zpPTT0egMPlc8ZvIQHWs4ZtNZNnAXRmXo6oS+6eyWr5sEMG03OVhklNrTXxiIRYzoUgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "*" + } + }, + "node_modules/json-schema-compare": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz", + "integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.4" + } + }, + "node_modules/json-schema-faker": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/json-schema-faker/-/json-schema-faker-0.5.8.tgz", + "integrity": "sha512-sqzPEbEDlpiH8U1tfmJHScXHy52onvMxITPsHyhe/jhS83g8TX6ruvRqt/ot1bXUPRsh7Ps1sWqJiBxIXmW5Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-ref-parser": "^6.1.0", + "jsonpath-plus": "^10.1.0" + }, + "bin": { + "jsf": "bin/gen.cjs" + } + }, + "node_modules/json-schema-ref-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-6.1.0.tgz", + "integrity": "sha512-pXe9H1m6IgIpXmE5JSb8epilNTGsmTb2iPohAXpOdhqGFbQjNeHHsZxU+C8w6T81GZxSPFLeUoqDJmzxx5IGuw==", + "deprecated": "Please switch to @apidevtools/json-schema-ref-parser", + "dev": true, + "license": "MIT", + "dependencies": { + "call-me-maybe": "^1.0.1", + "js-yaml": "^3.12.1", + "ono": "^4.0.11" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonc-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", + "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonpath-plus": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.4.0.tgz", + "integrity": "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/jsonrepair": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.14.0.tgz", + "integrity": "sha512-tWPGKMZf/8UPim+fcW2EfcQ/d/7aKUrP6IECz9G3Tu6Q5dX0orSleqJ9z6sSw7qrQkjF8/Edo4DvsWBZ8H+HNg==", + "dev": true, + "license": "ISC", + "bin": { + "jsonrepair": "bin/cli.js" + } + }, + "node_modules/liquid-json": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/liquid-json/-/liquid-json-0.3.1.tgz", + "integrity": "sha512-wUayTU8MS827Dam6MxgD72Ui+KOSF+u/eIqpatOtjnvgJ0+mnDq33uC2M7J0tPK+upe/DpUAuK4JUU89iBoNKQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micri": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/micri/-/micri-4.5.1.tgz", + "integrity": "sha512-AtvnSBGFglNr+iqs5gufpHT9xRXUabgu9vYEnQYPXSBs+nLSBvmUS5Mzg+3LJ9eQBrNA1o5M49WeqiX1f+d2sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "handler-agent": "0.2.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-format": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mime-format/-/mime-format-2.0.1.tgz", + "integrity": "sha512-XxU3ngPbEnrYnNbIX+lYSaYg0M01v6p2ntd2YaFksTu0vayaw5OJvbdRyWs07EYRlLED5qadUZ+xo+XhOvFhwg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "charset": "^1.0.0" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ono": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz", + "integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "format-util": "^1.0.3" + } + }, + "node_modules/openapi3-ts": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-2.0.2.tgz", + "integrity": "sha512-TxhYBMoqx9frXyOgnRHufjQfPXomTIHYKhSKJ6jHfj13kS8OEIhvmE8CTuQyKtjjWttAjX5DPxM1vmalEpo8Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "yaml": "^1.10.2" + } + }, + "node_modules/openapi3-ts/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/parse-multipart-data": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/parse-multipart-data/-/parse-multipart-data-1.5.0.tgz", + "integrity": "sha512-ck5zaMF0ydjGfejNMnlo5YU2oJ+pT+80Jb1y4ybanT27j+zbVP/jkYmCrUGsEln0Ox/hZmuvgy8Ra7AxbXP2Mw==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-prefer-header": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-prefer-header/-/parse-prefer-header-1.0.0.tgz", + "integrity": "sha512-+WJ3ncCrKOExuxF06XyKWS8bLkLttnlm6YPMZIFIUXNd09Xy0N2JISudxCaY+luDm43yTnHMHVU3zte4G2gN4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.camelcase": "^4.3.0" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pino": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-6.14.0.tgz", + "integrity": "sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-redact": "^3.0.0", + "fast-safe-stringify": "^2.0.8", + "flatstr": "^1.0.12", + "pino-std-serializers": "^3.1.0", + "process-warning": "^1.0.0", + "quick-format-unescaped": "^4.0.3", + "sonic-boom": "^1.0.2" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", + "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postman-collection": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.5.0.tgz", + "integrity": "sha512-152JSW9pdbaoJihwjc7Q8lc3nPg/PC9lPTHdMk7SHnHhu/GBJB7b2yb9zG7Qua578+3PxkQ/HYBuXpDSvsf7GQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@faker-js/faker": "5.5.3", + "file-type": "3.9.0", + "http-reasons": "0.1.0", + "iconv-lite": "0.6.3", + "liquid-json": "0.3.1", + "lodash": "4.17.21", + "mime-format": "2.0.1", + "mime-types": "2.1.35", + "postman-url-encoder": "3.0.5", + "semver": "7.6.3", + "uuid": "8.3.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postman-collection/node_modules/@faker-js/faker": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz", + "integrity": "sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==", + "deprecated": "Please update to a newer version.", + "dev": true, + "license": "MIT" + }, + "node_modules/postman-collection/node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/postman-url-encoder": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-3.0.5.tgz", + "integrity": "sha512-jOrdVvzUXBC7C+9gkIkpDJ3HIxOHTIqjpQ4C1EMt1ZGeMvSEpbFCKq23DEfgsj46vMnDgyQf+1ZLp2Wm+bKSsA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pretty-data": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/pretty-data/-/pretty-data-0.40.0.tgz", + "integrity": "sha512-YFLnEdDEDnkt/GEhet5CYZHCvALw6+Elyb/tp8kQG03ZSIuzeaDWpZYndCXwgqu4NAjh1PI534dhDS1mHarRnQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/process-warning": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", + "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", + "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/signale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", + "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/signale/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/signale/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/signale/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sonic-boom": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", + "integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "flatstr": "^1.0.12" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-template-lite": { + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/uri-template-lite/-/uri-template-lite-22.9.0.tgz", + "integrity": "sha512-cmGZaykSWEQ5UXKaGKnUS8zFvfp8j1Jvn7dlq3P7tGd5XeybXcfo0xnVBRWiNEp80nO1GYgCLwoaRJ8WMmmk3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate.io-array": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz", + "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/validate.io-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz", + "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==", + "dev": true + }, + "node_modules/validate.io-integer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz", + "integrity": "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==", + "dev": true, + "dependencies": { + "validate.io-number": "^1.0.3" + } + }, + "node_modules/validate.io-integer-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz", + "integrity": "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==", + "dev": true, + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-integer": "^1.0.4" + } + }, + "node_modules/validate.io-number": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz", + "integrity": "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==", + "dev": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..77d2cf0 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "archastro-python-tooling", + "version": "0.0.0", + "private": true, + "description": "JS tooling deps for the ArchAstro Python SDK — the channel-harness subprocess that backs channel contract tests, and the Prism mock server used by REST contract tests. Prism is pinned to 5.14.2 (5.15.x ships a broken tarball without dist/).", + "scripts": { + "regenerate": "bash scripts/regenerate_sdk.sh" + }, + "devDependencies": { + "@archastro/channel-harness": "^0.1.0", + "@stoplight/prism-cli": "5.14.2" + }, + "overrides": { + "@stoplight/prism-core": "5.8.0", + "@stoplight/prism-http": "5.12.0", + "@stoplight/prism-http-server": "5.12.2" + }, + "engines": { + "node": ">=20" + } +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1392c3b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +[project] +name = "archastro-platform-sdk" +version = "0.77.0" +description = "Python SDK for the ArchAstro Platform API" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "httpx>=0.27", + "pydantic>=2.0", + "websockets>=13.0", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src/archastro", "src/phx_channel"] + +[dependency-groups] +dev = [ + "pytest>=8.0", + "pytest-asyncio>=0.24", + "ruff>=0.11", +] + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +select = ["E4", "E7", "E9", "F", "B", "I", "UP"] +fixable = ["ALL"] + +[tool.ruff.format] +quote-style = "double" + +[tool.pytest.ini_options] +asyncio_mode = "auto" +addopts = "--import-mode=importlib" +testpaths = ["tests"] diff --git a/scripts/regenerate_sdk.sh b/scripts/regenerate_sdk.sh new file mode 100755 index 0000000..d71d7bd --- /dev/null +++ b/scripts/regenerate_sdk.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Regenerate the Python SDK from the canonical OpenAPI spec. +# +# Flow: +# 1. Fetch specs/platform-openapi.json from ArchAstro/archastro-openapi +# on GitHub (the canonical source of truth). +# 2. Copy it into ./specs/platform-openapi.json so the SDK package +# ships its own copy for contract-test consumers. +# 3. Run @archastro/sdk-generator (from public npm via npx) to emit +# Pydantic models, resources, and channel classes under +# src/archastro/platform/, and the contract-test tree under +# tests/contract/. +# +# Usage: +# ./scripts/regenerate_sdk.sh # pull spec from main +# ARCHASTRO_OPENAPI_REF=some-branch ./scripts/regenerate_sdk.sh +# ARCHASTRO_SDK_GENERATOR=@archastro/sdk-generator@0.1.0 ./scripts/regenerate_sdk.sh +# +# Env knobs: +# ARCHASTRO_OPENAPI_REF Git ref in archastro-openapi to pull the +# spec from (default: main). Useful when a +# spec change is on a branch awaiting merge. +# ARCHASTRO_SDK_GENERATOR Package spec for the generator passed to +# npx (default: @archastro/sdk-generator@latest). +# Pin to a specific version for reproducible +# regenerations in a release branch. + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +SPEC_DST="$REPO_ROOT/specs/platform-openapi.json" +CONFIG_FILE="$REPO_ROOT/scripts/sdk-generator-config.json" + +REF="${ARCHASTRO_OPENAPI_REF:-main}" +SPEC_URL="https://raw.githubusercontent.com/ArchAstro/archastro-openapi/${REF}/specs/platform-openapi.json" +SDK_GENERATOR_SPEC="${ARCHASTRO_SDK_GENERATOR:-@archastro/sdk-generator@latest}" + +log() { printf '==> %s\n' "$*"; } + +# ─── 1. Fetch the spec ────────────────────────────────────────── + +mkdir -p "$(dirname "$SPEC_DST")" +log "Fetching spec from $SPEC_URL" +curl --fail --silent --show-error --location "$SPEC_URL" -o "$SPEC_DST" + +# Sanity-check the spec. Pass the path via env var rather than +# interpolating into the JS string so paths with quotes/spaces are safe. +SPEC="$SPEC_DST" node -e ' + const s = require(process.env.SPEC); + const paths = Object.keys(s.paths ?? {}).length; + const schemas = Object.keys(s.components?.schemas ?? {}).length; + const channels = (s["x-channels"] ?? []).length; + console.log(`Spec: ${paths} routes, ${schemas} schemas, ${channels} channels`); +' + +# ─── 2. Generate SDK + contract tests ─────────────────────────── + +log "Generating Python SDK into $REPO_ROOT" +npx --yes "$SDK_GENERATOR_SPEC" \ + --spec "$SPEC_DST" \ + --config "$CONFIG_FILE" \ + --lang python \ + --out "$REPO_ROOT" + +log "Generating Python contract tests into $REPO_ROOT" +npx --yes "$SDK_GENERATOR_SPEC" \ + --spec "$SPEC_DST" \ + --config "$CONFIG_FILE" \ + --lang contract-tests-py \ + --out "$REPO_ROOT" + +# ─── 3. Normalize formatting ──────────────────────────────────── + +# Run ruff across the generated trees so the committed output matches +# the repo's style config. The generator emits deliberately plain +# Python; this brings it in line with what CI expects. +log "Applying ruff lint fixes + format" +uv run ruff check --fix --fix-only src tests >/dev/null +uv run ruff format src tests >/dev/null + +log "Done. Review the diff and commit, or re-run this script after the spec is updated upstream." diff --git a/scripts/sdk-generator-config.json b/scripts/sdk-generator-config.json new file mode 100644 index 0000000..2478cec --- /dev/null +++ b/scripts/sdk-generator-config.json @@ -0,0 +1,8 @@ +{ + "name": "archastro-platform-sdk", + "version": "0.77.0", + "baseUrl": "https://platform.archastro.ai", + "apiBase": "/api", + "defaultVersion": "v1", + "description": "Python SDK for the ArchAstro Platform API" +} diff --git a/specs/platform-openapi.json b/specs/platform-openapi.json new file mode 100644 index 0000000..b7d9f91 --- /dev/null +++ b/specs/platform-openapi.json @@ -0,0 +1,23235 @@ +{ + "components": { + "schemas": { + "AICompletionResult": { + "description": "Schema for AI chat completion results.", + "properties": { + "finish_reason": { + "description": "Completion stop reason", + "type": "string" + }, + "message": { + "$ref": "#/components/schemas/AIMessage", + "description": "Final assistant message" + }, + "messages": { + "description": "Full message history", + "items": { + "$ref": "#/components/schemas/AIMessage" + }, + "type": "array" + }, + "token_usage": { + "description": "Token usage keyed by model", + "type": "object" + } + }, + "required": [ + "message", + "messages", + "finish_reason" + ], + "type": "object" + }, + "AIImageInput": { + "description": "Schema for an input image (base64-encoded) used in image editing.", + "properties": { + "image_data": { + "description": "Base64-encoded image data", + "type": "string" + }, + "image_type": { + "description": "MIME type (e.g. image/png, image/jpeg)", + "type": "string" + } + }, + "required": [ + "image_data", + "image_type" + ], + "type": "object" + }, + "AIImageResult": { + "description": "Schema for an AI image generation result.", + "properties": { + "aspect_ratio": { + "description": "Aspect ratio (e.g. 16:9)", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "image_data": { + "description": "Base64-encoded image data", + "type": "string" + }, + "image_size": { + "description": "Image size tier (e.g. 1K, 2K)", + "type": "string" + }, + "image_type": { + "description": "MIME type (e.g. image/png)", + "type": "string" + }, + "image_url": { + "description": "URL to the generated image", + "type": "string" + }, + "model": { + "description": "Model used for generation", + "type": "string" + }, + "revised_prompt": { + "description": "Provider-revised prompt", + "type": "string" + }, + "size": { + "description": "Size string (e.g. 1024x1024)", + "type": "string" + }, + "usage": { + "description": "Token/usage information", + "type": "object" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "required": [ + "model" + ], + "type": "object" + }, + "AIMessage": { + "description": "AI chat message (OpenAI-compatible format).", + "properties": { + "content": { + "description": "Message text content", + "type": "string" + }, + "content_parts": { + "description": "Multimodal content parts", + "items": { + "type": "object" + }, + "type": "array" + }, + "resume_token": { + "description": "Resume token for continuing conversations", + "type": "string" + }, + "role": { + "description": "Message role (system, user, assistant, tool)", + "type": "string" + }, + "structured_output": { + "description": "Structured output data" + }, + "tool_calls": { + "description": "Tool calls from assistant", + "items": { + "$ref": "#/components/schemas/AIToolCall" + }, + "type": "array" + }, + "tool_results": { + "description": "Tool results from tool execution", + "items": { + "$ref": "#/components/schemas/AIToolResult" + }, + "type": "array" + } + }, + "required": [ + "role" + ], + "type": "object" + }, + "AIModel": { + "description": "Schema for AI model information.", + "properties": { + "id": { + "description": "Model identifier", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "AITool": { + "description": "OpenAI-style tool definition.", + "properties": { + "function": { + "$ref": "#/components/schemas/AIToolFunction", + "description": "Function tool definition" + }, + "type": { + "description": "Tool type (function)", + "type": "string" + } + }, + "required": [ + "type", + "function" + ], + "type": "object" + }, + "AIToolCall": { + "description": "Tool call from assistant message.", + "properties": { + "arguments": { + "description": "Tool arguments", + "type": "object" + }, + "id": { + "description": "Tool call ID", + "type": "string" + }, + "name": { + "description": "Tool/function name", + "type": "string" + }, + "thought_signature": { + "description": "Optional thought signature", + "type": "string" + } + }, + "required": [ + "id", + "name", + "arguments" + ], + "type": "object" + }, + "AIToolFunction": { + "description": "OpenAI-style function tool definition.", + "properties": { + "description": { + "description": "Function description", + "type": "string" + }, + "name": { + "description": "Function name", + "type": "string" + }, + "parameters": { + "description": "JSON Schema for function parameters", + "type": "object" + } + }, + "required": [ + "name", + "parameters" + ], + "type": "object" + }, + "AIToolResult": { + "description": "Tool result from tool execution.", + "properties": { + "content": { + "description": "Tool result content", + "type": "string" + }, + "id": { + "description": "Tool call ID this result responds to", + "type": "string" + }, + "name": { + "description": "Tool/function name", + "type": "string" + }, + "resolution": { + "description": "Structured tool resolution" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + }, + "Account": { + "description": "Schema for the current developer account.", + "properties": { + "alias": { + "description": "Developer alias", + "type": "string" + }, + "created_at": { + "description": "Account creation date", + "format": "date-time", + "type": "string" + }, + "email": { + "description": "Email address", + "type": "string" + }, + "email_verified": { + "description": "Whether email is verified", + "type": "boolean" + }, + "full_name": { + "description": "Full name", + "type": "string" + }, + "id": { + "description": "Account ID", + "type": "string" + }, + "system_role": { + "description": "System role", + "type": "string" + }, + "timezone": { + "description": "IANA timezone", + "type": "string" + } + }, + "required": [ + "id", + "email", + "system_role", + "email_verified", + "created_at" + ], + "type": "object" + }, + "Acl": { + "description": "Reusable API schema for access control lists.\n\nSupports two modes (mutually exclusive):\n\n**Replace mode** — send `grants` to replace all entries:\n\n {\"grants\": [{\"principal_type\": \"user\", \"principal\": \"...\", \"actions\": [\"read\"]}]}\n\nUse `{\"grants\": []}` to clear all entries.\n\n**Patch mode** — send `add` and/or `remove`:\n\n {\"add\": [...grants...], \"remove\": [{\"principal_type\": \"user\", \"principal\": \"...\"}]}\n\nCannot mix `grants` with `add`/`remove`.\n", + "properties": { + "add": { + "description": "Patch mode: grants to add or merge into existing", + "items": { + "$ref": "#/components/schemas/AclGrant" + }, + "type": "array" + }, + "grants": { + "description": "Replace mode: full list of grants (replaces all existing). Use [] to clear.", + "items": { + "$ref": "#/components/schemas/AclGrant" + }, + "type": "array" + }, + "remove": { + "description": "Patch mode: principals to remove from existing", + "items": { + "$ref": "#/components/schemas/AclRemoveTarget" + }, + "type": "array" + } + }, + "type": "object" + }, + "AclGrant": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "AclRemoveTarget": { + "description": "API schema for identifying a principal to remove from an ACL.", + "properties": { + "principal": { + "description": "Principal identifier to remove (omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type to remove", + "type": "string" + } + }, + "required": [ + "principal_type" + ], + "type": "object" + }, + "ActivityFeedEntry": { + "description": "API schema for an activity feed entry.", + "properties": { + "agent": { + "description": "Agent (public ID or expanded object when loaded)", + "oneOf": [ + { + "type": "string" + }, + { + "description": "API schema for an agent.", + "properties": { + "acl": { + "description": "Access control list. Contains grants array with principal_type, principal, and actions. Null when no ACL restrictions are applied.", + "properties": { + "add": { + "description": "Patch mode: grants to add or merge into existing", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "grants": { + "description": "Replace mode: full list of grants (replaces all existing). Use [] to clear.", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "remove": { + "description": "Patch mode: principals to remove from existing", + "items": { + "description": "API schema for identifying a principal to remove from an ACL.", + "properties": { + "principal": { + "description": "Principal identifier to remove (omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type to remove", + "type": "string" + } + }, + "required": [ + "principal_type" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "app": { + "description": "Application", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "default_model": { + "description": "Default AI model", + "type": "string" + }, + "email": { + "description": "Agent email", + "type": "string" + }, + "id": { + "description": "Agent ID (agi_...)", + "type": "string" + }, + "identity": { + "description": "Identity prompt", + "type": "string" + }, + "lookup_key": { + "description": "Unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Agent name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "phone_number": { + "description": "Agent phone number", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + } + ] + }, + "app": { + "description": "Application", + "type": "string" + }, + "attachments": { + "description": "Entry attachments", + "items": { + "type": "object" + }, + "type": "array" + }, + "automation_run": { + "description": "Automation run", + "type": "string" + }, + "content": { + "description": "Longer explanation (markdown)", + "type": "string" + }, + "correlation_id": { + "description": "Correlation ID for grouped entries", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Entry ID (afe_...)", + "type": "string" + }, + "kind": { + "description": "Entry kind", + "type": "string" + }, + "level": { + "description": "Severity level", + "type": "string" + }, + "metadata": { + "description": "Entry metadata", + "type": "object" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "routine_run": { + "description": "Routine run", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "session_record": { + "description": "Agent session", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "thread": { + "description": "Thread", + "type": "string" + }, + "title": { + "description": "One-line summary", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User (public ID or expanded object when loaded)", + "oneOf": [ + { + "type": "string" + }, + { + "description": "API schema for a user.", + "properties": { + "alias": { + "description": "User alias/handle", + "type": "string" + }, + "email": { + "description": "User email address", + "type": "string" + }, + "id": { + "description": "User ID", + "type": "string" + }, + "metadata": { + "description": "User metadata", + "type": "object" + }, + "name": { + "description": "User display name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + } + ] + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "Actor": { + "description": "Schema for a message actor (user or agent).\n\nActors represent the entity that sent a message.\nMaps to the actor format from MessageActorHelper.build_actor/1.\n", + "properties": { + "alias": { + "description": "Actor alias/handle", + "type": "string" + }, + "id": { + "description": "Actor ID (format: user-xxx or agent-xxx)", + "type": "string" + }, + "name": { + "description": "Actor display name", + "type": "string" + }, + "profile_picture": { + "$ref": "#/components/schemas/ImageSource", + "description": "Profile picture" + } + }, + "type": "object" + }, + "Agent": { + "description": "API schema for an agent.", + "properties": { + "acl": { + "$ref": "#/components/schemas/Acl", + "description": "Access control list. Contains grants array with principal_type, principal, and actions. Null when no ACL restrictions are applied." + }, + "app": { + "description": "Application", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "default_model": { + "description": "Default AI model", + "type": "string" + }, + "email": { + "description": "Agent email", + "type": "string" + }, + "id": { + "description": "Agent ID (agi_...)", + "type": "string" + }, + "identity": { + "description": "Identity prompt", + "type": "string" + }, + "lookup_key": { + "description": "Unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Agent name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "phone_number": { + "description": "Agent phone number", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "AgentComputer": { + "description": "API schema for an agent computer.", + "properties": { + "agent": { + "description": "Owning agent", + "type": "string" + }, + "app": { + "description": "Application ID", + "type": "string" + }, + "config": { + "description": "Configuration", + "type": "object" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "error_message": { + "description": "Error message", + "type": "string" + }, + "id": { + "description": "Computer ID (cmp_...)", + "type": "string" + }, + "last_active_at": { + "description": "Last active timestamp", + "format": "date-time", + "type": "string" + }, + "lookup_key": { + "description": "Unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Computer name", + "type": "string" + }, + "region": { + "description": "Region", + "type": "string" + }, + "sprite_url": { + "description": "Sprite URL", + "type": "string" + }, + "status": { + "description": "Computer status", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "AgentComputerListResponse": { + "description": "List response for agent computers.", + "properties": { + "data": { + "description": "List of agent computers", + "items": { + "$ref": "#/components/schemas/AgentComputer" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "AgentConversationHistory": { + "description": "API schema for an agent conversation history session.", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "last_interaction_at": { + "description": "Last interaction timestamp", + "format": "date-time", + "type": "string" + }, + "name": { + "description": "Session name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "session": { + "description": "Session ID (ach_...)", + "type": "string" + } + }, + "type": "object" + }, + "AgentExport": { + "description": "API schema for an agent export response.\n\nContains the reconstructed AgentTemplate and all dependent config files\nneeded for a fully self-contained re-deploy.\n", + "properties": { + "configs": { + "description": "Dependent config files", + "items": { + "$ref": "#/components/schemas/Config" + }, + "type": "array" + }, + "template": { + "description": "AgentTemplate config object", + "type": "object" + } + }, + "required": [ + "template", + "configs" + ], + "type": "object" + }, + "AgentImpersonationManifest": { + "description": "API schema for a full agent impersonation manifest.", + "properties": { + "agent": { + "$ref": "#/components/schemas/Agent", + "description": "Agent identity and metadata" + }, + "skills": { + "description": "Linked skills", + "items": { + "$ref": "#/components/schemas/AgentImpersonationSkillSummary" + }, + "type": "array" + }, + "tools": { + "description": "Active tools", + "items": { + "$ref": "#/components/schemas/AgentTool" + }, + "type": "array" + } + }, + "required": [ + "agent", + "tools", + "skills" + ], + "type": "object" + }, + "AgentImpersonationSkillFile": { + "description": "API schema for an agent impersonation skill file entry.", + "properties": { + "content_type": { + "description": "Raw content type for the file", + "type": "string" + }, + "download_url": { + "description": "URL to fetch raw file contents", + "type": "string" + }, + "path": { + "description": "Relative path within the skill bundle", + "type": "string" + } + }, + "required": [ + "path", + "download_url" + ], + "type": "object" + }, + "AgentImpersonationSkillList": { + "description": "API schema for an agent impersonation skill list response.", + "properties": { + "data": { + "description": "Agent-linked skills", + "items": { + "$ref": "#/components/schemas/AgentImpersonationSkillSummary" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "AgentImpersonationSkillManifest": { + "description": "API schema for an agent impersonation skill manifest.", + "properties": { + "description": { + "description": "Skill description", + "type": "string" + }, + "entrypoint": { + "description": "Primary file path for the skill", + "type": "string" + }, + "files": { + "description": "Skill files", + "items": { + "$ref": "#/components/schemas/AgentImpersonationSkillFile" + }, + "type": "array" + }, + "id": { + "description": "Skill config ID", + "type": "string" + }, + "name": { + "description": "Skill display name", + "type": "string" + }, + "slug": { + "description": "Skill slug", + "type": "string" + } + }, + "required": [ + "id", + "slug", + "name", + "entrypoint", + "files" + ], + "type": "object" + }, + "AgentImpersonationSkillSummary": { + "description": "API schema for an agent impersonation skill summary.", + "properties": { + "description": { + "description": "Skill description", + "type": "string" + }, + "id": { + "description": "Skill config ID", + "type": "string" + }, + "name": { + "description": "Skill display name", + "type": "string" + }, + "slug": { + "description": "Skill slug", + "type": "string" + } + }, + "required": [ + "id", + "slug", + "name" + ], + "type": "object" + }, + "AgentImpersonationToolList": { + "description": "API schema for the resolved callable tools list response.", + "properties": { + "data": { + "description": "Resolved callable tools", + "items": { + "$ref": "#/components/schemas/ResolvedTool" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "AgentImpersonationToolRunResult": { + "description": "API schema for the result of running an agent tool via impersonation.", + "properties": { + "duration_ms": { + "description": "Execution duration in milliseconds", + "type": "integer" + }, + "result": { + "description": "Tool execution result", + "type": "object" + } + }, + "required": [ + "result", + "duration_ms" + ], + "type": "object" + }, + "AgentListResponse": { + "description": "Paginated list response for agents.", + "properties": { + "data": { + "description": "List of agents", + "items": { + "$ref": "#/components/schemas/Agent" + }, + "type": "array" + }, + "has_next": { + "description": "Whether a next page exists", + "type": "boolean" + }, + "has_prev": { + "description": "Whether a previous page exists", + "type": "boolean" + }, + "page": { + "description": "Current page number", + "type": "integer" + }, + "page_size": { + "description": "Results per page", + "type": "integer" + }, + "total_entries": { + "description": "Total number of entries", + "type": "integer" + }, + "total_pages": { + "description": "Total number of pages", + "type": "integer" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "AgentRoutine": { + "description": "API schema for an agent routine.", + "properties": { + "acl": { + "$ref": "#/components/schemas/Acl", + "description": "Access control list. Contains grants array with principal_type, principal, and actions. Null when no ACL restrictions are applied." + }, + "agent": { + "description": "Owning agent ID", + "type": "string" + }, + "app": { + "description": "Application ID", + "type": "string" + }, + "config": { + "description": "Config ID", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Routine description", + "type": "string" + }, + "event_config": { + "description": "Event configuration", + "type": "object" + }, + "event_type": { + "description": "Event type", + "type": "string" + }, + "handler_type": { + "description": "Handler type", + "type": "string" + }, + "id": { + "description": "Routine ID (arn_...)", + "type": "string" + }, + "lookup_key": { + "description": "Unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Routine name", + "type": "string" + }, + "preset_config": { + "description": "Preset configuration", + "type": "object" + }, + "preset_name": { + "description": "Preset name", + "type": "string" + }, + "schedule": { + "description": "Schedule expression", + "type": "string" + }, + "script": { + "description": "Script content", + "type": "string" + }, + "status": { + "description": "Routine status", + "type": "string" + }, + "trigger_context": { + "description": "Trigger context", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "AgentRoutineListResponse": { + "description": "List response for agent routines.", + "properties": { + "data": { + "description": "List of agent routines", + "items": { + "$ref": "#/components/schemas/AgentRoutine" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "AgentRoutineRun": { + "description": "API schema for an agent routine run.", + "properties": { + "acl": { + "$ref": "#/components/schemas/Acl", + "description": "Access control list. Contains grants array with principal_type, principal, and actions. Null when no ACL restrictions are applied." + }, + "agent": { + "description": "Agent", + "type": "string" + }, + "app": { + "description": "Application", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "duration_ms": { + "description": "Duration in milliseconds", + "type": "integer" + }, + "event_id": { + "description": "Event ID", + "type": "string" + }, + "id": { + "description": "Run ID (arr_...)", + "type": "string" + }, + "metadata": { + "description": "Run metadata", + "type": "object" + }, + "payload": { + "description": "Event payload", + "type": "object" + }, + "result": { + "description": "Run result", + "type": "object" + }, + "routine": { + "description": "Routine", + "type": "string" + }, + "status": { + "description": "Run status", + "type": "string" + }, + "structured_response": { + "description": "Validated structured response when the run uses an AgentMessageSchema", + "type": "object" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "worker": { + "$ref": "#/components/schemas/WorkerStatus", + "description": "Background worker status. Null when no worker job is associated." + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "AgentRoutineRunListResponse": { + "description": "Cursor-paginated list response for agent routine runs.", + "properties": { + "after_cursor": { + "description": "Cursor for fetching items after this point", + "type": "string" + }, + "before_cursor": { + "description": "Cursor for fetching items before this point", + "type": "string" + }, + "data": { + "description": "List of routine runs", + "items": { + "$ref": "#/components/schemas/AgentRoutineRun" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "AgentSchedule": { + "description": "API schema for an agent schedule.", + "properties": { + "agent": { + "description": "Owning agent ID", + "type": "string" + }, + "app": { + "description": "Application ID", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "cron_expression": { + "description": "Cron expression (recurring only)", + "type": "string" + }, + "id": { + "description": "Schedule ID (asc_...)", + "type": "string" + }, + "instructions": { + "description": "Task instructions", + "type": "string" + }, + "last_run_at": { + "description": "Last execution time", + "format": "date-time", + "type": "string" + }, + "max_runs": { + "description": "Maximum runs (recurring only)", + "type": "integer" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "next_run_at": { + "description": "Next scheduled execution", + "format": "date-time", + "type": "string" + }, + "run_count": { + "description": "Number of times executed", + "type": "integer" + }, + "schedule_type": { + "description": "Schedule type (once or recurring)", + "type": "string" + }, + "scheduled_at": { + "description": "One-time execution time", + "format": "date-time", + "type": "string" + }, + "status": { + "description": "Schedule status", + "type": "string" + }, + "thread": { + "description": "Thread ID (if thread-bound)", + "type": "string" + }, + "timezone": { + "description": "Schedule timezone", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "AgentSession": { + "description": "API schema for an agent session.", + "properties": { + "agent": { + "description": "Owning agent ID (agi_...)", + "type": "string" + }, + "completed_at": { + "description": "When the session completed", + "format": "date-time", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "error": { + "description": "Error message if failed", + "type": "string" + }, + "id": { + "description": "Agent session ID (ase_...)", + "type": "string" + }, + "inbox": { + "description": "Inbox messages", + "items": { + "type": "object" + }, + "type": "array" + }, + "instructions": { + "description": "Task description for the session", + "type": "string" + }, + "is_system_session": { + "description": "Whether this is a system-created session", + "type": "boolean" + }, + "max_runs_per_turn": { + "description": "Max tool runs per turn", + "type": "integer" + }, + "max_tokens": { + "description": "Max tokens", + "type": "integer" + }, + "max_turns": { + "description": "Max turns", + "type": "integer" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Optional display name for the session", + "type": "string" + }, + "result": { + "description": "Session result", + "type": "object" + }, + "started_at": { + "description": "When the session started running", + "format": "date-time", + "type": "string" + }, + "status": { + "description": "Session status (pending, running, waiting, completed, failed, cancelled)", + "type": "string" + }, + "trajectory": { + "description": "Trajectory ID for the durable session transcript", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "AgentSessionListResponse": { + "description": "List response for agent sessions.", + "properties": { + "data": { + "description": "List of agent sessions", + "items": { + "$ref": "#/components/schemas/AgentSession" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "AgentSkill": { + "description": "API schema for an agent skill.", + "properties": { + "agent": { + "description": "Owning agent ID", + "type": "string" + }, + "app": { + "description": "Application ID", + "type": "string" + }, + "config": { + "description": "Skill config ID", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Agent skill ID (ask_...)", + "type": "string" + }, + "instruction": { + "description": "Instruction override", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "status": { + "description": "Skill status", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "AgentSkillList": { + "description": "API schema for agent skills list response.", + "properties": { + "data": { + "description": "List of agent skills", + "items": { + "$ref": "#/components/schemas/AgentSkill" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "AgentTool": { + "description": "API schema for an agent tool.", + "properties": { + "agent": { + "description": "Owning agent ID", + "type": "string" + }, + "app": { + "description": "Application ID", + "type": "string" + }, + "builtin_tool_config": { + "description": "Builtin tool configuration", + "type": "object" + }, + "builtin_tool_key": { + "description": "Builtin tool key", + "type": "string" + }, + "config": { + "description": "Config ID", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Tool description", + "type": "string" + }, + "handler_type": { + "description": "Handler type", + "type": "string" + }, + "id": { + "description": "Tool ID (atl_...)", + "type": "string" + }, + "instruction": { + "description": "Tool instruction", + "type": "string" + }, + "kind": { + "description": "Tool kind", + "type": "string" + }, + "lookup_key": { + "description": "Unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Tool name", + "type": "string" + }, + "parameters": { + "description": "Tool parameters", + "type": "object" + }, + "parameters_config": { + "description": "Parameters config ID", + "type": "string" + }, + "status": { + "description": "Tool status", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "AgentToolListResponse": { + "description": "List response for agent tools.", + "properties": { + "data": { + "description": "List of agent tools", + "items": { + "$ref": "#/components/schemas/AgentTool" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "ApiCall": { + "description": "Schema for an API call record.\n\nMaps to serialized API call output from developer portal API.\n", + "properties": { + "api_key_type": { + "description": "API key type (publishable or secret)", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "error_message": { + "description": "Error message if request failed", + "type": "string" + }, + "event_type": { + "description": "Webhook event type (e.g. push, pull_request)", + "type": "string" + }, + "full_url": { + "description": "Full request URL with query string", + "type": "string" + }, + "handler_module": { + "description": "ApiDsl action module", + "type": "string" + }, + "id": { + "description": "Public ID (aac_...)", + "type": "string" + }, + "ip_address": { + "description": "Client IP address", + "type": "string" + }, + "latency_ms": { + "description": "Latency in milliseconds", + "type": "integer" + }, + "metadata": { + "description": "Additional metadata (e.g. integration_ids for webhooks)", + "type": "object" + }, + "method": { + "description": "HTTP method", + "type": "string" + }, + "org": { + "description": "Org if org-scoped", + "type": "string" + }, + "path": { + "description": "Sanitized path with param placeholders", + "type": "string" + }, + "query_string": { + "description": "Query string", + "type": "string" + }, + "request_body": { + "description": "Request body payload (webhooks only)", + "type": "object" + }, + "request_headers": { + "description": "Request headers (webhooks only)", + "type": "object" + }, + "request_id": { + "description": "Request ID from Plug.RequestId", + "type": "string" + }, + "status_code": { + "description": "HTTP status code", + "type": "integer" + }, + "team": { + "description": "Team if team-scoped", + "type": "string" + }, + "thread": { + "description": "Thread if thread-scoped", + "type": "string" + }, + "user": { + "description": "User if user-scoped", + "type": "string" + } + }, + "required": [ + "id", + "method", + "path", + "full_url", + "status_code", + "latency_ms" + ], + "type": "object" + }, + "ApiExplorerEndpoint": { + "description": "Schema for a documented API endpoint in the developer API explorer.", + "properties": { + "deprecated": { + "description": "Whether the route is deprecated", + "type": "boolean" + }, + "description": { + "description": "Endpoint description", + "type": "string" + }, + "errors": { + "description": "Documented error responses", + "items": { + "type": "object" + }, + "type": "array" + }, + "method": { + "description": "HTTP method", + "type": "string" + }, + "params": { + "description": "Documented params", + "items": { + "type": "object" + }, + "type": "array" + }, + "path": { + "description": "Route path", + "type": "string" + }, + "returns": { + "description": "Return schema description", + "type": "object" + }, + "scope": { + "description": "Endpoint scope", + "type": "string" + }, + "tags": { + "description": "Route tags", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "method", + "path", + "scope", + "deprecated", + "tags", + "params", + "errors" + ], + "type": "object" + }, + "ApiExplorerIndex": { + "description": "Schema for the API explorer response.", + "properties": { + "data": { + "description": "Documented API endpoints", + "items": { + "$ref": "#/components/schemas/ApiExplorerEndpoint" + }, + "type": "array" + }, + "schemas": { + "description": "Collected schema definitions", + "type": "object" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "App": { + "description": "Schema for a developer app.\n\nMaps to serialized app output from developer portal API.\n", + "properties": { + "app_slug": { + "description": "Workspace slug (if set)", + "type": "string" + }, + "app_url": { + "description": "App URL", + "type": "string" + }, + "brand_name": { + "description": "Brand name for emails", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "App description", + "type": "string" + }, + "from_name": { + "description": "From name for emails", + "type": "string" + }, + "id": { + "description": "Public ID (dap_...)", + "type": "string" + }, + "marketing_url": { + "description": "Marketing URL", + "type": "string" + }, + "muted_color": { + "description": "Muted hex color", + "type": "string" + }, + "name": { + "description": "App name", + "type": "string" + }, + "primary_color": { + "description": "Primary hex color", + "type": "string" + }, + "sandboxes": { + "description": "App sandboxes with keys", + "items": { + "$ref": "#/components/schemas/Sandbox" + }, + "type": "array" + }, + "status": { + "description": "Status (active, suspended)", + "type": "string" + }, + "support_email": { + "description": "Support email address", + "type": "string" + }, + "third_party_oauth_enabled": { + "description": "Third-party OAuth enabled", + "type": "boolean" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "name", + "status" + ], + "type": "object" + }, + "AppEnvVarMasked": { + "description": "Schema for an app environment variable response with a masked value.", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Optional description", + "type": "string" + }, + "id": { + "description": "Environment variable ID", + "type": "string" + }, + "key": { + "description": "Environment variable key", + "type": "string" + }, + "masked_value": { + "description": "Masked environment variable value", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "key", + "masked_value" + ], + "type": "object" + }, + "AppEnvVarMaskedList": { + "description": "Schema for masked app environment variable list responses.", + "properties": { + "data": { + "description": "Environment variables", + "items": { + "$ref": "#/components/schemas/AppEnvVarMasked" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "AppEnvVarPlaintext": { + "description": "Schema for an app environment variable response that includes the plaintext value.", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Optional description", + "type": "string" + }, + "id": { + "description": "Environment variable ID", + "type": "string" + }, + "key": { + "description": "Environment variable key", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "value": { + "description": "Plaintext environment variable value", + "type": "string" + } + }, + "required": [ + "id", + "key", + "value" + ], + "type": "object" + }, + "AppKey": { + "description": "Schema for an API key.\n\nMaps to serialized app key output from developer portal API.\n", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "full_key": { + "description": "Full key shown only once at creation", + "type": "string" + }, + "id": { + "description": "Public ID (dak_...)", + "type": "string" + }, + "key_hint": { + "description": "Last 4 chars hint", + "type": "string" + }, + "key_value": { + "description": "Full key (publishable only)", + "type": "string" + }, + "last_used_at": { + "description": "Last used timestamp", + "format": "date-time", + "type": "string" + }, + "status": { + "description": "Status (active, revoked)", + "type": "string" + }, + "type": { + "description": "Key type (publishable, secret)", + "type": "string" + } + }, + "required": [ + "id", + "type", + "status" + ], + "type": "object" + }, + "AppSlug": { + "description": "Schema for an app slug mapping.", + "properties": { + "app": { + "description": "App identifier", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "creator": { + "description": "Creator identifier", + "type": "string" + }, + "id": { + "description": "Slug record ID", + "type": "string" + }, + "slug": { + "description": "Globally unique slug", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "slug", + "app" + ], + "type": "object" + }, + "Artifact": { + "description": "API schema for an artifact.", + "properties": { + "agent": { + "description": "Agent", + "type": "string" + }, + "content_type": { + "description": "MIME content type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "current_version": { + "description": "Current version ID", + "type": "string" + }, + "description": { + "description": "Artifact description", + "type": "string" + }, + "file": { + "description": "Storage file", + "type": "string" + }, + "file_name": { + "description": "Original filename", + "type": "string" + }, + "file_url": { + "description": "Signed file URL", + "type": "string" + }, + "id": { + "description": "Artifact ID", + "type": "string" + }, + "image_source": { + "$ref": "#/components/schemas/ImageSource", + "description": "Image source metadata" + }, + "name": { + "description": "Artifact name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "thread": { + "description": "Thread", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User", + "type": "string" + }, + "version": { + "description": "Current version number", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "ArtifactVersion": { + "description": "API schema for an artifact version.", + "properties": { + "artifact": { + "description": "Parent artifact", + "type": "string" + }, + "change_description": { + "description": "Description of changes", + "type": "string" + }, + "content_type": { + "description": "MIME content type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "file": { + "description": "Storage file", + "type": "string" + }, + "file_name": { + "description": "Original filename", + "type": "string" + }, + "file_url": { + "description": "Signed file URL", + "type": "string" + }, + "id": { + "description": "Artifact version ID", + "type": "string" + }, + "image_source": { + "$ref": "#/components/schemas/ImageSource", + "description": "Image source metadata" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "version_number": { + "description": "Version number", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "Attachment": { + "description": "Schema for a message attachment.\n\nAttachments can be of various types (file, scraped_link, artifact, task, media, action).\nFields present depend on the attachment type.\nMaps to format_attachments_for_client/1 output.\n", + "properties": { + "content_type": { + "description": "MIME content type (file, artifact, media types)", + "type": "string" + }, + "description": { + "description": "Description (scraped_link, artifact, task types)", + "type": "string" + }, + "filename": { + "description": "File name (file, artifact, media types)", + "type": "string" + }, + "height": { + "description": "Media height (media type)", + "type": "integer" + }, + "id": { + "description": "Attachment ID", + "type": "string" + }, + "image_height": { + "description": "Preview image height (scraped_link type)", + "type": "integer" + }, + "image_source": { + "$ref": "#/components/schemas/ImageSource", + "description": "Image metadata (file, scraped_link, artifact, media types)" + }, + "image_url": { + "description": "Preview image URL (scraped_link type)", + "type": "string" + }, + "image_width": { + "description": "Preview image width (scraped_link type)", + "type": "integer" + }, + "media_type": { + "description": "Media type (media type)", + "type": "string" + }, + "name": { + "description": "Media name (media type)", + "type": "string" + }, + "object": { + "description": "Embedded object (task, action types)", + "type": "object" + }, + "title": { + "description": "Title (scraped_link, artifact, task types)", + "type": "string" + }, + "type": { + "description": "Attachment type: file, scraped_link, artifact, task, media, action", + "type": "string" + }, + "url": { + "description": "URL to the resource (file, scraped_link, artifact, media types)", + "type": "string" + }, + "variants": { + "description": "Media variants (media type)", + "items": { + "$ref": "#/components/schemas/MediaVariant" + }, + "type": "array" + }, + "version": { + "description": "Artifact version number (artifact type)", + "type": "integer" + }, + "width": { + "description": "Media width (media type)", + "type": "integer" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + "AuthTokens": { + "description": "API schema for authentication token responses.", + "properties": { + "expires_in": { + "description": "Token TTL in seconds", + "type": "integer", + "x-sdk": "token_expiry" + }, + "metadata": { + "description": "Additional metadata (e.g., onboarding_job_id)", + "type": "object" + }, + "refresh_token": { + "description": "Refresh token", + "type": "string", + "x-sdk": "refresh_token" + }, + "token": { + "description": "Access token (JWT)", + "type": "string", + "x-sdk": "access_token" + }, + "token_type": { + "description": "Token type (Bearer)", + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User", + "description": "Authenticated user" + } + }, + "required": [ + "token", + "refresh_token", + "user", + "token_type", + "expires_in" + ], + "type": "object" + }, + "Automation": { + "description": "Schema for a developer automation.\n\nMaps to serialized automation output from developer portal API.\n", + "properties": { + "app": { + "description": "App (dap_...)", + "type": "string" + }, + "config": { + "description": "Associated config (cfg_...)", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "created_by_user": { + "description": "Creator user (usr_...)", + "type": "string" + }, + "creator": { + "description": "Creator account (dac_...)", + "type": "string" + }, + "description": { + "description": "Optional description", + "type": "string" + }, + "id": { + "description": "Public ID (aut_...)", + "type": "string" + }, + "input_schema_config": { + "description": "Input schema config (cfg_...)", + "type": "string" + }, + "invoke_auth": { + "description": "Auth mode: secret_key or user", + "type": "string" + }, + "lookup_key": { + "description": "Optional unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Automation name", + "type": "string" + }, + "run_as_agent": { + "description": "Agent to run as (agt_...)", + "type": "string" + }, + "run_as_user": { + "description": "User to run as (usr_...)", + "type": "string" + }, + "schedule": { + "description": "Cron expression for scheduled type", + "type": "string" + }, + "status": { + "description": "Status: draft, running, or paused", + "type": "string" + }, + "trigger": { + "description": "Event name for trigger type", + "type": "string" + }, + "type": { + "description": "Type: trigger, scheduled, or invoked", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "app", + "name", + "type", + "status" + ], + "type": "object" + }, + "AutomationRun": { + "description": "Schema for an automation run.\n\nMaps to serialized automation run output from developer portal API.\n", + "properties": { + "app": { + "description": "App (dap_...)", + "type": "string" + }, + "automation": { + "description": "Automation (aut_...)", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "event_id": { + "description": "Triggering event ID", + "type": "string" + }, + "id": { + "description": "Public ID (atr_...)", + "type": "string" + }, + "payload": { + "description": "Event payload", + "type": "object" + }, + "result": { + "description": "Workflow execution result (payload and output)", + "type": "object" + }, + "status": { + "description": "Status: pending, running, completed, failed, cancelled", + "type": "string" + }, + "team": { + "description": "Team if team-owned", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User if user-owned", + "type": "string" + } + }, + "required": [ + "id", + "app", + "automation", + "status" + ], + "type": "object" + }, + "BillingPlan": { + "description": "Schema for a billing plan.", + "properties": { + "description": { + "description": "Plan description", + "type": "string" + }, + "id": { + "description": "Plan identifier (e.g. paygo)", + "type": "string" + }, + "name": { + "description": "Human-readable plan name", + "type": "string" + }, + "target": { + "description": "Entity type this plan applies to (developer or org)", + "type": "string" + } + }, + "type": "object" + }, + "BillingSettingsApp": { + "description": "Schema for app billing settings.", + "properties": { + "billing_account": { + "description": "Developer account that owns billing (public ID)", + "type": "string" + }, + "org_billing_enabled": { + "description": "Whether orgs get their own Stripe customers", + "type": "boolean" + } + }, + "type": "object" + }, + "BillingSettingsOrg": { + "description": "Schema for org billing settings.", + "properties": { + "auto_reup_amount_cents": { + "description": "Amount (cents) to charge when threshold is crossed", + "type": "integer" + }, + "auto_reup_enabled": { + "description": "Whether automatic credit reup is enabled", + "type": "boolean" + }, + "auto_reup_threshold_cents": { + "description": "Balance threshold (cents) to trigger auto-reup", + "type": "integer" + }, + "billing_provider_environment": { + "description": "Stripe environment (live or test)", + "type": "string" + }, + "billing_provider_id": { + "description": "Stripe customer ID", + "type": "string" + }, + "pending_plan": { + "description": "Plan change awaiting Stripe confirmation, or __clear__ to remove plan", + "type": "string" + }, + "plan": { + "description": "Active billing plan (e.g. enterprise-pilot)", + "type": "string" + }, + "primary_user": { + "description": "Org admin user who is the billing contact (public ID)", + "type": "string" + } + }, + "type": "object" + }, + "BuiltinTool": { + "description": "An individual tool within a builtin tool catalog entry.", + "properties": { + "description": { + "description": "Tool description", + "type": "string" + }, + "name": { + "description": "Tool name", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "BuiltinToolCatalogEntry": { + "description": "A builtin tool catalog entry describing an available tool category.", + "properties": { + "config_schema": { + "description": "JSON schema for tool configuration", + "type": "object" + }, + "description": { + "description": "Tool description", + "type": "string" + }, + "instruction": { + "description": "Tool instruction", + "type": "string" + }, + "key": { + "description": "Unique tool key", + "type": "string" + }, + "label": { + "description": "Display label", + "type": "string" + }, + "providers": { + "description": "Supported providers", + "items": { + "type": "string" + }, + "type": "array" + }, + "requires_integration": { + "description": "Whether an integration is required", + "type": "boolean" + }, + "server_tool_type": { + "description": "Server tool type identifier", + "type": "string" + }, + "tools": { + "description": "List of individual tools", + "items": { + "$ref": "#/components/schemas/BuiltinTool" + }, + "type": "array" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "ChatMember": { + "description": "API schema for a chat member (user or agent).", + "properties": { + "agent": { + "$ref": "#/components/schemas/Agent", + "description": "Agent object (for agent members)" + }, + "membership_type": { + "description": "Membership type", + "type": "string" + }, + "type": { + "description": "Member type (user or agent)", + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User", + "description": "User object (for user members)" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "CheckoutSessionResult": { + "description": "Schema for Stripe Checkout Session creation result.", + "properties": { + "checkout_url": { + "description": "Stripe Checkout URL to redirect the user to", + "type": "string" + } + }, + "required": [ + "checkout_url" + ], + "type": "object" + }, + "CommentCreateParams": { + "description": "Schema for comment creation parameters.\n\nUsed by both Users.Tasks.CreateComment and Teams.Tasks.CreateComment actions.\n", + "properties": { + "body": { + "description": "Comment body text", + "type": "string" + } + }, + "required": [ + "body" + ], + "type": "object" + }, + "ComputerExecResult": { + "description": "Result of executing a command on an agent computer.", + "properties": { + "exit_code": { + "description": "Process exit code", + "type": "integer" + }, + "output": { + "description": "Command output", + "type": "string" + } + }, + "type": "object" + }, + "Config": { + "description": "API schema for a config resource.", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "current_version": { + "$ref": "#/components/schemas/ConfigVersion", + "description": "Current version" + }, + "id": { + "description": "Config ID (cfg_...)", + "type": "string" + }, + "is_archived": { + "description": "Whether config is archived", + "type": "boolean" + }, + "kind": { + "description": "Config kind (e.g., Agent, APITool)", + "type": "string" + }, + "lookup_key": { + "description": "Optional lookup key", + "type": "string" + }, + "mime_type": { + "description": "Content mime type", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "raw_content": { + "description": "Raw file content (system configs only)", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User", + "type": "string" + }, + "virtual_path": { + "description": "Unique path within the team", + "type": "string" + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, + "ConfigKind": { + "description": "Schema for a config kind in the list response.\n", + "properties": { + "classification": { + "description": "Kind classification: root or supplemental", + "type": "string" + }, + "description": { + "description": "Markdown documentation describing what this config kind represents and how to use it", + "type": "string" + }, + "kind": { + "description": "The config kind name (e.g., Agent, APITool)", + "type": "string" + }, + "sample_available": { + "description": "Whether a YAML sample is available", + "type": "boolean" + }, + "schema_available": { + "description": "Whether a JSON schema is available", + "type": "boolean" + } + }, + "required": [ + "kind", + "sample_available", + "schema_available", + "classification" + ], + "type": "object" + }, + "ConfigKindSchema": { + "description": "Schema for a config kind's JSON schema and sample response.\n", + "properties": { + "json_schema": { + "description": "JSON Schema for this config kind (can be null if not available)", + "type": "object" + }, + "kind": { + "description": "The config kind name", + "type": "string" + }, + "sample_yaml": { + "description": "Sample YAML content for this config kind (can be null if not available)", + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + "ConfigLoadResult": { + "description": "Schema for batch-loaded config content and metadata.", + "properties": { + "content": { + "description": "Fully resolved content tree", + "type": "object" + }, + "metadata": { + "description": "Metadata map keyed by JSON path", + "type": "object" + } + }, + "required": [ + "content", + "metadata" + ], + "type": "object" + }, + "ConfigSample": { + "description": "Schema for a sample config payload.", + "properties": { + "kind": { + "description": "Config kind", + "type": "string" + }, + "mime_type": { + "description": "Content MIME type", + "type": "string" + }, + "sample_yaml": { + "description": "Sample YAML content", + "type": "string" + } + }, + "required": [ + "kind", + "sample_yaml", + "mime_type" + ], + "type": "object" + }, + "ConfigSaveResult": { + "description": "Schema for batch-save config metadata.", + "properties": { + "metadata": { + "description": "Updated metadata with version numbers", + "type": "object" + } + }, + "required": [ + "metadata" + ], + "type": "object" + }, + "ConfigVersion": { + "description": "API schema for a config version.", + "properties": { + "change_description": { + "description": "Description of changes", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "data": { + "description": "Additional structured data", + "type": "object" + }, + "id": { + "description": "Config version ID (cfv_...)", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "version_number": { + "description": "Version number", + "type": "integer" + } + }, + "required": [ + "id", + "version_number" + ], + "type": "object" + }, + "ContextCredential": { + "description": "Schema for a user credential.\n\nMaps to serialized credential output from developer portal API.\n", + "properties": { + "alt_domains": { + "description": "Alternative domains", + "items": { + "type": "string" + }, + "type": "array" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Human-readable description", + "type": "string" + }, + "domain": { + "description": "Domain (e.g., app.schoology.com)", + "type": "string" + }, + "id": { + "description": "Public ID (ucr_...)", + "type": "string" + }, + "last_accessed_at": { + "description": "Last accessed timestamp", + "format": "date-time", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + } + }, + "required": [ + "id", + "domain" + ], + "type": "object" + }, + "ContextEntry": { + "description": "API schema for a context entry.", + "properties": { + "after_cursor": { + "description": "Pagination cursor (after)", + "type": "string" + }, + "agent_user": { + "description": "Agent user", + "type": "string" + }, + "before_cursor": { + "description": "Pagination cursor (before)", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "files": { + "description": "Loaded file objects", + "items": { + "type": "object" + }, + "type": "array" + }, + "id": { + "description": "Context entry ID", + "type": "string" + }, + "links": { + "description": "Loaded link objects", + "items": { + "type": "object" + }, + "type": "array" + }, + "media": { + "description": "Loaded media objects", + "items": { + "type": "object" + }, + "type": "array" + }, + "metadata": { + "description": "Entry metadata", + "type": "object" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "text": { + "description": "Entry text", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "ContextIngestion": { + "description": "Schema for a context ingestion.\n\nMaps to serialized context ingestion output from developer portal API.\n", + "properties": { + "agent": { + "description": "Agent", + "type": "string" + }, + "completed_at": { + "description": "Completed timestamp", + "format": "date-time", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "error": { + "description": "Error details (if failed)", + "type": "object" + }, + "id": { + "description": "Public ID (cig_...)", + "type": "string" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "source": { + "description": "Source ID", + "type": "string" + }, + "started_at": { + "description": "Started timestamp", + "format": "date-time", + "type": "string" + }, + "status": { + "description": "Status (pending, running, awaiting_callback, succeeded, failed)", + "type": "string" + }, + "team": { + "description": "Owning team ID", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user ID", + "type": "string" + } + }, + "required": [ + "id", + "status" + ], + "type": "object" + }, + "ContextIntegration": { + "description": "Schema for a context integration.\n\nMaps to serialized integration output from developer portal API.\n", + "properties": { + "agent": { + "description": "Owning agent", + "type": "string" + }, + "auth_type": { + "description": "Auth type: oauth or app_installation", + "type": "string" + }, + "connected_at": { + "description": "Connection timestamp", + "format": "date-time", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "expires_at": { + "description": "Token expiration timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Public ID (int_...)", + "type": "string" + }, + "installation": { + "description": "External installation (e.g. GitHub App installation)", + "type": "string" + }, + "last_refreshed_at": { + "description": "Last token refresh timestamp", + "format": "date-time", + "type": "string" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "org": { + "description": "Owning org", + "type": "string" + }, + "provider": { + "description": "Provider name (e.g., google, github)", + "type": "string" + }, + "scopes": { + "description": "OAuth scopes", + "items": { + "type": "string" + }, + "type": "array" + }, + "status": { + "description": "Connection status: connected, disconnected, or token_expired", + "type": "string" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + }, + "workspace_key": { + "description": "Workspace key", + "type": "string" + } + }, + "required": [ + "id", + "provider", + "auth_type", + "status" + ], + "type": "object" + }, + "ContextItem": { + "description": "Schema for a context item.\n\nMaps to serialized context item output from developer portal API.\n", + "properties": { + "agent": { + "description": "Agent", + "type": "string" + }, + "content_type": { + "description": "Content type", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Public ID (cim_...)", + "type": "string" + }, + "item_group": { + "description": "Item group if part of a group", + "type": "string" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "normalized_content": { + "description": "Normalized content text", + "type": "string" + }, + "raw_content": { + "description": "Raw content data", + "type": "object" + }, + "source": { + "description": "Source", + "type": "string" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "ContextSource": { + "description": "Schema for a context source.\n\nMaps to serialized context source output from developer portal API.\n", + "properties": { + "agent": { + "description": "Owning agent", + "type": "string" + }, + "context_installation": { + "description": "Associated installation", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Public ID (cso_...)", + "type": "string" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "org": { + "description": "Owning organization", + "type": "string" + }, + "parent_source": { + "description": "Parent source", + "type": "string" + }, + "payload": { + "description": "Type-specific configuration", + "type": "object" + }, + "sandbox": { + "description": "Owning sandbox", + "type": "string" + }, + "state": { + "description": "State: active or paused", + "type": "string" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "thread": { + "description": "Associated thread", + "type": "string" + }, + "type": { + "description": "Source type (e.g., gmail, github_activity)", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + } + }, + "required": [ + "id", + "type", + "state" + ], + "type": "object" + }, + "Credential": { + "description": "API schema for a user credential.", + "properties": { + "alt_domains": { + "description": "Alternative domains", + "type": "object" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Description", + "type": "string" + }, + "domain": { + "description": "Primary domain", + "type": "string" + }, + "id": { + "description": "Credential ID (ucr_...)", + "type": "string" + }, + "last_accessed_at": { + "description": "Last accessed timestamp", + "format": "date-time", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "secret_group": { + "description": "Secret group ID", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "CredentialWithSecrets": { + "description": "API schema for a credential with decrypted secret values.", + "properties": { + "credential": { + "description": "The credential with decrypted values", + "type": "object" + } + }, + "required": [ + "credential" + ], + "type": "object" + }, + "CreditBalance": { + "description": "Schema for credit balance response.", + "properties": { + "available_cents": { + "description": "Available credit balance in cents", + "type": "integer" + }, + "currency": { + "description": "Currency code (e.g. usd)", + "type": "string" + }, + "has_payment_method": { + "description": "Whether a saved payment method exists", + "type": "boolean" + } + }, + "required": [ + "available_cents", + "currency", + "has_payment_method" + ], + "type": "object" + }, + "CustomObject": { + "description": "API schema for a custom object.", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "fields": { + "description": "Object field values", + "type": "object" + }, + "id": { + "description": "Public ID (cobj_...)", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "row_key": { + "description": "Row key", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "schema_type": { + "description": "Schema type (lookup_key)", + "type": "string" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + }, + "version": { + "description": "Aggregate version for OCC", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "Dataset": { + "description": "Schema for an analytics dataset definition.\n", + "properties": { + "dimensions": { + "description": "Available dimensions", + "items": { + "$ref": "#/components/schemas/DatasetDimension" + }, + "type": "array" + }, + "metrics": { + "description": "Available metrics", + "items": { + "$ref": "#/components/schemas/DatasetMetric" + }, + "type": "array" + }, + "name": { + "description": "Dataset identifier", + "type": "string" + }, + "time_dimension": { + "$ref": "#/components/schemas/DatasetDimension", + "description": "Time dimension for time series queries" + } + }, + "required": [ + "name", + "dimensions", + "metrics" + ], + "type": "object" + }, + "DatasetDimension": { + "description": "Schema for a dataset dimension field.\n", + "properties": { + "label": { + "description": "Human-readable label", + "type": "string" + }, + "name": { + "description": "Dimension identifier", + "type": "string" + }, + "type": { + "description": "Data type (string, integer, boolean, datetime, date)", + "type": "string" + } + }, + "required": [ + "name", + "type", + "label" + ], + "type": "object" + }, + "DatasetMetric": { + "description": "Schema for a dataset metric field.\n", + "properties": { + "aggregation": { + "description": "Aggregation function (sum, count, avg, min, max, count_distinct)", + "type": "string" + }, + "label": { + "description": "Human-readable label", + "type": "string" + }, + "name": { + "description": "Metric identifier", + "type": "string" + }, + "type": { + "description": "Output type (integer, float)", + "type": "string" + } + }, + "required": [ + "name", + "type", + "aggregation", + "label" + ], + "type": "object" + }, + "DatasetQueryResult": { + "description": "Schema for a dataset query result.\n", + "properties": { + "columns": { + "description": "Column definitions with name, type, and label", + "items": { + "type": "object" + }, + "type": "array" + }, + "meta": { + "description": "Query metadata (total_rows, query_time_ms)", + "type": "object" + }, + "rows": { + "description": "Result rows as maps of column_name → value", + "items": { + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "columns", + "rows", + "meta" + ], + "type": "object" + }, + "DeletionConfirmation": { + "description": "Schema for delete operations that return a deleted count.", + "properties": { + "deleted_count": { + "description": "Number of deleted records", + "type": "integer" + } + }, + "required": [ + "deleted_count" + ], + "type": "object" + }, + "Developer": { + "description": "Schema for developer account billing settings.", + "properties": { + "auto_reup_amount_cents": { + "description": "Amount (cents) to charge when threshold is crossed", + "type": "integer" + }, + "auto_reup_enabled": { + "description": "Whether automatic credit reup is enabled", + "type": "boolean" + }, + "auto_reup_threshold_cents": { + "description": "Balance threshold (cents) to trigger auto-reup", + "type": "integer" + }, + "billing_provider_environment": { + "description": "Stripe environment (live or test)", + "type": "string" + }, + "billing_provider_id": { + "description": "Stripe customer ID", + "type": "string" + }, + "pending_plan": { + "description": "Plan change awaiting Stripe confirmation, or __clear__ to remove plan", + "type": "string" + }, + "plan": { + "description": "Active billing plan (e.g. paygo)", + "type": "string" + } + }, + "type": "object" + }, + "DeveloperOrg": { + "description": "Schema for an organization.\n\nMaps to serialized org output from developer portal API.\n", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Organization description", + "type": "string" + }, + "domain": { + "description": "Primary domain", + "type": "string" + }, + "id": { + "description": "Public ID (org_...)", + "type": "string" + }, + "industry": { + "description": "Industry category", + "type": "string" + }, + "name": { + "description": "Organization name", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier (nullable)", + "type": "string" + }, + "slug": { + "description": "URL-safe slug", + "type": "string" + }, + "status": { + "description": "Status (active, suspended, trialing)", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "website": { + "description": "Website URL", + "type": "string" + } + }, + "required": [ + "id", + "name", + "slug", + "domain" + ], + "type": "object" + }, + "DeveloperSystemAccessToken": { + "description": "Schema for a system access token (developer admin view).\n\nMaps to serialized system access token output from developer portal API.\n", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Public ID (sat_...)", + "type": "string" + }, + "last_used_at": { + "description": "Last used timestamp", + "format": "date-time", + "type": "string" + }, + "name": { + "description": "Optional label for the token", + "type": "string" + }, + "revoked_at": { + "description": "Revoked timestamp", + "format": "date-time", + "type": "string" + }, + "token": { + "description": "Raw JWT (only present on creation)", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "DeveloperThread": { + "description": "Schema for a thread in the developer portal.\n\nMaps to serialized thread output from developer portal API.\n", + "properties": { + "app_name": { + "description": "Associated app name", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Public ID (thr_...)", + "type": "string" + }, + "is_channel": { + "description": "Whether this is a channel thread", + "type": "boolean" + }, + "is_default": { + "description": "Whether this is the default thread", + "type": "boolean" + }, + "is_unlisted": { + "description": "Whether thread is hidden from listings", + "type": "boolean" + }, + "key": { + "description": "Unique key within owner scope", + "type": "string" + }, + "org": { + "description": "Organization (nullable)", + "type": "string" + }, + "owner": { + "description": "Owner public", + "type": "string" + }, + "owner_name": { + "description": "Owner display name", + "type": "string" + }, + "owner_type": { + "description": "Owner type: team, user, agent, or nil", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier (nullable)", + "type": "string" + }, + "slug": { + "description": "URL-friendly slug", + "type": "string" + }, + "title": { + "description": "Thread title", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "title" + ], + "type": "object" + }, + "DeveloperUser": { + "description": "Schema for a user (developer admin view).\n\nMaps to serialized user output from developer portal API.\n", + "properties": { + "alias": { + "description": "User alias", + "type": "string" + }, + "confirmed_at": { + "description": "Email confirmed timestamp", + "format": "date-time", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "email": { + "description": "Email address", + "type": "string" + }, + "full_name": { + "description": "Full name", + "type": "string" + }, + "id": { + "description": "Public ID (usr_...)", + "type": "string" + }, + "is_system_user": { + "description": "Whether this is a system user", + "type": "boolean" + }, + "org": { + "description": "Organization (nullable)", + "type": "string" + }, + "org_name": { + "description": "Organization display name (nullable)", + "type": "string" + }, + "org_role": { + "description": "Organization role (admin, member, viewer; nullable)", + "type": "string" + }, + "password": { + "description": "Temporary plaintext password returned on reset", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier (nullable)", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "email" + ], + "type": "object" + }, + "DeviceAuthorizationResponse": { + "description": "API schema for OAuth device authorization responses.", + "properties": { + "device_code": { + "description": "Device verification code", + "type": "string" + }, + "expires_in": { + "description": "TTL in seconds", + "type": "integer" + }, + "interval": { + "description": "Polling interval in seconds", + "type": "integer" + }, + "user_code": { + "description": "User-facing verification code", + "type": "string" + }, + "verification_uri": { + "description": "Base verification URI", + "type": "string" + }, + "verification_uri_complete": { + "description": "Full verification URI with code", + "type": "string" + } + }, + "required": [ + "device_code", + "user_code", + "verification_uri", + "verification_uri_complete", + "expires_in", + "interval" + ], + "type": "object" + }, + "DeviceAuthorizationStatusResponse": { + "description": "API schema for OAuth device authorization approval and denial responses.", + "properties": { + "status": { + "description": "Authorization status (approved or denied)", + "type": "string" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "Domain": { + "description": "Schema for a registered domain.\n\nMaps to serialized domain output from developer portal API.\n", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "domain": { + "description": "Domain name", + "type": "string" + }, + "id": { + "description": "Public ID (dad_...)", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "verified": { + "description": "Whether domain is verified", + "type": "boolean" + } + }, + "required": [ + "id", + "domain" + ], + "type": "object" + }, + "DomainEvent": { + "description": "Schema for a developer portal domain event.", + "properties": { + "agent": { + "description": "Agent identifier", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "event_name": { + "description": "Event name", + "type": "string" + }, + "id": { + "description": "Domain event ID", + "type": "string" + }, + "idempotency_key": { + "description": "Idempotency key for the event", + "type": "string" + }, + "payload": { + "description": "Event payload", + "type": "object" + }, + "team": { + "description": "Team identifier", + "type": "string" + }, + "user": { + "description": "User identifier", + "type": "string" + } + }, + "required": [ + "id", + "event_name", + "payload" + ], + "type": "object" + }, + "DomainEventPage": { + "description": "Schema for paginated domain event responses.", + "properties": { + "data": { + "description": "Domain events", + "items": { + "$ref": "#/components/schemas/DomainEvent" + }, + "type": "array" + }, + "has_next": { + "description": "Whether a next page exists", + "type": "boolean" + }, + "has_prev": { + "description": "Whether a previous page exists", + "type": "boolean" + }, + "page": { + "description": "Current page number", + "type": "integer" + }, + "page_size": { + "description": "Page size", + "type": "integer" + }, + "total_entries": { + "description": "Total entries", + "type": "integer" + }, + "total_pages": { + "description": "Total pages", + "type": "integer" + } + }, + "required": [ + "data", + "page", + "page_size", + "total_entries", + "total_pages", + "has_next", + "has_prev" + ], + "type": "object" + }, + "EncryptedSecret": { + "description": "Schema for an encrypted secret payload.", + "properties": { + "encrypted_value": { + "description": "Encrypted ciphertext value", + "type": "string" + } + }, + "required": [ + "encrypted_value" + ], + "type": "object" + }, + "EvalResult": { + "description": "Schema for an eval result.", + "properties": { + "agent_response": { + "description": "Agent response", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "duration_ms": { + "description": "Execution duration", + "type": "integer" + }, + "grader_details": { + "description": "Grader details", + "type": "object" + }, + "id": { + "description": "Eval result ID", + "type": "string" + }, + "run": { + "description": "Parent eval run identifier", + "type": "string" + }, + "score": { + "description": "Result score", + "type": "number" + }, + "status": { + "description": "Result status", + "type": "string" + }, + "task": { + "description": "Eval task identifier", + "type": "string" + }, + "task_input": { + "description": "Task input summary", + "type": "string" + }, + "transcript": { + "description": "Execution transcript", + "type": "object" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "run", + "status" + ], + "type": "object" + }, + "EvalResultList": { + "description": "Schema for eval result list responses.", + "properties": { + "data": { + "description": "Eval results", + "items": { + "$ref": "#/components/schemas/EvalResult" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "EvalRun": { + "description": "Schema for an eval run.", + "properties": { + "agent": { + "description": "Agent identifier", + "type": "string" + }, + "agent_name": { + "description": "Agent name", + "type": "string" + }, + "completed_at": { + "description": "Completion timestamp", + "format": "date-time", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Eval run ID", + "type": "string" + }, + "results": { + "description": "Eval results for this run", + "items": { + "$ref": "#/components/schemas/EvalResult" + }, + "type": "array" + }, + "started_at": { + "description": "Start timestamp", + "format": "date-time", + "type": "string" + }, + "status": { + "description": "Run status", + "type": "string" + }, + "suite": { + "description": "Eval suite identifier", + "type": "string" + }, + "summary": { + "description": "Aggregate run summary", + "type": "object" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "suite", + "status" + ], + "type": "object" + }, + "EvalRunList": { + "description": "Schema for eval run list responses.", + "properties": { + "data": { + "description": "Eval runs", + "items": { + "$ref": "#/components/schemas/EvalRun" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "EvalSuite": { + "description": "Schema for an eval suite.", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Suite description", + "type": "string" + }, + "id": { + "description": "Eval suite ID", + "type": "string" + }, + "name": { + "description": "Suite name", + "type": "string" + }, + "org": { + "description": "Owner organization identifier", + "type": "string" + }, + "status": { + "description": "Suite status", + "type": "string" + }, + "tasks": { + "description": "Eval tasks included in the suite", + "items": { + "$ref": "#/components/schemas/EvalTask" + }, + "type": "array" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owner user identifier", + "type": "string" + } + }, + "required": [ + "id", + "name", + "status" + ], + "type": "object" + }, + "EvalSuiteList": { + "description": "Schema for eval suite list responses.", + "properties": { + "data": { + "description": "Eval suites", + "items": { + "$ref": "#/components/schemas/EvalSuite" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "EvalTask": { + "description": "Schema for an eval task.", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "expected_outcome": { + "description": "Expected outcome", + "type": "string" + }, + "grading_criteria": { + "description": "Grading criteria", + "items": { + "type": "object" + }, + "type": "array" + }, + "id": { + "description": "Eval task ID", + "type": "string" + }, + "input_message": { + "description": "Task input message", + "type": "string" + }, + "mock_agent_memory": { + "description": "Mock agent memory", + "type": "object" + }, + "mock_context_items": { + "description": "Mock context items", + "items": { + "type": "object" + }, + "type": "array" + }, + "mock_datetime": { + "description": "Mock datetime", + "type": "string" + }, + "mock_participants": { + "description": "Mock participants", + "items": { + "type": "object" + }, + "type": "array" + }, + "mock_tools": { + "description": "Mock tool definitions", + "items": { + "type": "object" + }, + "type": "array" + }, + "status": { + "description": "Task status", + "type": "string" + }, + "suite": { + "description": "Parent eval suite identifier", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "suite", + "input_message", + "expected_outcome", + "status" + ], + "type": "object" + }, + "EvalTaskList": { + "description": "Schema for eval task list responses.", + "properties": { + "data": { + "description": "Eval tasks", + "items": { + "$ref": "#/components/schemas/EvalTask" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "EventCatalogEntry": { + "description": "Schema for an event catalog entry with full payload schema and sample.\n", + "properties": { + "description": { + "description": "Human-readable description", + "type": "string" + }, + "name": { + "description": "Event name (e.g., thread.created)", + "type": "string" + }, + "parent": { + "description": "Parent envelope name (only present for sub_event entries)", + "type": "string" + }, + "sample": { + "description": "Sample payload for this event", + "type": "object" + }, + "schema": { + "description": "JSON Schema describing the event payload", + "type": "object" + }, + "sub_events": { + "description": "Known sub-event types (only present for envelope entries)", + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "description": "Entry type: \"event\" for exact events, \"envelope\" for wildcard families", + "type": "string" + } + }, + "required": [ + "name", + "description", + "schema", + "sample" + ], + "type": "object" + }, + "EventType": { + "description": "Schema for an event type available for automation triggers.\n\nRepresents events from the workflow event catalog.\n", + "properties": { + "description": { + "description": "Human-readable description", + "type": "string" + }, + "name": { + "description": "Event name (e.g., thread.created)", + "type": "string" + } + }, + "required": [ + "name", + "description" + ], + "type": "object" + }, + "FileRefreshResult": { + "description": "API schema for file URL refresh responses.", + "properties": { + "image_source": { + "$ref": "#/components/schemas/ImageSource", + "description": "Updated image source with fresh URL" + }, + "success": { + "description": "Whether the refresh succeeded", + "type": "boolean" + } + }, + "required": [ + "success", + "image_source" + ], + "type": "object" + }, + "ImageSource": { + "description": "API schema for image source metadata.", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "Installation": { + "description": "API schema for an installation.", + "properties": { + "agent": { + "description": "Owning agent", + "type": "string" + }, + "config": { + "description": "Configuration", + "type": "object" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Installation ID (cin_...)", + "type": "string" + }, + "kind": { + "description": "Installation kind", + "type": "string" + }, + "shared_integration": { + "description": "Bound shared integration", + "type": "string" + }, + "state": { + "description": "Installation state", + "type": "string" + }, + "status_payload": { + "description": "Status payload", + "type": "object" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "InstallationKind": { + "description": "API schema for an installation kind.", + "properties": { + "accepts_sources": { + "description": "Whether this kind accepts sources", + "type": "boolean" + }, + "category": { + "description": "Category", + "type": "string" + }, + "config_schema": { + "description": "JSON schema for configuration", + "type": "object" + }, + "description": { + "description": "Description", + "type": "string" + }, + "kind": { + "description": "Installation kind identifier", + "type": "string" + }, + "label": { + "description": "Display label", + "type": "string" + }, + "provider": { + "description": "Integration provider", + "type": "string" + }, + "requires_integration": { + "description": "Whether this kind requires an integration", + "type": "boolean" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + "InstallationKindListResponse": { + "description": "List response for installation kinds.", + "properties": { + "data": { + "description": "List of installation kinds", + "items": { + "$ref": "#/components/schemas/InstallationKind" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "InstallationListResponse": { + "description": "List response for installations.", + "properties": { + "data": { + "description": "List of installations", + "items": { + "$ref": "#/components/schemas/Installation" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "InstallationSource": { + "description": "API schema for an installation source.", + "properties": { + "agent": { + "description": "Owning agent", + "type": "string" + }, + "context_installation": { + "description": "Installation ID", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Source ID (cso_...)", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "parent_source": { + "description": "Parent source ID", + "type": "string" + }, + "payload": { + "description": "Source payload", + "type": "object" + }, + "state": { + "description": "Source state", + "type": "string" + }, + "team": { + "description": "Team ID", + "type": "string" + }, + "thread": { + "description": "Thread ID", + "type": "string" + }, + "type": { + "description": "Source type", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User ID", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "InstallationSourceListResponse": { + "description": "List response for installation sources.", + "properties": { + "data": { + "description": "List of installation sources", + "items": { + "$ref": "#/components/schemas/InstallationSource" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "Integration": { + "description": "Schema for integration records with connector state.", + "properties": { + "id": { + "description": "Integration ID", + "type": "string" + }, + "org": { + "description": "Organization (nullable)", + "type": "string" + }, + "provider": { + "description": "Provider identifier", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier (nullable)", + "type": "string" + }, + "secret_group": { + "description": "Secret group", + "type": "string" + }, + "state": { + "description": "Connector state information", + "type": "object" + }, + "team": { + "description": "Team (if team-scoped)", + "type": "string" + }, + "user": { + "description": "User (if user-scoped)", + "type": "string" + }, + "workspace_key": { + "description": "Provider workspace identifier", + "type": "string" + } + }, + "required": [ + "id", + "provider", + "state" + ], + "type": "object" + }, + "IntegrationAction": { + "description": "Schema for integration action metadata.", + "properties": { + "description": { + "description": "Action description", + "type": "string" + }, + "json_schema": { + "description": "JSON Schema for action parameters", + "type": "object" + }, + "key": { + "description": "Action key (e.g., gmail.list_messages)", + "type": "string" + }, + "scopes_any_of": { + "description": "Required scope sets for this action", + "type": "object" + } + }, + "required": [ + "key", + "json_schema" + ], + "type": "object" + }, + "IntegrationCreateParams": { + "description": "Integration fields for auto-creating the underlying integration.\n\nWhen creating an agent installation for an `integration/*` kind, callers\ncan pass this object to auto-create the underlying Integration record.\nRequired fields depend on the kind's auth type:\n\n- `app_installation` kinds (slack_bot, github_app): requires `installation_id`\n- `oauth` kinds (gmail, outlook, slack): requires `access_token`\n", + "properties": { + "access_token": { + "description": "OAuth access token or API key", + "type": "string" + }, + "installation_id": { + "description": "External installation ID (e.g. GitHub App installation ID, Slack team_id)", + "type": "string" + }, + "metadata": { + "description": "Provider-specific metadata (e.g. bot_user_id)", + "type": "object" + }, + "refresh_token": { + "description": "OAuth refresh token", + "type": "string" + }, + "workspace_key": { + "description": "Workspace name or identifier", + "type": "string" + } + }, + "type": "object" + }, + "IntegrationProvider": { + "description": "Schema for an integration provider entry.\n\nRepresents an available integration provider (OAuth or MCP) that can be\nused with `create integration --provider `.\n", + "properties": { + "auth_type": { + "description": "Auth mechanism: oauth, bearer, or app_installation", + "type": "string" + }, + "description": { + "description": "Short description of the provider", + "type": "string" + }, + "display_name": { + "description": "Human-readable display name", + "type": "string" + }, + "provider": { + "description": "Provider key (e.g., google, mcp:system:mcp-github)", + "type": "string" + }, + "type": { + "description": "Provider type: oauth, mcp, or app_installation", + "type": "string" + } + }, + "required": [ + "provider", + "display_name", + "type", + "auth_type" + ], + "type": "object" + }, + "KeyValueStorageEntry": { + "description": "Schema for a key-value storage entry.\n\nMaps exactly to render_entry/1 output in ApiStorageController.\n", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "key": { + "description": "Storage key", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User", + "type": "string" + }, + "value": { + "description": "Stored value", + "type": "string" + } + }, + "required": [ + "user", + "key", + "value" + ], + "type": "object" + }, + "KeyValueStorageEntryList": { + "description": "List response for key-value storage entries.", + "properties": { + "data": { + "description": "Storage entries owned by the caller", + "items": { + "$ref": "#/components/schemas/KeyValueStorageEntry" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "KnowledgeSearchResult": { + "description": "API schema for a knowledge search result item.", + "properties": { + "content": { + "description": "Normalized content text", + "type": "string" + }, + "content_type": { + "description": "Content MIME type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Item ID (cim_...)", + "type": "string" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "raw_content": { + "description": "Raw content data", + "type": "object" + }, + "type": { + "description": "Source type (requires preloaded :source association)", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "LlmCall": { + "description": "Schema for an LLM session call.\n\nMaps to serialized LLM call output from developer portal API.\n", + "properties": { + "call_id": { + "description": "Unique call UUID", + "type": "string" + }, + "completion_tokens": { + "description": "Number of completion tokens", + "type": "integer" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "error_message": { + "description": "Error message if call failed", + "type": "string" + }, + "id": { + "description": "Public ID (alc_...)", + "type": "string" + }, + "latency_ms": { + "description": "Latency in milliseconds", + "type": "integer" + }, + "message_count": { + "description": "Number of messages included in the call metadata", + "type": "integer" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "model": { + "description": "LLM model name", + "type": "string" + }, + "prompt_tokens": { + "description": "Number of prompt tokens", + "type": "integer" + }, + "session_id": { + "description": "Session UUID grouping related calls", + "type": "string" + }, + "source": { + "description": "Machine-readable call source key", + "type": "string" + }, + "status": { + "description": "Call status (success or error)", + "type": "string" + }, + "team": { + "description": "Team if team-scoped", + "type": "string" + }, + "total_tokens": { + "description": "Total tokens (prompt + completion)", + "type": "integer" + }, + "trajectory": { + "description": "Trajectory identifier", + "type": "string" + }, + "user": { + "description": "User if user-scoped", + "type": "string" + } + }, + "required": [ + "id", + "session_id", + "call_id", + "prompt_tokens", + "completion_tokens", + "total_tokens", + "latency_ms" + ], + "type": "object" + }, + "LlmCallSourceOption": { + "description": "Source option metadata for LLM call filtering.\n", + "properties": { + "label": { + "description": "Human-friendly source label", + "type": "string" + }, + "source": { + "description": "Machine-readable LLM source key", + "type": "string" + } + }, + "required": [ + "source", + "label" + ], + "type": "object" + }, + "LlmCallTrajectory": { + "description": "Schema for LLM call trajectory contents.", + "properties": { + "download_url": { + "description": "Signed transcript download URL", + "type": "string" + }, + "messages": { + "description": "Trajectory messages", + "items": { + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "messages" + ], + "type": "object" + }, + "Media": { + "description": "API schema for a media item.", + "properties": { + "content_type": { + "description": "Original variant content type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "filename": { + "description": "Original variant filename", + "type": "string" + }, + "height": { + "description": "Original variant height", + "type": "integer" + }, + "id": { + "description": "Media ID", + "type": "string" + }, + "media_type": { + "description": "Media type", + "type": "string" + }, + "name": { + "description": "Media name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "url": { + "description": "Original variant URL", + "type": "string" + }, + "variants": { + "description": "Media variants", + "items": { + "$ref": "#/components/schemas/MediaVariant" + }, + "type": "array" + }, + "width": { + "description": "Original variant width", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "MediaVariant": { + "description": "API schema for a media variant.", + "properties": { + "content_type": { + "description": "File content type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "file": { + "description": "Storage file", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "height": { + "description": "Height in pixels", + "type": "integer" + }, + "id": { + "description": "Variant ID", + "type": "string" + }, + "image_source": { + "$ref": "#/components/schemas/ImageSource", + "description": "Image source metadata" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "url": { + "description": "Signed download URL", + "type": "string" + }, + "variant_key": { + "description": "Variant key (original, thumbnail, etc)", + "type": "string" + }, + "width": { + "description": "Width in pixels", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "Member": { + "description": "Schema for an app member (account with role on an app).\n\nMaps to serialized membership output from developer portal API.\n", + "properties": { + "account": { + "$ref": "#/components/schemas/MemberAccount", + "description": "Associated account" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Public ID (dar_...)", + "type": "string" + }, + "role": { + "description": "Role (admin, developer)", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "role", + "account" + ], + "type": "object" + }, + "MemberAccount": { + "description": "Associated account", + "properties": { + "alias": { + "description": "Account alias", + "type": "string" + }, + "email": { + "description": "Account email", + "type": "string" + }, + "full_name": { + "description": "Full name", + "type": "string" + }, + "id": { + "description": "Account public ID (dva_...)", + "type": "string" + } + }, + "required": [ + "id", + "email" + ], + "type": "object" + }, + "MemberInvite": { + "description": "Schema for a pending member invite.\n\nMaps to serialized invite output from developer portal API.\n", + "properties": { + "email": { + "description": "Invitee's email address", + "type": "string" + }, + "expires_at": { + "description": "When the invite expires", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Public ID (ami_...)", + "type": "string" + }, + "invited_at": { + "description": "When the invite was created", + "format": "date-time", + "type": "string" + }, + "inviter": { + "description": "Account that sent the invite", + "type": "object" + }, + "role": { + "description": "Role (admin, developer)", + "type": "string" + }, + "status": { + "description": "Invite status (pending or expired)", + "type": "string" + } + }, + "required": [ + "id", + "email", + "role", + "status", + "invited_at", + "expires_at" + ], + "type": "object" + }, + "Message": { + "description": "API schema for a chat message.", + "properties": { + "actors": { + "description": "Message actors", + "items": { + "$ref": "#/components/schemas/Actor" + }, + "type": "array" + }, + "agent": { + "description": "Agent if sent by an agent user", + "type": "string" + }, + "attachments": { + "description": "Message attachments", + "items": { + "$ref": "#/components/schemas/Attachment" + }, + "type": "array" + }, + "branched_thread": { + "description": "Branched thread (if message spawned a thread)", + "type": "string" + }, + "content": { + "description": "Message content", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "has_replies": { + "description": "Whether message has replies", + "type": "boolean" + }, + "id": { + "description": "Message ID (msg_...)", + "type": "string" + }, + "idempotency_key": { + "description": "Client-provided idempotency key", + "type": "string" + }, + "legacy_agent": { + "description": "Legacy agent if sent by legacy chat agent", + "type": "string" + }, + "metadata": { + "description": "Message metadata", + "type": "object" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "reactions": { + "description": "Message reactions", + "items": { + "$ref": "#/components/schemas/MessageReaction" + }, + "type": "array" + }, + "rendering_mode": { + "description": "Rendering mode hint", + "type": "string" + }, + "replies": { + "description": "Inline replies (if loaded)", + "items": { + "type": "object" + }, + "type": "array" + }, + "replies_after_cursor": { + "description": "Cursor for replies pagination", + "type": "string" + }, + "replies_before_cursor": { + "description": "Cursor for replies pagination", + "type": "string" + }, + "reply_count": { + "description": "Number of replies", + "type": "integer" + }, + "reply_to": { + "description": "Parent message object (if loaded)", + "type": "object" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "thread": { + "description": "Parent thread", + "type": "string" + }, + "user": { + "description": "Author user (public ID or expanded object when loaded)", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "MessageReaction": { + "description": "Schema for inline message reactions.\n\nThis is the compact format used in Message.reactions[], which differs from\nthe full Reaction schema used in standalone reaction endpoints.\nMaps to format_reactions_for_client/1 output.\n", + "properties": { + "payload": { + "description": "Reaction payload (e.g., {emoji: '👍'})", + "type": "object" + }, + "type": { + "description": "Reaction type (e.g., emoji_reaction)", + "type": "string" + }, + "user": { + "description": "User who added the reaction", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "MessageUpdateParams": { + "description": "Schema for message update request parameters.\n\nUsed as the body wrapper when updating a message.\n", + "properties": { + "content": { + "description": "New message content", + "type": "string" + } + }, + "type": "object" + }, + "MetadataFilter": { + "description": "Filter object for matching thread metadata.", + "properties": { + "key": { + "description": "Metadata key to match", + "type": "string" + }, + "type": { + "description": "Filter type (must be \"metadata\")", + "type": "string" + }, + "value": { + "description": "Metadata value to match", + "type": "string" + } + }, + "required": [ + "type", + "key", + "value" + ], + "type": "object" + }, + "NotificationSettingUpdate": { + "description": "Schema for a single notification setting update entry.\n", + "properties": { + "level": { + "description": "Notification level (global, team, thread)", + "type": "string" + }, + "muted": { + "description": "Whether notifications are muted", + "type": "boolean" + }, + "team": { + "description": "Team (required for team-level settings)", + "type": "string" + }, + "thread": { + "description": "Thread (required for thread-level settings)", + "type": "string" + } + }, + "required": [ + "level", + "muted" + ], + "type": "object" + }, + "OAuthClient": { + "description": "Schema for an OAuth client registration.", + "properties": { + "client_id": { + "description": "Public OAuth client ID", + "type": "string" + }, + "client_name": { + "description": "Display name for the client", + "type": "string" + }, + "client_secret": { + "description": "Client secret shown only at creation", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "enabled": { + "description": "Whether the client is enabled", + "type": "boolean" + }, + "id": { + "description": "OAuth client registration ID", + "type": "string" + }, + "redirect_uris": { + "description": "Allowed redirect URIs", + "items": { + "type": "string" + }, + "type": "array" + }, + "scopes": { + "description": "Allowed OAuth scopes", + "items": { + "type": "string" + }, + "type": "array" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "client_id", + "client_name", + "redirect_uris", + "scopes", + "enabled" + ], + "type": "object" + }, + "OAuthClientList": { + "description": "Schema for OAuth client list responses.", + "properties": { + "data": { + "description": "OAuth clients", + "items": { + "$ref": "#/components/schemas/OAuthClient" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "OAuthError": { + "description": "OAuth error response per RFC 6749", + "properties": { + "error": { + "description": "Error code (e.g. slow_down, invalid_grant)", + "type": "string" + }, + "error_description": { + "description": "Human-readable error description", + "type": "string" + } + }, + "required": [ + "error" + ], + "type": "object" + }, + "OAuthProvider": { + "description": "Schema for an OAuth provider configuration.\n\nMaps to serialized OAuth provider output from developer portal API.\n", + "properties": { + "callback_urls": { + "description": "Allowed callback URLs", + "items": { + "type": "string" + }, + "type": "array" + }, + "client_id": { + "description": "OAuth client ID", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "display_name": { + "description": "Display name", + "type": "string" + }, + "enabled": { + "description": "Whether provider is enabled", + "type": "boolean" + }, + "id": { + "description": "Public ID (dop_...)", + "type": "string" + }, + "provider": { + "description": "Provider type (github, google)", + "type": "string" + }, + "scopes": { + "description": "OAuth scopes", + "items": { + "type": "string" + }, + "type": "array" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "provider", + "client_id" + ], + "type": "object" + }, + "OAuthTokenResponse": { + "description": "API schema for OAuth token endpoint responses.", + "properties": { + "access_token": { + "description": "OAuth access token", + "type": "string", + "x-sdk": "access_token" + }, + "expires_in": { + "description": "Token TTL in seconds", + "type": "integer", + "x-sdk": "token_expiry" + }, + "refresh_token": { + "description": "OAuth refresh token", + "type": "string", + "x-sdk": "refresh_token" + }, + "scope": { + "description": "Granted scopes (space-separated)", + "type": "string" + }, + "token_type": { + "description": "Token type (Bearer)", + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User", + "description": "Authenticated user" + } + }, + "required": [ + "access_token", + "token_type", + "expires_in" + ], + "type": "object" + }, + "Org": { + "description": "API schema for an organization.", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Description", + "type": "string" + }, + "domain": { + "description": "Domain", + "type": "string" + }, + "id": { + "description": "Organization ID (org_...)", + "type": "string" + }, + "industry": { + "description": "Industry", + "type": "string" + }, + "name": { + "description": "Organization name", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "slug": { + "description": "URL slug", + "type": "string" + }, + "status": { + "description": "Status", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "website": { + "description": "Website URL", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "OrgAuthPolicy": { + "description": "Schema for org auth policy response.\n", + "properties": { + "allowed_email_domains": { + "description": "Allowed email domains", + "items": { + "type": "string" + }, + "type": "array" + }, + "auth_method": { + "description": "Auth method (default, sso)", + "type": "string" + }, + "require_2fa": { + "description": "Whether 2FA is required", + "type": "boolean" + }, + "sso_providers": { + "description": "Enabled SSO providers", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "auth_method", + "require_2fa", + "sso_providers", + "allowed_email_domains" + ], + "type": "object" + }, + "OrgEnvVar": { + "description": "Schema for an organization environment variable response that includes the\nplaintext value.\n", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Optional description", + "type": "string" + }, + "id": { + "description": "Organization env var ID (oev_...)", + "type": "string" + }, + "key": { + "description": "Environment variable key", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "value": { + "description": "Environment variable value", + "type": "string" + } + }, + "required": [ + "id", + "key", + "value" + ], + "type": "object" + }, + "OrgEnvVarMasked": { + "description": "Schema for an organization environment variable response that masks the\nsecret value.\n", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Optional description", + "type": "string" + }, + "id": { + "description": "Organization env var ID (oev_...)", + "type": "string" + }, + "key": { + "description": "Environment variable key", + "type": "string" + }, + "masked_value": { + "description": "Masked environment variable value", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "key", + "masked_value" + ], + "type": "object" + }, + "OrgEnvVarMaskedList": { + "description": "Schema for organization environment variable list responses.", + "properties": { + "data": { + "description": "Organization environment variables", + "items": { + "$ref": "#/components/schemas/OrgEnvVarMasked" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "OrgSamlSettings": { + "description": "Schema for SAML provider settings response.\n\nMasks certificate contents — exposes only boolean flags indicating whether\na primary/secondary certificate is configured.\n", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "enabled": { + "description": "Whether the provider is active", + "type": "boolean" + }, + "entity_id": { + "description": "IdP entity ID", + "type": "string" + }, + "has_certificate": { + "description": "Whether a primary certificate is configured", + "type": "boolean" + }, + "has_certificate_secondary": { + "description": "Whether a secondary certificate is configured", + "type": "boolean" + }, + "id": { + "description": "SAML provider ID (saml_...)", + "type": "string" + }, + "label": { + "description": "Display label for the SSO button", + "type": "string" + }, + "sso_url": { + "description": "IdP SSO URL", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "entity_id", + "sso_url" + ], + "type": "object" + }, + "PaginatedMessages": { + "description": "Schema for paginated messages response.\n\nUsed by thread messages list endpoints.\n", + "properties": { + "after_cursor": { + "description": "Cursor for fetching items after this point", + "type": "string" + }, + "before_cursor": { + "description": "Cursor for fetching items before this point", + "type": "string" + }, + "messages": { + "description": "List of message objects", + "items": { + "$ref": "#/components/schemas/Message" + }, + "type": "array" + } + }, + "required": [ + "messages" + ], + "type": "object" + }, + "PaginatedReplies": { + "description": "Schema for paginated message replies response.\n\nUsed by message replies list endpoints.\nNote: This response is NOT wrapped in a \"data\" field.\n", + "properties": { + "after_cursor": { + "description": "Cursor for fetching items after this point", + "type": "string" + }, + "before_cursor": { + "description": "Cursor for fetching items before this point", + "type": "string" + }, + "has_more": { + "description": "Whether more replies exist beyond the current page", + "type": "boolean" + }, + "replies": { + "description": "List of reply message objects", + "items": { + "$ref": "#/components/schemas/Message" + }, + "type": "array" + }, + "total_count": { + "description": "Total number of replies", + "type": "integer" + } + }, + "required": [ + "replies" + ], + "type": "object" + }, + "PasswordUpdateParams": { + "description": "Schema for password update parameters.\n", + "properties": { + "password": { + "description": "New password", + "type": "string" + }, + "password_confirmation": { + "description": "New password confirmation", + "type": "string" + } + }, + "required": [ + "password", + "password_confirmation" + ], + "type": "object" + }, + "Persona": { + "description": "API schema for a persona.", + "properties": { + "activated": { + "description": "Whether persona is activated", + "type": "boolean" + }, + "agent": { + "description": "Associated agent (always nil)", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Persona ID", + "type": "string" + }, + "is_active": { + "description": "Whether persona is active", + "type": "boolean" + }, + "is_enabled_for_thread": { + "description": "Whether persona is enabled for thread", + "type": "boolean" + }, + "metadata": { + "description": "Persona metadata", + "type": "object" + }, + "name": { + "description": "Persona display name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "personality": { + "description": "Persona personality description", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "PersonaCreateParams": { + "description": "Schema for persona creation parameters.\n", + "properties": { + "name": { + "description": "Persona display name", + "type": "string" + }, + "personality": { + "description": "Persona personality description", + "type": "string" + }, + "profile_picture_style": { + "description": "Profile picture generation style", + "type": "string" + } + }, + "required": [ + "name", + "personality" + ], + "type": "object" + }, + "PersonaUpdateParams": { + "description": "Schema for persona update parameters.\n", + "properties": { + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "name": { + "description": "Persona display name", + "type": "string" + }, + "personality": { + "description": "Persona personality description", + "type": "string" + } + }, + "type": "object" + }, + "PictureParams": { + "description": "Schema for picture upload parameters.\n\nUsed for uploading profile pictures via base64 encoded data.\n", + "properties": { + "data": { + "description": "Base64 encoded image data", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "mime_type": { + "description": "MIME type of the image", + "type": "string" + } + }, + "required": [ + "data", + "mime_type", + "filename" + ], + "type": "object" + }, + "PublicOrg": { + "description": "Public org schema for authenticated endpoints.\n\nOnly exposes fields safe for any authenticated user: id, name, domain,\nand logo. Does NOT expose sandbox, status, industry, description, or\nother internal fields that the Developer.Org schema includes.\n", + "properties": { + "domain": { + "description": "Primary domain", + "type": "string" + }, + "id": { + "description": "Public ID (org_...)", + "type": "string" + }, + "name": { + "description": "Organization name", + "type": "string" + } + }, + "required": [ + "id", + "name", + "domain" + ], + "type": "object" + }, + "PushNotificationResult": { + "description": "API schema for push notification test results.", + "properties": { + "results": { + "description": "Per-device results", + "items": { + "type": "object" + }, + "type": "array" + }, + "success": { + "description": "Whether the notification was sent", + "type": "boolean" + }, + "total_sent": { + "description": "Number of notifications sent", + "type": "integer" + } + }, + "required": [ + "success" + ], + "type": "object" + }, + "Reaction": { + "description": "API schema for a message reaction.", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "feedback_type": { + "description": "Type of feedback (e.g., emoji_reaction)", + "type": "string" + }, + "id": { + "description": "Reaction ID (umf_...)", + "type": "string" + }, + "message": { + "description": "Message the reaction is on", + "type": "string" + }, + "payload": { + "description": "Reaction payload (e.g., {emoji: ...})", + "type": "object" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User who added the reaction", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "ResolvedTool": { + "description": false, + "properties": { + "description": { + "description": "What this tool does", + "type": "string" + }, + "name": { + "description": "Callable tool function name", + "type": "string" + }, + "parameters": { + "description": "JSON Schema describing the expected input", + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "RoutinePreset": { + "description": "A routine preset with its metadata.", + "properties": { + "config": { + "description": "Default configuration", + "type": "object" + }, + "description": { + "description": "Preset description", + "type": "string" + }, + "event_type": { + "description": "Event type", + "type": "string" + }, + "label": { + "description": "Display label", + "type": "string" + }, + "name": { + "description": "Preset name", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "RuntimeEnvVar": { + "description": "Schema for runtime environment variable metadata.", + "properties": { + "description": { + "description": "Optional description", + "type": "string" + }, + "key": { + "description": "Environment variable key", + "type": "string" + }, + "source": { + "description": "Source of the env var (app or org)", + "type": "string" + } + }, + "required": [ + "key", + "source" + ], + "type": "object" + }, + "RuntimeEnvVarList": { + "description": "Schema for runtime environment variable metadata list responses.", + "properties": { + "data": { + "description": "Runtime environment variables available to the current script context", + "items": { + "$ref": "#/components/schemas/RuntimeEnvVar" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "Sandbox": { + "description": "Schema for a developer sandbox.\n", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Public ID (dsb_...)", + "type": "string" + }, + "keys": { + "description": "Sandbox API keys", + "items": { + "$ref": "#/components/schemas/SandboxKey" + }, + "type": "array" + }, + "name": { + "description": "Sandbox name", + "type": "string" + }, + "slug": { + "description": "Sandbox slug (unique per app)", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "name", + "slug" + ], + "type": "object" + }, + "SandboxEmail": { + "description": "Schema for a sandbox-captured email.\n", + "properties": { + "bcc": { + "description": "BCC recipients", + "items": { + "type": "object" + }, + "type": "array" + }, + "cc": { + "description": "CC recipients", + "items": { + "type": "object" + }, + "type": "array" + }, + "created_at": { + "description": "When the email was captured", + "format": "date-time", + "type": "string" + }, + "from_address": { + "description": "Sender email address", + "type": "string" + }, + "from_name": { + "description": "Sender display name", + "type": "string" + }, + "headers": { + "description": "Custom email headers", + "type": "object" + }, + "html_body": { + "description": "HTML body", + "type": "string" + }, + "id": { + "description": "Public ID (sem_...)", + "type": "string" + }, + "reply_to": { + "description": "Reply-to address", + "type": "object" + }, + "subject": { + "description": "Email subject", + "type": "string" + }, + "text_body": { + "description": "Plain text body", + "type": "string" + }, + "to": { + "description": "Recipients [{name, address}]", + "items": { + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "id", + "from_address", + "to" + ], + "type": "object" + }, + "SandboxKey": { + "description": "Schema for a sandbox API key.\n", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "expires_at": { + "description": "Expiry timestamp", + "format": "date-time", + "type": "string" + }, + "full_key": { + "description": "Full key shown only once at creation", + "type": "string" + }, + "id": { + "description": "Public ID (dsk_...)", + "type": "string" + }, + "key_hint": { + "description": "Last 4 chars hint", + "type": "string" + }, + "key_value": { + "description": "Full key (publishable only)", + "type": "string" + }, + "last_used_at": { + "description": "Last used timestamp", + "format": "date-time", + "type": "string" + }, + "status": { + "description": "Status (active, revoked)", + "type": "string" + }, + "type": { + "description": "Key type (publishable, secret)", + "type": "string" + } + }, + "required": [ + "id", + "type", + "status" + ], + "type": "object" + }, + "ScheduledJob": { + "description": "API schema for a scheduled job.", + "properties": { + "args": { + "description": "Job arguments", + "type": "object" + }, + "description": { + "description": "Job description", + "type": "string" + }, + "id": { + "description": "Job ID", + "type": "string" + }, + "recurring": { + "description": "Recurrence pattern", + "type": "string" + }, + "scheduled_at": { + "description": "Scheduled execution time", + "format": "date-time", + "type": "string" + }, + "thread": { + "description": "Thread", + "type": "string" + }, + "title": { + "description": "Job title", + "type": "string" + }, + "worker": { + "description": "Worker module name", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "Scrape": { + "description": "API schema for a scrape result.", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Page description", + "type": "string" + }, + "id": { + "description": "Scrape ID (scp_...)", + "type": "string" + }, + "image_height": { + "description": "Image height in pixels", + "type": "integer" + }, + "image_url": { + "description": "Image URL", + "type": "string" + }, + "image_width": { + "description": "Image width in pixels", + "type": "integer" + }, + "last_scraped_at": { + "description": "Last scraped timestamp", + "format": "date-time", + "type": "string" + }, + "metadata": { + "description": "Scrape metadata", + "type": "object" + }, + "status": { + "description": "Scrape status", + "type": "string" + }, + "title": { + "description": "Page title", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "url": { + "description": "Scraped URL", + "type": "string" + }, + "version": { + "description": "Scrape version", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "Secret": { + "description": "API schema for a secret (user, team, or user-team).", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Secret description", + "type": "string" + }, + "id": { + "description": "Secret ID", + "type": "string" + }, + "last_accessed_at": { + "description": "Last accessed timestamp", + "format": "date-time", + "type": "string" + }, + "name": { + "description": "Secret name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "secret_group": { + "description": "Secret group", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "SecretValue": { + "description": "API schema for a decrypted secret value response.", + "properties": { + "name": { + "description": "Secret name", + "type": "string" + }, + "value": { + "description": "Decrypted secret value", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "StatusPing": { + "description": "API schema for the status/ping health check response.", + "properties": { + "success": { + "description": "Whether the ping succeeded", + "type": "boolean" + }, + "token": { + "description": "Token status details", + "type": "object" + }, + "user": { + "$ref": "#/components/schemas/User", + "description": "Authenticated user (if token is valid)" + } + }, + "required": [ + "success", + "token" + ], + "type": "object" + }, + "StorageFile": { + "description": "API schema for a storage file.", + "properties": { + "content_type": { + "description": "MIME content type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "id": { + "description": "File ID", + "type": "string" + }, + "image_source": { + "$ref": "#/components/schemas/ImageSource", + "description": "Image source metadata" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "size": { + "description": "File size in bytes", + "type": "integer" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "url": { + "description": "Signed download URL", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "SystemAccessToken": { + "description": "API schema for a system access token.", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Token ID (sat_...)", + "type": "string" + }, + "last_used_at": { + "description": "Last used timestamp", + "format": "date-time", + "type": "string" + }, + "name": { + "description": "Token name", + "type": "string" + }, + "revoked_at": { + "description": "Revocation timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "Task": { + "description": "API schema for a task.", + "properties": { + "closed_at": { + "description": "When the task was closed", + "format": "date-time", + "type": "string" + }, + "comments_count": { + "description": "Number of comments", + "type": "integer" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "created_by": { + "description": "Legacy creator", + "type": "string" + }, + "created_by_actor": { + "description": "Creator actor details", + "type": "object" + }, + "created_by_persona": { + "description": "Creator persona", + "type": "string" + }, + "created_by_type": { + "description": "Creator type (user, agent)", + "type": "string" + }, + "created_by_user": { + "description": "Creator user", + "type": "string" + }, + "description": { + "description": "Task description", + "type": "string" + }, + "due_date": { + "description": "Due date", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Task ID (tsk_...)", + "type": "string" + }, + "links": { + "description": "Related links", + "type": "object" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "name": { + "description": "Task name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "owner": { + "description": "Legacy owner", + "type": "string" + }, + "owner_actor": { + "description": "Owner actor details", + "type": "object" + }, + "owner_persona": { + "description": "Owner persona", + "type": "string" + }, + "owner_user": { + "description": "Owner user", + "type": "string" + }, + "priority": { + "description": "Priority level (0-4)", + "type": "integer" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "status": { + "description": "Task status", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "name", + "status" + ], + "type": "object" + }, + "TaskActivityEntry": { + "description": "Schema for a task activity entry.\n\nMaps exactly to the activity entries produced by TaskActivitySentenceGenerator.\n", + "properties": { + "event_id": { + "description": "Event ID", + "type": "string" + }, + "event_type": { + "description": "Type of event", + "type": "string" + }, + "sentence": { + "description": "Human-readable description of the activity", + "type": "string" + }, + "timestamp": { + "description": "When the event occurred", + "format": "date-time", + "type": "string" + } + }, + "type": "object" + }, + "TaskComment": { + "description": "API schema for a task comment.", + "properties": { + "author": { + "description": "Legacy author", + "type": "string" + }, + "author_actor": { + "description": "Author actor details", + "type": "object" + }, + "author_persona": { + "description": "Author persona", + "type": "string" + }, + "author_user": { + "description": "Author user", + "type": "string" + }, + "body": { + "description": "Comment body text", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Comment ID", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "task": { + "description": "Task", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "body" + ], + "type": "object" + }, + "TaskCreateParams": { + "description": "Schema for task creation parameters.\n\nUsed by both Users.Tasks.Create and Teams.Tasks.Create actions.\n", + "properties": { + "description": { + "description": "Task description", + "type": "string" + }, + "due_date": { + "description": "Due date", + "format": "date-time", + "type": "string" + }, + "links": { + "description": "Related links", + "type": "object" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "name": { + "description": "Task name", + "type": "string" + }, + "owner_persona": { + "description": "Owner persona if assigned to agent", + "type": "string" + }, + "owner_user": { + "description": "Owner user if assigned to user", + "type": "string" + }, + "priority": { + "description": "Priority level (0-4)", + "type": "integer" + }, + "status": { + "description": "Task status (open, in_progress, done)", + "type": "string" + }, + "task": { + "description": "Custom task ID (optional, auto-generated if not provided)", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "TaskUpdateParams": { + "description": "Schema for task update parameters.\n\nUsed by both Users.Tasks.Update and Teams.Tasks.Update actions.\nAll fields are optional since updates only modify provided fields.\n", + "properties": { + "description": { + "description": "Task description", + "type": "string" + }, + "due_date": { + "description": "Due date", + "format": "date-time", + "type": "string" + }, + "links": { + "description": "Related links", + "type": "object" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "name": { + "description": "Task name", + "type": "string" + }, + "owner_persona": { + "description": "Owner persona if assigned to agent", + "type": "string" + }, + "owner_user": { + "description": "Owner user if assigned to user", + "type": "string" + }, + "priority": { + "description": "Priority level (0-4)", + "type": "integer" + }, + "status": { + "description": "Task status (open, in_progress, done)", + "type": "string" + } + }, + "type": "object" + }, + "Team": { + "description": "API schema for a team.", + "properties": { + "acl": { + "$ref": "#/components/schemas/Acl", + "description": "Access control list. Contains grants array with principal_type, principal, and actions. Null when no ACL restrictions are applied." + }, + "app": { + "description": "Application", + "type": "string" + }, + "badges": { + "description": "Badge counts by category", + "type": "object" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Team description", + "type": "string" + }, + "id": { + "description": "Team ID", + "type": "string" + }, + "membership_status": { + "description": "Viewer's membership role (owner, admin, member) or null if not a member", + "type": "string" + }, + "metadata": { + "description": "Team metadata", + "type": "object" + }, + "name": { + "description": "Team name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "slug": { + "description": "URL slug", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "TeamCreateParams": { + "description": "Schema for team creation parameters.\n", + "properties": { + "acl": { + "$ref": "#/components/schemas/Acl", + "description": "Access control list" + }, + "description": { + "description": "Team description", + "type": "string" + }, + "metadata": { + "description": "Arbitrary key-value metadata", + "type": "object" + }, + "name": { + "description": "Team name", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "TeamInvite": { + "description": "Schema for a team invite response.\n", + "properties": { + "code": { + "description": "6-character invite code", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + }, + "TeamMember": { + "description": "Schema for a team member.\n", + "properties": { + "alias": { + "description": "User alias", + "type": "string" + }, + "email": { + "description": "User email", + "type": "string" + }, + "full_name": { + "description": "User full name", + "type": "string" + }, + "id": { + "description": "Public user ID", + "type": "string" + }, + "role": { + "description": "Role in the team (owner, admin, member)", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "TeamMembership": { + "description": "API schema for a team membership.", + "properties": { + "agent": { + "$ref": "#/components/schemas/Agent", + "description": "Agent object (when loaded)" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Membership ID", + "type": "string" + }, + "joined_at": { + "description": "Join timestamp", + "format": "date-time", + "type": "string" + }, + "metadata": { + "description": "Membership metadata", + "type": "object" + }, + "name": { + "description": "Member name", + "type": "string" + }, + "profile_picture": { + "$ref": "#/components/schemas/ImageSource", + "description": "Profile picture" + }, + "role": { + "description": "Role in team", + "type": "string" + }, + "team": { + "description": "Team object (when loaded)", + "type": "object" + }, + "type": { + "description": "Member type (user, agent, unknown)", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User", + "description": "User object (when loaded)" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "TeamMembershipListResponse": { + "description": "Paginated list response for team memberships.", + "properties": { + "data": { + "description": "List of team memberships", + "items": { + "$ref": "#/components/schemas/TeamMembership" + }, + "type": "array" + }, + "has_next": { + "description": "Whether a next page exists", + "type": "boolean" + }, + "has_prev": { + "description": "Whether a previous page exists", + "type": "boolean" + }, + "page": { + "description": "Current page number", + "type": "integer" + }, + "page_size": { + "description": "Results per page", + "type": "integer" + }, + "total_entries": { + "description": "Total number of entries", + "type": "integer" + }, + "total_pages": { + "description": "Total number of pages", + "type": "integer" + } + }, + "required": [ + "data" + ], + "type": "object" + }, + "TeamUpdateParams": { + "description": "Schema for team update parameters.\n", + "properties": { + "acl": { + "$ref": "#/components/schemas/Acl", + "description": "Access control list" + }, + "description": { + "description": "Team description", + "type": "string" + }, + "metadata": { + "description": "Arbitrary key-value metadata", + "type": "object" + }, + "name": { + "description": "Team name", + "type": "string" + }, + "profile_picture": { + "$ref": "#/components/schemas/TeamUpdateParamsProfilePicture", + "description": "Base64-encoded profile picture" + } + }, + "type": "object" + }, + "TeamUpdateParamsProfilePicture": { + "description": "Base64-encoded profile picture", + "properties": { + "data": { + "description": "Base64 encoded image data", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "mime_type": { + "description": "MIME type of the image", + "type": "string" + } + }, + "type": "object" + }, + "TemplateFilter": { + "description": "Filter object for matching personas by template ID.", + "properties": { + "id": { + "description": "Persona template ID to match", + "type": "string" + }, + "type": { + "description": "Filter type (must be \"template\")", + "type": "string" + } + }, + "required": [ + "type", + "id" + ], + "type": "object" + }, + "Thread": { + "description": "API schema for a chat thread.", + "properties": { + "agent_user": { + "description": "Owning agent user", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "creator": { + "$ref": "#/components/schemas/User", + "description": "Creator user object" + }, + "description": { + "description": "Thread description", + "type": "string" + }, + "id": { + "description": "Thread ID (thr_...)", + "type": "string" + }, + "is_channel": { + "description": "Whether this is a channel", + "type": "boolean" + }, + "is_default": { + "description": "Whether this is the default thread", + "type": "boolean" + }, + "is_transient": { + "description": "Whether this thread is transient", + "type": "boolean" + }, + "is_unlisted": { + "description": "Whether this thread is unlisted", + "type": "boolean" + }, + "key": { + "description": "Thread key", + "type": "string" + }, + "last_activity": { + "description": "Last activity timestamp", + "format": "date-time", + "type": "string" + }, + "metadata": { + "description": "Thread metadata", + "type": "object" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "parent_message": { + "$ref": "#/components/schemas/Message", + "description": "Parent message object" + }, + "participant": { + "description": "Participant users", + "items": { + "type": "string" + }, + "type": "array" + }, + "participants": { + "description": "Participant user objects", + "items": { + "$ref": "#/components/schemas/User" + }, + "type": "array" + }, + "participating_actor": { + "description": "Actors participating in thread", + "items": { + "type": "string" + }, + "type": "array" + }, + "participating_agents": { + "description": "Agents participating in thread", + "items": { + "$ref": "#/components/schemas/Agent" + }, + "type": "array" + }, + "role": { + "description": "User's role in the thread", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "settings": { + "$ref": "#/components/schemas/ThreadSettings", + "description": "Thread settings" + }, + "slug": { + "description": "Thread slug", + "type": "string" + }, + "sub_threads": { + "description": "Sub-threads", + "items": { + "type": "object" + }, + "type": "array" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "title": { + "description": "Thread title", + "type": "string" + }, + "ttl": { + "description": "Time-to-live in seconds", + "type": "integer" + }, + "unread_count": { + "description": "Unread message count", + "type": "integer" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "ThreadAction": { + "description": "API schema for a thread action.", + "properties": { + "call_to_action": { + "description": "Call to action text", + "type": "string" + }, + "completion_result": { + "description": "Result after action completion", + "type": "object" + }, + "id": { + "description": "Thread action ID (tha_...)", + "type": "string" + }, + "metadata": { + "description": "Action metadata", + "type": "object" + }, + "native_template": { + "description": "Native template for mobile clients", + "type": "object" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "path": { + "description": "URL path for action", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "status": { + "description": "Action status (active, canceled, done)", + "type": "string" + }, + "type": { + "description": "Action type (connect_google, add_credential, send_email)", + "type": "string" + } + }, + "required": [ + "id", + "type", + "status" + ], + "type": "object" + }, + "ThreadCreateParams": { + "description": "Schema for thread creation parameters.\n\nUsed by both Users.Threads.Create and Teams.Threads.Create actions.\n", + "properties": { + "create_legacy_agent": { + "description": "Create a legacy chat agent for this thread", + "type": "boolean" + }, + "description": { + "description": "Thread description", + "type": "string" + }, + "is_unlisted": { + "description": "Whether the thread is unlisted", + "type": "boolean" + }, + "key": { + "description": "Unique key for the thread", + "type": "string" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "org_id": { + "description": "Organization ID", + "type": "string" + }, + "profile_picture": { + "$ref": "#/components/schemas/ThreadCreateParamsProfilePicture", + "description": "Base64 encoded profile picture" + }, + "settings": { + "$ref": "#/components/schemas/ThreadSettings", + "description": "Thread settings" + }, + "title": { + "description": "Thread title", + "type": "string" + } + }, + "type": "object" + }, + "ThreadCreateParamsProfilePicture": { + "description": "Base64 encoded profile picture", + "properties": { + "data": { + "description": "Base64 encoded image data", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "mime_type": { + "description": "MIME type of the image", + "type": "string" + } + }, + "type": "object" + }, + "ThreadDetail": { + "description": "Schema for thread detail response in the developer portal.\n\nExtended thread data including members and associated entities.\n", + "properties": { + "agent": { + "description": "Associated agent info (id, name)", + "type": "object" + }, + "app_name": { + "description": "Associated app name", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "creator": { + "description": "Thread creator info (id, email, full_name)", + "type": "object" + }, + "id": { + "description": "Public ID (thr_...)", + "type": "string" + }, + "is_channel": { + "description": "Whether this is a channel thread", + "type": "boolean" + }, + "is_default": { + "description": "Whether this is the default thread", + "type": "boolean" + }, + "is_unlisted": { + "description": "Whether thread is hidden from listings", + "type": "boolean" + }, + "key": { + "description": "Unique key within owner scope", + "type": "string" + }, + "members": { + "description": "Thread member list", + "items": { + "type": "object" + }, + "type": "array" + }, + "metadata": { + "description": "Thread metadata", + "type": "object" + }, + "org": { + "description": "Organization (nullable)", + "type": "string" + }, + "owner": { + "description": "Owner public", + "type": "string" + }, + "owner_name": { + "description": "Owner display name", + "type": "string" + }, + "owner_type": { + "description": "Owner type: team, user, agent, or nil", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier (nullable)", + "type": "string" + }, + "slug": { + "description": "URL-friendly slug", + "type": "string" + }, + "title": { + "description": "Thread title", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "title" + ], + "type": "object" + }, + "ThreadMember": { + "description": "API schema for a thread member.", + "properties": { + "membership_type": { + "description": "Membership type (owner or member)", + "type": "string" + }, + "thread": { + "description": "Thread", + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User", + "description": "User details (when loaded)" + } + }, + "type": "object" + }, + "ThreadMessage": { + "description": "Schema for a thread message in the developer portal.\n\nMaps to serialized message output from developer portal API.\n", + "properties": { + "admin": { + "description": "Admin-only metadata and trajectory", + "type": "object" + }, + "agent": { + "description": "Agent identifier (nullable)", + "type": "string" + }, + "app": { + "description": "App identifier", + "type": "string" + }, + "attachments": { + "description": "Message attachments", + "items": { + "$ref": "#/components/schemas/Attachment" + }, + "type": "array" + }, + "content": { + "description": "Message content", + "type": "string" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Public ID (msg_...)", + "type": "string" + }, + "org": { + "description": "Organization (nullable)", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier (nullable)", + "type": "string" + }, + "sender": { + "description": "Sender public", + "type": "string" + }, + "sender_name": { + "description": "Display name of sender", + "type": "string" + }, + "sender_type": { + "description": "Type: user, agent, or system", + "type": "string" + }, + "team": { + "description": "Team identifier (nullable)", + "type": "string" + }, + "user": { + "description": "User identifier (nullable)", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "ThreadMessageTrajectory": { + "description": "API schema for a thread message trajectory.", + "properties": { + "agent_message": { + "description": "Agent message", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Thread message trajectory ID (tmt_...)", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "thread": { + "description": "Thread", + "type": "string" + }, + "trajectory": { + "description": "Trajectory", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user_message": { + "description": "User message", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "ThreadReadStatus": { + "description": "Schema for thread read status response.\n", + "properties": { + "last_read_message": { + "description": "Last read message", + "type": "string" + }, + "thread": { + "description": "Thread", + "type": "string" + }, + "unread_count": { + "description": "Number of unread messages", + "type": "integer" + } + }, + "required": [ + "thread", + "unread_count" + ], + "type": "object" + }, + "ThreadSettings": { + "description": "Schema for thread settings response.\n\nUsed by thread settings show/update endpoints.\n", + "properties": { + "agent_enabled": { + "description": "Whether the agent is enabled for this thread", + "type": "boolean" + } + }, + "type": "object" + }, + "ThreadUpdateParams": { + "description": "Schema for thread update parameters.\n\nUsed by both Users.Threads.Update and Teams.Threads.Update actions.\n", + "properties": { + "description": { + "description": "Thread description", + "type": "string" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "profile_picture": { + "$ref": "#/components/schemas/ThreadUpdateParamsProfilePicture", + "description": "Base64 encoded profile picture" + }, + "title": { + "description": "Thread title", + "type": "string" + } + }, + "type": "object" + }, + "ThreadUpdateParamsProfilePicture": { + "description": "Base64 encoded profile picture", + "properties": { + "data": { + "description": "Base64 encoded image data", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "mime_type": { + "description": "MIME type of the image", + "type": "string" + } + }, + "type": "object" + }, + "Trajectory": { + "description": "API schema for an AI trajectory.", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "file": { + "description": "Storage file", + "type": "string" + }, + "id": { + "description": "Trajectory ID (trj_...)", + "type": "string" + }, + "messages": { + "description": "Trajectory messages", + "type": "object" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "User": { + "description": "API schema for a user.", + "properties": { + "alias": { + "description": "User alias/handle", + "type": "string" + }, + "email": { + "description": "User email address", + "type": "string" + }, + "id": { + "description": "User ID", + "type": "string" + }, + "metadata": { + "description": "User metadata", + "type": "object" + }, + "name": { + "description": "User display name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "UserFeedback": { + "description": "API schema for user feedback on messages.", + "properties": { + "comment": { + "description": "Optional comment", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Feedback ID", + "type": "string" + }, + "message": { + "description": "Message", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "rating": { + "description": "Rating (positive/negative)", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "source": { + "description": "Feedback source", + "type": "string" + }, + "thread": { + "description": "Thread", + "type": "string" + }, + "updated_at": { + "description": "Update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "message", + "thread", + "rating", + "source" + ], + "type": "object" + }, + "UserInvite": { + "description": "API schema for a user invite.", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Invite ID (uin_...)", + "type": "string" + }, + "key": { + "description": "Invite key", + "type": "string" + }, + "metadata": { + "description": "Invite metadata", + "type": "object" + }, + "thread": { + "description": "Thread", + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User", + "description": "Invite creator" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "ValidationResult": { + "description": "Schema for config validation result.\n", + "properties": { + "errors": { + "description": "List of validation errors", + "items": { + "type": "string" + }, + "type": "array" + }, + "valid": { + "description": "Whether the config is valid", + "type": "boolean" + }, + "warnings": { + "description": "Optional warnings emitted during validation", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "valid" + ], + "type": "object" + }, + "Webhook": { + "description": "Schema for a developer webhook.\n\nMaps to serialized webhook output from developer portal API.\n", + "properties": { + "context_installation": { + "description": "Bound context installation", + "type": "string" + }, + "context_sources": { + "description": "Bound context sources", + "items": { + "type": "string" + }, + "type": "array" + }, + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "enabled": { + "description": "Whether the webhook is enabled", + "type": "boolean" + }, + "id": { + "description": "Public ID (whk_...)", + "type": "string" + }, + "lookup_key": { + "description": "Lookup key for generic webhooks", + "type": "string" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "provider": { + "description": "Provider type for known providers (github, slack), nil for generic webhooks", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "webhook_url": { + "description": "URL to send webhooks to", + "type": "string" + } + }, + "required": [ + "id", + "enabled", + "webhook_url" + ], + "type": "object" + }, + "WebhookEvent": { + "description": "Schema for a webhook event.\n\nMaps to serialized webhook event output from developer portal API.\n", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "error": { + "description": "Error message if failed", + "type": "string" + }, + "event_type": { + "description": "Event type from the provider", + "type": "string" + }, + "headers": { + "description": "Request headers", + "type": "object" + }, + "id": { + "description": "Public ID (whe_...)", + "type": "string" + }, + "payload": { + "description": "Event payload", + "type": "object" + }, + "processed_at": { + "description": "When the event was processed", + "format": "date-time", + "type": "string" + }, + "status": { + "description": "Processing status (pending, processed, failed)", + "type": "string" + } + }, + "required": [ + "id", + "status" + ], + "type": "object" + }, + "WorkerStatus": { + "description": "API schema for background worker status on a routine run.", + "properties": { + "attempt": { + "description": "Current attempt number (0 = not yet attempted)", + "type": "integer" + }, + "max_attempts": { + "description": "Maximum allowed attempts", + "type": "integer" + }, + "status": { + "description": "Worker state: queued, executing, retrying, completed, discarded, or cancelled", + "type": "string" + } + }, + "required": [ + "status", + "attempt", + "max_attempts" + ], + "type": "object" + }, + "WorkingMemoryEntry": { + "description": "API schema for a working memory entry.", + "properties": { + "agent": { + "description": "Owning agent", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "expires_at": { + "description": "Expiration timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Memory entry ID (amm_...)", + "type": "string" + }, + "key": { + "description": "Memory key", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "value": { + "description": "Memory value", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "WorkingMemoryEntryListResponse": { + "description": "Paginated list response for working memory entries.", + "properties": { + "data": { + "description": "List of working memory entries", + "items": { + "$ref": "#/components/schemas/WorkingMemoryEntry" + }, + "type": "array" + }, + "has_next": { + "description": "Whether a next page exists", + "type": "boolean" + }, + "has_prev": { + "description": "Whether a previous page exists", + "type": "boolean" + }, + "page": { + "description": "Current page number", + "type": "integer" + }, + "page_size": { + "description": "Results per page", + "type": "integer" + }, + "total_entries": { + "description": "Total number of entries", + "type": "integer" + }, + "total_pages": { + "description": "Total number of pages", + "type": "integer" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "info": { + "description": "Agent-first API for runtime + developer control-plane operations (users, teams, agents, routines, context, workflows, integrations, and webhooks).", + "title": "ArchAstro Platform API", + "version": "v1" + }, + "openapi": "3.0.0", + "paths": { + "/api/v1/agent_computers/{computer}": { + "delete": { + "operationId": "delete_api_v1_agent_computers__computer", + "parameters": [ + { + "description": "Computer ID", + "in": "path", + "name": "computer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Computer not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_agent_computers__computer", + "parameters": [ + { + "description": "Computer ID", + "in": "path", + "name": "computer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentComputer" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Computer not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_computers/{computer}/exec": { + "post": { + "operationId": "post_api_v1_agent_computers__computer_exec", + "parameters": [ + { + "description": "Computer ID", + "in": "path", + "name": "computer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "app": { + "description": "App ID", + "type": "string" + }, + "command": { + "description": "Shell command to execute", + "type": "string" + }, + "computer": { + "description": "Computer ID", + "type": "string" + }, + "dir": { + "description": "Working directory for the command", + "type": "string" + } + }, + "required": [ + "computer", + "command" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComputerExecResult" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Computer not found" + }, + "422": { + "description": "Execution failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_computers/{computer}/refresh": { + "post": { + "operationId": "post_api_v1_agent_computers__computer_refresh", + "parameters": [ + { + "description": "Computer ID", + "in": "path", + "name": "computer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "app": { + "description": "App ID", + "type": "string" + }, + "computer": { + "description": "Computer ID", + "type": "string" + } + }, + "required": [ + "computer" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentComputer" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Computer not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_installations": { + "get": { + "operationId": "get_api_v1_agent_installations", + "parameters": [ + { + "description": "Filter by agent ID", + "in": "query", + "name": "agent", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstallationListResponse" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_installations/{installation}": { + "delete": { + "operationId": "delete_api_v1_agent_installations__installation", + "parameters": [ + { + "description": "Installation ID", + "in": "path", + "name": "installation", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Installation not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_agent_installations__installation", + "parameters": [ + { + "description": "Installation ID", + "in": "path", + "name": "installation", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Installation" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Installation not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_installations/{installation}/activate": { + "post": { + "operationId": "post_api_v1_agent_installations__installation_activate", + "parameters": [ + { + "description": "Installation ID", + "in": "path", + "name": "installation", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "app": { + "description": "App ID", + "type": "string" + }, + "installation": { + "description": "Installation ID", + "type": "string" + } + }, + "required": [ + "installation" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Installation" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Installation not found" + }, + "422": { + "description": "Cannot activate - requires integration or invalid state" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_installations/{installation}/installation_sources": { + "get": { + "operationId": "get_api_v1_agent_installations__installation_installation_sources", + "parameters": [ + { + "description": "Installation ID", + "in": "path", + "name": "installation", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstallationSourceListResponse" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Installation not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_agent_installations__installation_installation_sources", + "parameters": [ + { + "description": "Installation ID", + "in": "path", + "name": "installation", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "app": { + "description": "App ID", + "type": "string" + }, + "installation": { + "description": "Installation ID", + "type": "string" + }, + "payload": { + "description": "Source payload", + "type": "object" + }, + "type": { + "description": "Source type (e.g. file/document, web/link)", + "type": "string" + } + }, + "required": [ + "installation", + "type", + "payload" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstallationSource" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Installation not found" + }, + "422": { + "description": "Invalid source type, state, or payload" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_installations/{installation}/pause": { + "post": { + "operationId": "post_api_v1_agent_installations__installation_pause", + "parameters": [ + { + "description": "Installation ID", + "in": "path", + "name": "installation", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "app": { + "description": "App ID", + "type": "string" + }, + "installation": { + "description": "Installation ID", + "type": "string" + } + }, + "required": [ + "installation" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Installation" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Installation not found" + }, + "422": { + "description": "Cannot pause - invalid state" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_installations/{installation}/suspend": { + "post": { + "operationId": "post_api_v1_agent_installations__installation_suspend", + "parameters": [ + { + "description": "Installation ID", + "in": "path", + "name": "installation", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "app": { + "description": "App ID", + "type": "string" + }, + "installation": { + "description": "Installation ID", + "type": "string" + }, + "reason": { + "description": "Optional suspension reason", + "type": "string" + } + }, + "required": [ + "installation" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Installation" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Installation not found" + }, + "422": { + "description": "Cannot suspend - already suspended or invalid state" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_routines": { + "get": { + "operationId": "get_api_v1_agent_routines", + "parameters": [ + { + "description": "Filter by agent ID", + "in": "query", + "name": "agent", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Filter by event type", + "in": "query", + "name": "event_type", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentRoutineListResponse" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_routines/presets": { + "get": { + "operationId": "get_api_v1_agent_routines_presets", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/RoutinePreset" + }, + "type": "array" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_routines/runs/{run}": { + "get": { + "operationId": "get_api_v1_agent_routines_runs__run", + "parameters": [ + { + "description": "Routine run ID", + "in": "path", + "name": "run", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentRoutineRun" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Routine run not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_routines/{routine}": { + "delete": { + "operationId": "delete_api_v1_agent_routines__routine", + "parameters": [ + { + "description": "Routine ID", + "in": "path", + "name": "routine", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Routine not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_agent_routines__routine", + "parameters": [ + { + "description": "Routine ID", + "in": "path", + "name": "routine", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentRoutine" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Routine not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "patch": { + "operationId": "patch_api_v1_agent_routines__routine", + "parameters": [ + { + "description": "Routine ID", + "in": "path", + "name": "routine", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "acl": { + "description": "Access control list", + "properties": { + "add": { + "description": "Patch mode: grants to add or merge into existing", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "grants": { + "description": "Replace mode: full list of grants (replaces all existing). Use [] to clear.", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "remove": { + "description": "Patch mode: principals to remove from existing", + "items": { + "description": "API schema for identifying a principal to remove from an ACL.", + "properties": { + "principal": { + "description": "Principal identifier to remove (omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type to remove", + "type": "string" + } + }, + "required": [ + "principal_type" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "config": { + "description": "Config ID", + "type": "string" + }, + "description": { + "description": "Description", + "type": "string" + }, + "event_config": { + "description": "Event config map. Keys are event types, values are entry objects with \"filters\" (filter map) and optional \"dedupe_key_path\" (JSON path for deduplication, e.g. \"$.thread.id\")", + "type": "object" + }, + "event_type": { + "description": "Event type (deprecated, use event_config)", + "type": "string" + }, + "handler_type": { + "description": "Handler type", + "type": "string" + }, + "lookup_key": { + "description": "Lookup key", + "type": "string" + }, + "metadata": { + "description": "Metadata", + "type": "object" + }, + "name": { + "description": "Routine name", + "type": "string" + }, + "preset_config": { + "description": "Preset config", + "type": "object" + }, + "preset_name": { + "description": "Preset name", + "type": "string" + }, + "routine": { + "description": "Routine ID", + "type": "string" + }, + "schedule": { + "description": "Cron expression for scheduled routines", + "type": "string" + }, + "script": { + "description": "Script content", + "type": "string" + }, + "trigger_context": { + "description": "Trigger context: chat_session or event", + "type": "string" + } + }, + "required": [ + "routine" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentRoutine" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Routine not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_routines/{routine}/activate": { + "post": { + "operationId": "post_api_v1_agent_routines__routine_activate", + "parameters": [ + { + "description": "Routine ID", + "in": "path", + "name": "routine", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "app": { + "description": "App ID", + "type": "string" + }, + "routine": { + "description": "Routine ID", + "type": "string" + } + }, + "required": [ + "routine" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentRoutine" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Routine not found" + }, + "422": { + "description": "Unprocessable entity - no workflow config attached" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_routines/{routine}/invoke": { + "post": { + "operationId": "post_api_v1_agent_routines__routine_invoke", + "parameters": [ + { + "description": "Routine ID or lookup_key", + "in": "path", + "name": "routine", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "app": { + "description": "App ID", + "type": "string" + }, + "idempotency_key": { + "description": "Idempotency key to deduplicate invocations", + "type": "string" + }, + "message": { + "description": "The message to send", + "type": "string" + }, + "metadata": { + "description": "Optional per-call metadata", + "type": "object" + }, + "routine": { + "description": "Routine ID or lookup_key", + "type": "string" + }, + "session_key": { + "description": "Session key (required when session_scope is per_key)", + "type": "string" + }, + "user": { + "description": "User ID (S2S/developer only; client uses viewer)", + "type": "string" + } + }, + "required": [ + "routine", + "message" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentRoutineRun" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Routine not found" + }, + "422": { + "description": "Unprocessable entity" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_routines/{routine}/pause": { + "post": { + "operationId": "post_api_v1_agent_routines__routine_pause", + "parameters": [ + { + "description": "Routine ID", + "in": "path", + "name": "routine", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "app": { + "description": "App ID", + "type": "string" + }, + "routine": { + "description": "Routine ID", + "type": "string" + } + }, + "required": [ + "routine" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentRoutine" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Routine not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_routines/{routine}/runs": { + "get": { + "operationId": "get_api_v1_agent_routines__routine_runs", + "parameters": [ + { + "description": "Routine ID", + "in": "path", + "name": "routine", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Filter by status", + "in": "query", + "name": "status", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Maximum number of results (default 50, max 100)", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Cursor for fetching items before this point (older)", + "in": "query", + "name": "before_cursor", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Cursor for fetching items after this point (newer)", + "in": "query", + "name": "after_cursor", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentRoutineRunListResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Invalid cursor" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Routine not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_sessions": { + "get": { + "operationId": "get_api_v1_agent_sessions", + "parameters": [ + { + "description": "Filter by agent ID", + "in": "query", + "name": "agent", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Filter by statuses (pending, running, waiting, completed, failed, cancelled)", + "in": "query", + "name": "status", + "required": false, + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Filter by routine run IDs", + "in": "query", + "name": "routine_run", + "required": false, + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Exclude system-created sessions", + "in": "query", + "name": "exclude_system", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "description": "Maximum number of results (default 25, max 100)", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSessionListResponse" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_agent_sessions", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent": { + "description": "Agent ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "instructions": { + "description": "Task description for the session", + "type": "string" + }, + "max_runs_per_turn": { + "description": "Max tool runs per turn (default 25)", + "type": "integer" + }, + "max_tokens": { + "description": "Max tokens (default 20000)", + "type": "integer" + }, + "max_turns": { + "description": "Max turns (default 100)", + "type": "integer" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Optional display name for the session", + "type": "string" + }, + "team": { + "description": "Optional team context", + "type": "string" + }, + "thread": { + "description": "Optional thread context", + "type": "string" + }, + "user": { + "description": "Optional user context", + "type": "string" + } + }, + "required": [ + "agent", + "instructions" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSession" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_sessions/{agent_session}": { + "delete": { + "operationId": "delete_api_v1_agent_sessions__agent_session", + "parameters": [ + { + "description": "Agent session ID", + "in": "path", + "name": "agent_session", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Session not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_agent_sessions__agent_session", + "parameters": [ + { + "description": "Agent session ID", + "in": "path", + "name": "agent_session", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSession" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Session not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "patch": { + "operationId": "patch_api_v1_agent_sessions__agent_session", + "parameters": [ + { + "description": "Agent session ID", + "in": "path", + "name": "agent_session", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent_session": { + "description": "Agent session ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + } + }, + "required": [ + "agent_session" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSession" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Session not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_sessions/{agent_session}/cancel": { + "post": { + "operationId": "post_api_v1_agent_sessions__agent_session_cancel", + "parameters": [ + { + "description": "Agent session ID", + "in": "path", + "name": "agent_session", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent_session": { + "description": "Agent session ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + } + }, + "required": [ + "agent_session" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSession" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Session not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_sessions/{agent_session}/message": { + "post": { + "operationId": "post_api_v1_agent_sessions__agent_session_message", + "parameters": [ + { + "description": "Agent session ID", + "in": "path", + "name": "agent_session", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent_session": { + "description": "Agent session ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "content": { + "description": "Message content", + "type": "string" + }, + "metadata": { + "description": "Message metadata", + "type": "object" + }, + "role": { + "description": "Message role (default: user)", + "type": "string" + } + }, + "required": [ + "agent_session", + "content" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSession" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Session not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_skills": { + "get": { + "operationId": "get_api_v1_agent_skills", + "parameters": [ + { + "description": "Filter by agent ID(s)", + "in": "query", + "name": "agent", + "required": false, + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSkillList" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_agent_skills", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent": { + "description": "Agent ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "config": { + "description": "Skill config ID", + "type": "string" + }, + "instruction": { + "description": "Optional instruction override", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + } + }, + "required": [ + "agent", + "config" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSkill" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Agent not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_skills/{agent_skill}": { + "delete": { + "operationId": "delete_api_v1_agent_skills__agent_skill", + "parameters": [ + { + "description": "Agent skill ID", + "in": "path", + "name": "agent_skill", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_agent_skills__agent_skill", + "parameters": [ + { + "description": "Agent skill ID", + "in": "path", + "name": "agent_skill", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSkill" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "patch": { + "operationId": "patch_api_v1_agent_skills__agent_skill", + "parameters": [ + { + "description": "Agent skill ID", + "in": "path", + "name": "agent_skill", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent_skill": { + "description": "Agent skill ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "instruction": { + "description": "Instruction override", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + } + }, + "required": [ + "agent_skill" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSkill" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_skills/{agent_skill}/activate": { + "post": { + "operationId": "post_api_v1_agent_skills__agent_skill_activate", + "parameters": [ + { + "description": "Agent skill ID", + "in": "path", + "name": "agent_skill", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent_skill": { + "description": "Agent skill ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + } + }, + "required": [ + "agent_skill" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSkill" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_skills/{agent_skill}/deactivate": { + "post": { + "operationId": "post_api_v1_agent_skills__agent_skill_deactivate", + "parameters": [ + { + "description": "Agent skill ID", + "in": "path", + "name": "agent_skill", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent_skill": { + "description": "Agent skill ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + } + }, + "required": [ + "agent_skill" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSkill" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_tools": { + "get": { + "operationId": "get_api_v1_agent_tools", + "parameters": [ + { + "description": "Filter by agent ID", + "in": "query", + "name": "agent", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Filter by kind (builtin or custom)", + "in": "query", + "name": "kind", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentToolListResponse" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_tools/catalog": { + "get": { + "operationId": "get_api_v1_agent_tools_catalog", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/BuiltinToolCatalogEntry" + }, + "type": "array" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_tools/{tool}": { + "delete": { + "operationId": "delete_api_v1_agent_tools__tool", + "parameters": [ + { + "description": "Tool ID", + "in": "path", + "name": "tool", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Tool not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_agent_tools__tool", + "parameters": [ + { + "description": "Tool ID", + "in": "path", + "name": "tool", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentTool" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Tool not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "patch": { + "operationId": "patch_api_v1_agent_tools__tool", + "parameters": [ + { + "description": "Tool ID", + "in": "path", + "name": "tool", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "app": { + "description": "App ID", + "type": "string" + }, + "builtin_tool_config": { + "description": "Built-in tool config", + "type": "object" + }, + "config": { + "description": "Config ID", + "type": "string" + }, + "description": { + "description": "Description", + "type": "string" + }, + "handler_type": { + "description": "Handler type: script or workflow_graph", + "type": "string" + }, + "instruction": { + "description": "LLM usage instruction", + "type": "string" + }, + "lookup_key": { + "description": "Lookup key", + "type": "string" + }, + "metadata": { + "description": "Metadata", + "type": "object" + }, + "name": { + "description": "Tool name (custom only)", + "type": "string" + }, + "parameters": { + "description": "JSON schema for tool input parameters", + "type": "object" + }, + "parameters_config": { + "description": "Config ID for a reusable JsonSchema", + "type": "string" + }, + "tool": { + "description": "Tool ID", + "type": "string" + } + }, + "required": [ + "tool" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentTool" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Tool not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_tools/{tool}/activate": { + "post": { + "operationId": "post_api_v1_agent_tools__tool_activate", + "parameters": [ + { + "description": "Tool ID", + "in": "path", + "name": "tool", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "app": { + "description": "App ID", + "type": "string" + }, + "tool": { + "description": "Tool ID", + "type": "string" + } + }, + "required": [ + "tool" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentTool" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Tool not found" + }, + "422": { + "description": "Cannot activate tool" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agent_tools/{tool}/deactivate": { + "post": { + "operationId": "post_api_v1_agent_tools__tool_deactivate", + "parameters": [ + { + "description": "Tool ID", + "in": "path", + "name": "tool", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "app": { + "description": "App ID", + "type": "string" + }, + "tool": { + "description": "Tool ID", + "type": "string" + } + }, + "required": [ + "tool" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentTool" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Tool not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agents": { + "get": { + "operationId": "get_api_v1_agents", + "parameters": [ + { + "description": "Page number (default 1)", + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Results per page (default 25)", + "in": "query", + "name": "page_size", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search by agent, org, team, or owner fields", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Filter to agents owned by this user", + "in": "query", + "name": "user", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentListResponse" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_agents", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "acl": { + "description": "Access control list", + "properties": { + "add": { + "description": "Patch mode: grants to add or merge into existing", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "grants": { + "description": "Replace mode: full list of grants (replaces all existing). Use [] to clear.", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "remove": { + "description": "Patch mode: principals to remove from existing", + "items": { + "description": "API schema for identifying a principal to remove from an ACL.", + "properties": { + "principal": { + "description": "Principal identifier to remove (omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type to remove", + "type": "string" + } + }, + "required": [ + "principal_type" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "email": { + "description": "Agent email", + "type": "string" + }, + "identity": { + "description": "Identity prompt describing who the agent is", + "type": "string" + }, + "lookup_key": { + "description": "Unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "model": { + "description": "Default AI model for this agent", + "type": "string" + }, + "name": { + "description": "Agent name", + "type": "string" + }, + "org": { + "description": "Organization ID", + "type": "string" + }, + "phone_number": { + "description": "Agent phone number", + "type": "string" + }, + "profile_picture": { + "description": "Base64-encoded profile picture", + "properties": { + "data": { + "description": "Base64-encoded image data", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "mime_type": { + "description": "MIME type of the image", + "type": "string" + } + }, + "required": [ + "data", + "mime_type", + "filename" + ], + "type": "object" + }, + "team": { + "description": "Team ID", + "type": "string" + }, + "template": { + "description": "Template ID to provision agent from", + "type": "string" + }, + "user": { + "description": "User ID", + "type": "string" + } + }, + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Agent" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "name or template_id is required" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Template not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agents/{agent}": { + "delete": { + "operationId": "delete_api_v1_agents__agent", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Agent not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_agents__agent", + "parameters": [ + { + "description": "Agent ID or lookup_key (handle)", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Agent" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Agent not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "patch": { + "operationId": "patch_api_v1_agents__agent", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "acl": { + "description": "Access control list", + "properties": { + "add": { + "description": "Patch mode: grants to add or merge into existing", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "grants": { + "description": "Replace mode: full list of grants (replaces all existing). Use [] to clear.", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "remove": { + "description": "Patch mode: principals to remove from existing", + "items": { + "description": "API schema for identifying a principal to remove from an ACL.", + "properties": { + "principal": { + "description": "Principal identifier to remove (omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type to remove", + "type": "string" + } + }, + "required": [ + "principal_type" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "agent": { + "description": "Agent ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "email": { + "description": "Agent email", + "type": "string" + }, + "identity": { + "description": "Identity prompt describing who the agent is", + "type": "string" + }, + "lookup_key": { + "description": "Unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "model": { + "description": "Default AI model for this agent", + "type": "string" + }, + "name": { + "description": "Agent name", + "type": "string" + }, + "org": { + "description": "Organization ID", + "type": "string" + }, + "phone_number": { + "description": "Agent phone number", + "type": "string" + }, + "profile_picture": { + "description": "Base64-encoded profile picture", + "properties": { + "data": { + "description": "Base64-encoded image data", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "mime_type": { + "description": "MIME type of the image", + "type": "string" + } + }, + "required": [ + "data", + "mime_type", + "filename" + ], + "type": "object" + }, + "team": { + "description": "Team ID", + "type": "string" + }, + "user": { + "description": "User ID", + "type": "string" + } + }, + "required": [ + "agent" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Agent" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Agent not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agents/{agent}/agent_computers": { + "get": { + "operationId": "get_api_v1_agents__agent_agent_computers", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentComputerListResponse" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Agent not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_agents__agent_agent_computers", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent": { + "description": "Agent ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "config": { + "description": "Computer configuration", + "type": "object" + }, + "lookup_key": { + "description": "Unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Computer name", + "type": "string" + }, + "region": { + "description": "Region to provision in (default: iad)", + "type": "string" + } + }, + "required": [ + "agent", + "name" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentComputer" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Agent not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agents/{agent}/agent_installations": { + "get": { + "operationId": "get_api_v1_agents__agent_agent_installations", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstallationListResponse" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Agent not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_agents__agent_agent_installations", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent": { + "description": "Agent ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "config": { + "description": "Optional configuration", + "type": "object" + }, + "integration": { + "description": "Integration fields to auto-create for integration/* kinds. When provided, creates the underlying Integration record and activates the installation.", + "properties": { + "access_token": { + "description": "OAuth access token or API key", + "type": "string" + }, + "installation_id": { + "description": "External installation ID (e.g. GitHub App installation ID, Slack team_id)", + "type": "string" + }, + "metadata": { + "description": "Provider-specific metadata (e.g. bot_user_id)", + "type": "object" + }, + "refresh_token": { + "description": "OAuth refresh token", + "type": "string" + }, + "workspace_key": { + "description": "Workspace name or identifier", + "type": "string" + } + }, + "type": "object" + }, + "kind": { + "description": "Installation kind (gmail, outlook, github, scrape/site)", + "type": "string" + }, + "shared_integration": { + "description": "Shared org/app integration ID to bind to this installation", + "type": "string" + } + }, + "required": [ + "agent", + "kind" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Installation" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Agent not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agents/{agent}/agent_installations/kinds": { + "get": { + "operationId": "get_api_v1_agents__agent_agent_installations_kinds", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstallationKindListResponse" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agents/{agent}/agent_routines": { + "post": { + "operationId": "post_api_v1_agents__agent_agent_routines", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "acl": { + "description": "Access control list", + "properties": { + "add": { + "description": "Patch mode: grants to add or merge into existing", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "grants": { + "description": "Replace mode: full list of grants (replaces all existing). Use [] to clear.", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "remove": { + "description": "Patch mode: principals to remove from existing", + "items": { + "description": "API schema for identifying a principal to remove from an ACL.", + "properties": { + "principal": { + "description": "Principal identifier to remove (omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type to remove", + "type": "string" + } + }, + "required": [ + "principal_type" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "agent": { + "description": "Agent ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "config": { + "description": "Config ID (for workflow_graph handler)", + "type": "string" + }, + "description": { + "description": "Description", + "type": "string" + }, + "event_config": { + "description": "Event config map. Keys are event types, values are entry objects with \"filters\" (filter map) and optional \"dedupe_key_path\" (JSON path for deduplication, e.g. \"$.thread.id\")", + "type": "object" + }, + "event_type": { + "description": "Event type (deprecated, use event_config)", + "type": "string" + }, + "handler_type": { + "description": "Handler type: workflow_graph, script, or preset", + "type": "string" + }, + "lookup_key": { + "description": "Unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Routine name", + "type": "string" + }, + "preset_config": { + "description": "Preset config", + "type": "object" + }, + "preset_name": { + "description": "Preset name (for preset handler)", + "type": "string" + }, + "schedule": { + "description": "Cron expression for scheduled routines", + "type": "string" + }, + "script": { + "description": "Script content (for script handler)", + "type": "string" + }, + "status": { + "description": "Initial status: draft or active (default: draft)", + "type": "string" + }, + "trigger_context": { + "description": "Trigger context: chat_session or event (default: event)", + "type": "string" + } + }, + "required": [ + "agent", + "name", + "handler_type" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentRoutine" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Agent not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agents/{agent}/agent_tools": { + "get": { + "operationId": "get_api_v1_agents__agent_agent_tools", + "parameters": [ + { + "description": "Filter by agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Filter by kind (builtin or custom)", + "in": "query", + "name": "kind", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentToolListResponse" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_agents__agent_agent_tools", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent": { + "description": "Agent ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "builtin_tool_config": { + "description": "Built-in tool config", + "type": "object" + }, + "builtin_tool_key": { + "description": "Built-in tool key (for builtin kind)", + "type": "string" + }, + "config": { + "description": "Config ID (for custom kind)", + "type": "string" + }, + "description": { + "description": "Tool description (for custom kind)", + "type": "string" + }, + "handler_type": { + "description": "Handler type: script or workflow_graph (for custom kind)", + "type": "string" + }, + "kind": { + "description": "Tool kind: builtin or custom", + "type": "string" + }, + "lookup_key": { + "description": "Unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Tool name (for custom kind)", + "type": "string" + }, + "parameters": { + "description": "JSON schema for parameters (for custom kind)", + "type": "object" + }, + "status": { + "description": "Tool status: draft or active (default: draft)", + "type": "string" + } + }, + "required": [ + "agent", + "kind" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentTool" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Agent not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agents/{agent}/agent_working_memory": { + "get": { + "operationId": "get_api_v1_agents__agent_agent_working_memory", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Page number (default 1)", + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Results per page (default 25)", + "in": "query", + "name": "page_size", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Filter by key (ilike match)", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingMemoryEntryListResponse" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Agent not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agents/{agent}/export": { + "get": { + "description": "Reconstructs an AgentTemplate config from a deployed agent and its sub-resources\n(tools, routines, skills, installations). Returns the template plus all dependent\nconfig files (scripts, workflows, skills, schemas) with their raw content for a\nfully self-contained export.\n", + "operationId": "get_api_v1_agents__agent_export", + "parameters": [ + { + "description": "Agent ID or lookup_key", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentExport" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Agent not found" + } + }, + "summary": "Export agent as AgentTemplate", + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agents/{agent}/schedules": { + "get": { + "operationId": "get_api_v1_agents__agent_schedules", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Filter by status (default: active and paused)", + "in": "query", + "name": "status", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "List of schedules", + "properties": { + "data": { + "description": "Schedule entries", + "items": { + "description": "API schema for an agent schedule.", + "properties": { + "agent": { + "description": "Owning agent ID", + "type": "string" + }, + "app": { + "description": "Application ID", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "cron_expression": { + "description": "Cron expression (recurring only)", + "type": "string" + }, + "id": { + "description": "Schedule ID (asc_...)", + "type": "string" + }, + "instructions": { + "description": "Task instructions", + "type": "string" + }, + "last_run_at": { + "description": "Last execution time", + "format": "date-time", + "type": "string" + }, + "max_runs": { + "description": "Maximum runs (recurring only)", + "type": "integer" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "next_run_at": { + "description": "Next scheduled execution", + "format": "date-time", + "type": "string" + }, + "run_count": { + "description": "Number of times executed", + "type": "integer" + }, + "schedule_type": { + "description": "Schedule type (once or recurring)", + "type": "string" + }, + "scheduled_at": { + "description": "One-time execution time", + "format": "date-time", + "type": "string" + }, + "status": { + "description": "Schedule status", + "type": "string" + }, + "thread": { + "description": "Thread ID (if thread-bound)", + "type": "string" + }, + "timezone": { + "description": "Schedule timezone", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Invalid status value" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agents/{agent}/schedules/{schedule}": { + "get": { + "operationId": "get_api_v1_agents__agent_schedules__schedule", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Schedule ID", + "in": "path", + "name": "schedule", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSchedule" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Schedule not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agents/{agent}/search": { + "post": { + "operationId": "post_api_v1_agents__agent_search", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent": { + "description": "Agent ID", + "type": "string" + }, + "max_results": { + "description": "Max results to return", + "type": "integer" + }, + "mode": { + "description": "Search mode: hybrid, vector, or fulltext", + "type": "string" + }, + "query": { + "description": "Search query", + "type": "string" + }, + "recency_days": { + "description": "Limit results to last N days", + "type": "integer" + }, + "source_types": { + "description": "Filter by source types", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "agent", + "query" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Search results", + "properties": { + "data": { + "description": "Matching knowledge items", + "items": { + "description": "API schema for a knowledge search result item.", + "properties": { + "content": { + "description": "Normalized content text", + "type": "string" + }, + "content_type": { + "description": "Content MIME type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Item ID (cim_...)", + "type": "string" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "raw_content": { + "description": "Raw content data", + "type": "object" + }, + "type": { + "description": "Source type (requires preloaded :source association)", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Agent not found" + }, + "422": { + "description": "Invalid parameters" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/agents/{agent}/threads": { + "post": { + "operationId": "post_api_v1_agents__agent_threads", + "parameters": [ + { + "description": "Agent ID", + "in": "path", + "name": "agent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent": { + "description": "Agent ID", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "skip_welcome_message": { + "description": "Skip automatic welcome message", + "type": "boolean" + }, + "thread": { + "description": "Thread attributes", + "properties": { + "description": { + "description": "Thread description", + "type": "string" + }, + "is_unlisted": { + "description": "Whether thread is unlisted", + "type": "boolean" + }, + "key": { + "description": "Unique thread key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "org": { + "description": "Organization ID", + "type": "string" + }, + "settings": { + "description": "Thread settings", + "type": "object" + }, + "title": { + "description": "Thread title", + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "agent", + "thread" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Thread" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Agent not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/ai/chat/completions": { + "post": { + "operationId": "post_api_v1_ai_chat_completions", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "context": { + "description": "Template context map", + "type": "object" + }, + "messages": { + "description": "Chat completion messages", + "items": { + "description": "AI chat message (OpenAI-compatible format).", + "properties": { + "content": { + "description": "Message text content", + "type": "string" + }, + "content_parts": { + "description": "Multimodal content parts", + "items": { + "type": "object" + }, + "type": "array" + }, + "resume_token": { + "description": "Resume token for continuing conversations", + "type": "string" + }, + "role": { + "description": "Message role (system, user, assistant, tool)", + "type": "string" + }, + "structured_output": { + "description": "Structured output data" + }, + "tool_calls": { + "description": "Tool calls from assistant", + "items": { + "description": "Tool call from assistant message.", + "properties": { + "arguments": { + "description": "Tool arguments", + "type": "object" + }, + "id": { + "description": "Tool call ID", + "type": "string" + }, + "name": { + "description": "Tool/function name", + "type": "string" + }, + "thought_signature": { + "description": "Optional thought signature", + "type": "string" + } + }, + "required": [ + "id", + "name", + "arguments" + ], + "type": "object" + }, + "type": "array" + }, + "tool_results": { + "description": "Tool results from tool execution", + "items": { + "description": "Tool result from tool execution.", + "properties": { + "content": { + "description": "Tool result content", + "type": "string" + }, + "id": { + "description": "Tool call ID this result responds to", + "type": "string" + }, + "name": { + "description": "Tool/function name", + "type": "string" + }, + "resolution": { + "description": "Structured tool resolution" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "role" + ], + "type": "object" + }, + "type": "array" + }, + "opts": { + "description": "Completion options", + "properties": { + "max_tokens": { + "description": "Maximum tokens for the response", + "type": "integer" + }, + "model": { + "description": "Model identifier", + "type": "string" + }, + "server_tools": { + "description": "Server-side tool declarations (e.g. [{type: \"search\"}])", + "items": { + "type": "object" + }, + "type": "array" + }, + "temperature": { + "description": "Sampling temperature", + "type": "number" + }, + "tools": { + "description": "OpenAI tool definitions", + "items": { + "description": "OpenAI-style tool definition.", + "properties": { + "function": { + "description": "Function tool definition", + "properties": { + "description": { + "description": "Function description", + "type": "string" + }, + "name": { + "description": "Function name", + "type": "string" + }, + "parameters": { + "description": "JSON Schema for function parameters", + "type": "object" + } + }, + "required": [ + "name", + "parameters" + ], + "type": "object" + }, + "type": { + "description": "Tool type (function)", + "type": "string" + } + }, + "required": [ + "type", + "function" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "messages", + "opts" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AICompletionResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/ai/chat/models": { + "get": { + "operationId": "get_api_v1_ai_chat_models", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Available models", + "properties": { + "data": { + "description": "The models", + "items": { + "description": "Schema for AI model information.", + "properties": { + "id": { + "description": "Model identifier", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/ai/image/edits": { + "post": { + "operationId": "post_api_v1_ai_image_edits", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "aspect_ratio": { + "description": "Aspect ratio (e.g. 1:1, 16:9)", + "type": "string" + }, + "background": { + "description": "Background setting (model-dependent)", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "image_size": { + "description": "Image size tier for Gemini (e.g. 1K, 2K, 4K)", + "type": "string" + }, + "images": { + "description": "Source images to edit", + "items": { + "description": "Schema for an input image (base64-encoded) used in image editing.", + "properties": { + "image_data": { + "description": "Base64-encoded image data", + "type": "string" + }, + "image_type": { + "description": "MIME type (e.g. image/png, image/jpeg)", + "type": "string" + } + }, + "required": [ + "image_data", + "image_type" + ], + "type": "object" + }, + "type": "array" + }, + "model": { + "description": "Model identifier (defaults to the platform default)", + "type": "string" + }, + "output_format": { + "description": "Output format (png, jpeg, webp)", + "type": "string" + }, + "prompt": { + "description": "Text description of the edit to apply", + "type": "string" + }, + "quality": { + "description": "Quality setting (model-dependent)", + "type": "string" + }, + "size": { + "description": "Size string for OpenAI models (e.g. 1024x1024)", + "type": "string" + }, + "style": { + "description": "Style setting (model-dependent)", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "required": [ + "prompt", + "images" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AIImageResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "422": { + "description": "Image editing failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/ai/image/generations": { + "post": { + "operationId": "post_api_v1_ai_image_generations", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "aspect_ratio": { + "description": "Aspect ratio (e.g. 1:1, 16:9)", + "type": "string" + }, + "background": { + "description": "Background setting (model-dependent)", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "image_size": { + "description": "Image size tier for Gemini (e.g. 1K, 2K, 4K)", + "type": "string" + }, + "model": { + "description": "Model identifier (defaults to the platform default)", + "type": "string" + }, + "n": { + "description": "Number of images to generate (default 1)", + "type": "integer" + }, + "output_format": { + "description": "Output format (png, jpeg, webp)", + "type": "string" + }, + "prompt": { + "description": "Text description of the image to generate", + "type": "string" + }, + "quality": { + "description": "Quality setting (model-dependent)", + "type": "string" + }, + "size": { + "description": "Size string for OpenAI models (e.g. 1024x1024)", + "type": "string" + }, + "style": { + "description": "Style setting (model-dependent)", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "required": [ + "prompt" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AIImageResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "422": { + "description": "Image generation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/ai/image/models": { + "get": { + "operationId": "get_api_v1_ai_image_models", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Available models", + "properties": { + "data": { + "description": "The models", + "items": { + "description": "Schema for AI model information.", + "properties": { + "id": { + "description": "Model identifier", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/artifacts/{artifact}": { + "delete": { + "operationId": "delete_api_v1_artifacts__artifact", + "parameters": [ + { + "description": "Artifact ID", + "in": "path", + "name": "artifact", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Artifact not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_artifacts__artifact", + "parameters": [ + { + "description": "Artifact ID", + "in": "path", + "name": "artifact", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Artifact" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Artifact not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "put": { + "operationId": "put_api_v1_artifacts__artifact", + "parameters": [ + { + "description": "Artifact ID", + "in": "path", + "name": "artifact", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "artifact": { + "description": "Artifact ID", + "type": "string" + }, + "description": { + "description": "Artifact description", + "type": "string" + }, + "file_content": { + "description": "Base64 encoded file content", + "type": "string" + }, + "file_content_type": { + "description": "File MIME type", + "type": "string" + }, + "file_name": { + "description": "File name", + "type": "string" + }, + "from_version": { + "description": "Current version for optimistic concurrency control", + "type": "integer" + }, + "name": { + "description": "Artifact name", + "type": "string" + } + }, + "required": [ + "artifact", + "from_version" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Artifact" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Artifact not found" + }, + "409": { + "description": "Version conflict" + }, + "422": { + "description": "Validation error" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/artifacts/{artifact}/archive": { + "post": { + "operationId": "post_api_v1_artifacts__artifact_archive", + "parameters": [ + { + "description": "Artifact ID", + "in": "path", + "name": "artifact", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "artifact": { + "description": "Artifact ID", + "type": "string" + } + }, + "required": [ + "artifact" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Artifact not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/artifacts/{artifact}/content": { + "get": { + "operationId": "get_api_v1_artifacts__artifact_content", + "parameters": [ + { + "description": "Artifact ID", + "in": "path", + "name": "artifact", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional version number", + "in": "query", + "name": "version", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "*/*": { + "schema": { + "format": "binary", + "type": "string" + } + } + }, + "description": "Raw artifact file content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Artifact content not found" + }, + "422": { + "description": "Error retrieving content" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/auth/login": { + "post": { + "operationId": "post_api_v1_auth_login", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "email": { + "description": "User email address", + "type": "string" + }, + "password": { + "description": "User password", + "type": "string" + } + }, + "required": [ + "email", + "password" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthTokens" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Invalid credentials" + }, + "429": { + "description": "Rate limited" + } + }, + "tags": [ + "auth" + ], + "x-auth": [ + "publishable_key" + ] + } + }, + "/api/v1/auth/refresh": { + "post": { + "operationId": "post_api_v1_auth_refresh", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "refresh_token": { + "description": "Refresh token to exchange", + "type": "string" + } + }, + "required": [ + "refresh_token" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthTokens" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Invalid or expired refresh token" + }, + "429": { + "description": "Rate limited" + } + }, + "tags": [ + "auth" + ], + "x-auth": [ + "publishable_key" + ] + } + }, + "/api/v1/auth/register": { + "post": { + "operationId": "post_api_v1_auth_register", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "alias": { + "description": "User alias/handle", + "type": "string" + }, + "email": { + "description": "User email address", + "type": "string" + }, + "full_name": { + "description": "User's full name", + "type": "string" + }, + "invite_code": { + "description": "Invite code for invite-based registration", + "type": "string" + }, + "password": { + "description": "User password (required for standard registration)", + "type": "string" + }, + "team_invite": { + "description": "Team invite ID for team-based registration", + "type": "string" + }, + "timezone": { + "description": "User timezone", + "type": "string" + } + }, + "required": [ + "email" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthTokens" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Missing required parameters" + }, + "404": { + "description": "Team invite not found" + }, + "422": { + "description": "Validation failed" + } + }, + "tags": [ + "auth" + ], + "x-auth": [ + "publishable_key" + ] + } + }, + "/api/v1/auth/token": { + "post": { + "operationId": "post_api_v1_auth_token", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "timezone": { + "description": "User timezone to update if still default", + "type": "string" + }, + "token": { + "description": "One-time login token from email", + "type": "string" + } + }, + "required": [ + "token" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthTokens" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Missing token" + }, + "401": { + "description": "Invalid or expired token" + }, + "429": { + "description": "Rate limited" + }, + "500": { + "description": "Token exchange failed" + } + }, + "tags": [ + "auth" + ], + "x-auth": [ + "publishable_key" + ], + "x-sdk-name": "exchange_login_token" + } + }, + "/api/v1/automation_runs/{automation_run}": { + "get": { + "description": "Fetches one run created by an invoked automation.\n\nThis public lookup route only returns runs whose parent automation has\n`type: :invoked`.\n", + "operationId": "get_api_v1_automation_runs__automation_run", + "parameters": [ + { + "description": "Automation run ID", + "in": "path", + "name": "automation_run", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRun" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Automation run not found" + } + }, + "summary": "Get a single invoked automation run", + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/automations/{automation}/invoke": { + "post": { + "operationId": "post_api_v1_automations__automation_invoke", + "parameters": [ + { + "description": "Automation ID or lookup_key", + "in": "path", + "name": "automation", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "app": { + "description": "App ID (optional, derived from auth key if omitted)", + "type": "string" + }, + "automation": { + "description": "Automation ID or lookup_key", + "type": "string" + }, + "idempotency_key": { + "description": "Idempotency key to deduplicate invocations", + "type": "string" + }, + "payload": { + "description": "Input payload (validated against input_schema if configured)", + "type": "object" + } + }, + "required": [ + "automation" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRun" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Automation not found" + }, + "422": { + "description": "Unprocessable entity" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/config": { + "get": { + "operationId": "get_api_v1_config", + "parameters": [ + { + "description": "Team ID (for team-owned configs)", + "in": "query", + "name": "team", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "User ID (for user-owned configs, defaults to current user)", + "in": "query", + "name": "user", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Filter by config kind", + "in": "query", + "name": "kind", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "List of configs", + "properties": { + "data": { + "description": "The configs", + "items": { + "description": "API schema for a config resource.", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "current_version": { + "description": "Current version", + "properties": { + "change_description": { + "description": "Description of changes", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "data": { + "description": "Additional structured data", + "type": "object" + }, + "id": { + "description": "Config version ID (cfv_...)", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "version_number": { + "description": "Version number", + "type": "integer" + } + }, + "required": [ + "id", + "version_number" + ], + "type": "object" + }, + "id": { + "description": "Config ID (cfg_...)", + "type": "string" + }, + "is_archived": { + "description": "Whether config is archived", + "type": "boolean" + }, + "kind": { + "description": "Config kind (e.g., Agent, APITool)", + "type": "string" + }, + "lookup_key": { + "description": "Optional lookup key", + "type": "string" + }, + "mime_type": { + "description": "Content mime type", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "raw_content": { + "description": "Raw file content (system configs only)", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User", + "type": "string" + }, + "virtual_path": { + "description": "Unique path within the team", + "type": "string" + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request - owner required" + }, + "401": { + "description": "Unauthorized" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_config", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "change_description": { + "description": "Description of changes", + "type": "string" + }, + "data": { + "description": "Additional structured data stored on the version", + "type": "object" + }, + "kind": { + "description": "Config kind (e.g., Agent, APITool)", + "type": "string" + }, + "lookup_key": { + "description": "Optional lookup key", + "type": "string" + }, + "mime_type": { + "description": "Content mime type", + "type": "string" + }, + "raw_content": { + "description": "Raw content (YAML/JSON/etc)", + "type": "string" + }, + "team": { + "description": "Team ID (for team-owned configs)", + "type": "string" + }, + "user": { + "description": "User ID (for user-owned configs)", + "type": "string" + }, + "virtual_path": { + "description": "Unique path within the owner scope", + "type": "string" + } + }, + "required": [ + "kind", + "raw_content", + "mime_type" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request - owner required" + }, + "401": { + "description": "Unauthorized" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/config/encrypt_secret": { + "post": { + "operationId": "post_api_v1_config_encrypt_secret", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "plaintext": { + "description": "Secret value to encrypt", + "type": "string" + } + }, + "required": [ + "plaintext" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Encrypted secret value", + "properties": { + "encrypted_value": { + "description": "Encrypted ciphertext for use in secret_value!", + "type": "string" + } + }, + "required": [ + "encrypted_value" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "422": { + "description": "Encryption failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/config/kinds": { + "get": { + "operationId": "get_api_v1_config_kinds", + "parameters": [ + { + "description": "Filter by config kind name(s)", + "in": "query", + "name": "kind", + "required": false, + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "List of available config kinds", + "properties": { + "data": { + "description": "The config kinds", + "items": { + "description": "Schema for a config kind in the list response.\n", + "properties": { + "classification": { + "description": "Kind classification: root or supplemental", + "type": "string" + }, + "description": { + "description": "Markdown documentation describing what this config kind represents and how to use it", + "type": "string" + }, + "kind": { + "description": "The config kind name (e.g., Agent, APITool)", + "type": "string" + }, + "sample_available": { + "description": "Whether a YAML sample is available", + "type": "boolean" + }, + "schema_available": { + "description": "Whether a JSON schema is available", + "type": "boolean" + } + }, + "required": [ + "kind", + "sample_available", + "schema_available", + "classification" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/config/kinds/{kind}/schema": { + "get": { + "operationId": "get_api_v1_config_kinds__kind_schema", + "parameters": [ + { + "description": "The config kind to get schema for", + "in": "path", + "name": "kind", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigKindSchema" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Kind not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/config/system": { + "get": { + "operationId": "get_api_v1_config_system", + "parameters": [ + { + "description": "Filter by config kind", + "in": "query", + "name": "kind", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "List of system configs", + "properties": { + "data": { + "description": "The system configs", + "items": { + "description": "API schema for a config resource.", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "current_version": { + "description": "Current version", + "properties": { + "change_description": { + "description": "Description of changes", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "data": { + "description": "Additional structured data", + "type": "object" + }, + "id": { + "description": "Config version ID (cfv_...)", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "version_number": { + "description": "Version number", + "type": "integer" + } + }, + "required": [ + "id", + "version_number" + ], + "type": "object" + }, + "id": { + "description": "Config ID (cfg_...)", + "type": "string" + }, + "is_archived": { + "description": "Whether config is archived", + "type": "boolean" + }, + "kind": { + "description": "Config kind (e.g., Agent, APITool)", + "type": "string" + }, + "lookup_key": { + "description": "Optional lookup key", + "type": "string" + }, + "mime_type": { + "description": "Content mime type", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "raw_content": { + "description": "Raw file content (system configs only)", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User", + "type": "string" + }, + "virtual_path": { + "description": "Unique path within the team", + "type": "string" + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/config/system/{system}": { + "get": { + "operationId": "get_api_v1_config_system__system", + "parameters": [ + { + "description": "Config path or lookup_key", + "in": "path", + "name": "system", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Config not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/config/system/{system}/clone": { + "post": { + "operationId": "post_api_v1_config_system__system_clone", + "parameters": [ + { + "description": "Source system config path or lookup_key", + "in": "path", + "name": "system", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "lookup_key": { + "description": "Override lookup_key on the clone", + "type": "string" + }, + "org": { + "description": "Scope the clone to an org (must match viewer's org if set)", + "type": "string" + }, + "system": { + "description": "Source system config path or lookup_key", + "type": "string" + }, + "team": { + "description": "Clone to this team", + "type": "string" + }, + "user": { + "description": "Clone to this user", + "type": "string" + }, + "virtual_path": { + "description": "Override virtual_path on the clone", + "type": "string" + } + }, + "required": [ + "system" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request - owner required" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Source config not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/config/validate": { + "post": { + "operationId": "post_api_v1_config_validate", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "kind": { + "description": "Config kind to validate against", + "type": "string" + }, + "mime_type": { + "description": "Content mime type", + "type": "string" + }, + "raw_content": { + "description": "Raw content to validate", + "type": "string" + }, + "team": { + "description": "Team ID (for team-owned configs)", + "type": "string" + }, + "user": { + "description": "User ID (for user-owned configs)", + "type": "string" + } + }, + "required": [ + "kind", + "raw_content", + "mime_type" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationResult" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request - owner required" + }, + "401": { + "description": "Unauthorized" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/config/{config}": { + "delete": { + "operationId": "delete_api_v1_config__config", + "parameters": [ + { + "description": "Config ID", + "in": "path", + "name": "config", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "400": { + "description": "Bad request - owner required" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Config not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_config__config", + "parameters": [ + { + "description": "Config ID, virtual_path, or lookup_key", + "in": "path", + "name": "config", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Team ID (for team-owned configs)", + "in": "query", + "name": "team", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "User ID (for user-owned configs)", + "in": "query", + "name": "user", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request - owner required" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Config not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "put": { + "operationId": "put_api_v1_config__config", + "parameters": [ + { + "description": "Config ID", + "in": "path", + "name": "config", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "change_description": { + "description": "Description of changes", + "type": "string" + }, + "config": { + "description": "Config ID", + "type": "string" + }, + "data": { + "description": "Additional structured data stored on the version", + "type": "object" + }, + "lookup_key": { + "description": "Update lookup key", + "type": "string" + }, + "mime_type": { + "description": "Content mime type", + "type": "string" + }, + "raw_content": { + "description": "Raw content (YAML/JSON/etc)", + "type": "string" + }, + "team": { + "description": "Team ID (for team-owned configs)", + "type": "string" + }, + "user": { + "description": "User ID (for user-owned configs)", + "type": "string" + }, + "virtual_path": { + "description": "Update virtual path", + "type": "string" + } + }, + "required": [ + "config", + "raw_content", + "mime_type" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request - owner required" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Config not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/config/{config}/archive": { + "post": { + "operationId": "post_api_v1_config__config_archive", + "parameters": [ + { + "description": "Config ID", + "in": "path", + "name": "config", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "config": { + "description": "Config ID", + "type": "string" + }, + "team": { + "description": "Team ID (for team-owned configs)", + "type": "string" + }, + "user": { + "description": "User ID (for user-owned configs)", + "type": "string" + } + }, + "required": [ + "config" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request - owner required" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Config not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/config/{config}/content": { + "get": { + "operationId": "get_api_v1_config__config_content", + "parameters": [ + { + "description": "Config ID", + "in": "path", + "name": "config", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Team ID (for team-owned configs)", + "in": "query", + "name": "team", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "User ID (for user-owned configs)", + "in": "query", + "name": "user", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Output format: yaml or json (converts if needed)", + "in": "query", + "name": "format", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "*/*": { + "schema": { + "format": "binary", + "type": "string" + } + } + }, + "description": "Raw config content" + }, + "400": { + "description": "Bad request - owner required or conversion not possible" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Config not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/config/{config}/unarchive": { + "post": { + "operationId": "post_api_v1_config__config_unarchive", + "parameters": [ + { + "description": "Config ID", + "in": "path", + "name": "config", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "config": { + "description": "Config ID", + "type": "string" + }, + "team": { + "description": "Team ID (for team-owned configs)", + "type": "string" + }, + "user": { + "description": "User ID (for user-owned configs)", + "type": "string" + } + }, + "required": [ + "config" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request - owner required" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Config not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/config/{config}/versions": { + "get": { + "operationId": "get_api_v1_config__config_versions", + "parameters": [ + { + "description": "Config ID", + "in": "path", + "name": "config", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Team ID (for team-owned configs)", + "in": "query", + "name": "team", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "User ID (for user-owned configs)", + "in": "query", + "name": "user", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Version history response", + "properties": { + "versions": { + "description": "List of versions", + "items": { + "description": "API schema for a config version.", + "properties": { + "change_description": { + "description": "Description of changes", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "data": { + "description": "Additional structured data", + "type": "object" + }, + "id": { + "description": "Config version ID (cfv_...)", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "version_number": { + "description": "Version number", + "type": "integer" + } + }, + "required": [ + "id", + "version_number" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "versions" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request - owner required" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Config not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/custom_objects/{object}": { + "delete": { + "operationId": "delete_api_v1_custom_objects__object", + "parameters": [ + { + "description": "Object ID", + "in": "path", + "name": "object", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_custom_objects__object", + "parameters": [ + { + "description": "Object schema type (lookup_key)", + "in": "query", + "name": "type", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Object ID", + "in": "path", + "name": "object", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomObject" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "put": { + "operationId": "put_api_v1_custom_objects__object", + "parameters": [ + { + "description": "Object ID", + "in": "path", + "name": "object", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "field_ops": { + "description": "Granular array operations per field", + "type": "object" + }, + "fields": { + "description": "Object field values to update", + "type": "object" + }, + "object": { + "description": "Object ID", + "type": "string" + }, + "type": { + "description": "Object schema type (lookup_key)", + "type": "string" + } + }, + "required": [ + "object", + "fields" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Updated custom object response", + "properties": { + "data": { + "description": "The updated object", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "fields": { + "description": "Object field values", + "type": "object" + }, + "id": { + "description": "Public ID (cobj_...)", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "row_key": { + "description": "Row key", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "schema_type": { + "description": "Schema type (lookup_key)", + "type": "string" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + }, + "version": { + "description": "Aggregate version for OCC", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "meta": { + "description": "Version metadata", + "type": "object" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/installation_sources/{source}": { + "delete": { + "operationId": "delete_api_v1_installation_sources__source", + "parameters": [ + { + "description": "Source ID to remove", + "in": "path", + "name": "source", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Installation or source not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/kv": { + "get": { + "operationId": "get_api_v1_kv", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KeyValueStorageEntryList" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_kv", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "key": { + "description": "Storage key", + "type": "string" + }, + "value": { + "description": "Value to store", + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KeyValueStorageEntry" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "422": { + "description": "Invalid parameters" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/kv/{key}": { + "delete": { + "operationId": "delete_api_v1_kv__key", + "parameters": [ + { + "description": "Storage key", + "in": "path", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Entry not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_kv__key", + "parameters": [ + { + "description": "Storage key", + "in": "path", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KeyValueStorageEntry" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Entry not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "put": { + "operationId": "put_api_v1_kv__key", + "parameters": [ + { + "description": "Storage key", + "in": "path", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "value": { + "description": "Value to store", + "type": "string" + } + }, + "required": [ + "value" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KeyValueStorageEntry" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "422": { + "description": "Invalid parameters" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ], + "x-sdk-name": "upsert" + } + }, + "/api/v1/orgs": { + "get": { + "operationId": "get_api_v1_orgs", + "parameters": [ + { + "description": "Search by name, slug, or domain (case-insensitive)", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Page number (default 1)", + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Results per page (default 25, max 100)", + "in": "query", + "name": "page_size", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Paginated list of organizations", + "properties": { + "data": { + "description": "The organizations", + "items": { + "description": "Public org schema for authenticated endpoints.\n\nOnly exposes fields safe for any authenticated user: id, name, domain,\nand logo. Does NOT expose sandbox, status, industry, description, or\nother internal fields that the Developer.Org schema includes.\n", + "properties": { + "domain": { + "description": "Primary domain", + "type": "string" + }, + "id": { + "description": "Public ID (org_...)", + "type": "string" + }, + "name": { + "description": "Organization name", + "type": "string" + } + }, + "required": [ + "id", + "name", + "domain" + ], + "type": "object" + }, + "type": "array" + }, + "has_next": { + "description": "Whether there are more pages", + "type": "boolean" + }, + "has_prev": { + "description": "Whether there are previous pages", + "type": "boolean" + }, + "page": { + "description": "Current page", + "type": "integer" + }, + "page_size": { + "description": "Results per page", + "type": "integer" + }, + "total_entries": { + "description": "Total matching organizations", + "type": "integer" + }, + "total_pages": { + "description": "Total pages", + "type": "integer" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "App-scoped token required. Use a token scoped to the target app." + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/team_memberships": { + "get": { + "operationId": "get_api_v1_team_memberships", + "parameters": [ + { + "description": "Filter by team IDs", + "in": "query", + "name": "team", + "required": false, + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Filter by user IDs", + "in": "query", + "name": "user", + "required": false, + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Filter by agent IDs", + "in": "query", + "name": "agent", + "required": false, + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Page number (default 1)", + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Results per page (default 25)", + "in": "query", + "name": "page_size", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamMembershipListResponse" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "tags": [ + "s2s" + ], + "x-auth": [ + "secret_key" + ] + } + }, + "/api/v1/team_memberships/{team_membership}": { + "delete": { + "operationId": "delete_api_v1_team_memberships__team_membership", + "parameters": [ + { + "description": "Team membership ID to remove", + "in": "path", + "name": "team_membership", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Team or member not found" + } + }, + "tags": [ + "s2s" + ], + "x-auth": [ + "secret_key" + ] + } + }, + "/api/v1/teams": { + "get": { + "operationId": "get_api_v1_teams", + "parameters": [ + { + "description": "Page number (default 1)", + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Results per page (default 25)", + "in": "query", + "name": "page_size", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search by name or description", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Filter by membership: joined (member teams only), joinable (ACL-visible non-member teams)", + "in": "query", + "name": "membership", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Paginated list of teams", + "properties": { + "data": { + "description": "The teams", + "items": { + "description": "API schema for a team.", + "properties": { + "acl": { + "description": "Access control list. Contains grants array with principal_type, principal, and actions. Null when no ACL restrictions are applied.", + "properties": { + "add": { + "description": "Patch mode: grants to add or merge into existing", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "grants": { + "description": "Replace mode: full list of grants (replaces all existing). Use [] to clear.", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "remove": { + "description": "Patch mode: principals to remove from existing", + "items": { + "description": "API schema for identifying a principal to remove from an ACL.", + "properties": { + "principal": { + "description": "Principal identifier to remove (omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type to remove", + "type": "string" + } + }, + "required": [ + "principal_type" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "app": { + "description": "Application", + "type": "string" + }, + "badges": { + "description": "Badge counts by category", + "type": "object" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Team description", + "type": "string" + }, + "id": { + "description": "Team ID", + "type": "string" + }, + "membership_status": { + "description": "Viewer's membership role (owner, admin, member) or null if not a member", + "type": "string" + }, + "metadata": { + "description": "Team metadata", + "type": "object" + }, + "name": { + "description": "Team name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "slug": { + "description": "URL slug", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + }, + "has_next": { + "description": "Whether there is a next page", + "type": "boolean" + }, + "has_prev": { + "description": "Whether there is a previous page", + "type": "boolean" + }, + "page": { + "description": "Current page number", + "type": "integer" + }, + "page_size": { + "description": "Results per page", + "type": "integer" + }, + "total_entries": { + "description": "Total number of teams", + "type": "integer" + }, + "total_pages": { + "description": "Total number of pages", + "type": "integer" + } + }, + "required": [ + "data", + "page", + "page_size", + "total_entries", + "total_pages", + "has_next", + "has_prev" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_teams", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "acl": { + "description": "Access control list", + "properties": { + "add": { + "description": "Patch mode: grants to add or merge into existing", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "grants": { + "description": "Replace mode: full list of grants (replaces all existing). Use [] to clear.", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "remove": { + "description": "Patch mode: principals to remove from existing", + "items": { + "description": "API schema for identifying a principal to remove from an ACL.", + "properties": { + "principal": { + "description": "Principal identifier to remove (omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type to remove", + "type": "string" + } + }, + "required": [ + "principal_type" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "description": { + "description": "Team description", + "type": "string" + }, + "metadata": { + "description": "Arbitrary key-value metadata", + "type": "object" + }, + "name": { + "description": "Team name", + "type": "string" + }, + "org": { + "description": "Organization ID", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Team" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/teams/join_by_code": { + "post": { + "description": "Accepts either `join_code` or `invite_code`.\n\nFor user-authenticated requests, the current user joins the team identified by\nthe invite code. For server-to-server requests, provide `agent` or `user` to\nadd that principal to the team instead.\n", + "operationId": "post_api_v1_teams_join_by_code", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent": { + "description": "Optional agent ID — when provided, adds the agent to the team instead of the user", + "type": "string" + }, + "invite_code": { + "description": "12-character invite code (alias for join_code)", + "type": "string" + }, + "join_code": { + "description": "12-character invite code", + "type": "string" + }, + "user": { + "description": "User ID to join (required for S2S requests without agent_id)", + "type": "string" + } + }, + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Team" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Invalid join code format" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Invalid or expired join code" + }, + "429": { + "description": "Too many requests" + } + }, + "summary": "Join a team using an invite code", + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/teams/{team}": { + "delete": { + "operationId": "delete_api_v1_teams__team", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Team not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_teams__team", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Team" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Team not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "patch": { + "operationId": "patch_api_v1_teams__team", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "acl": { + "description": "Access control list", + "properties": { + "add": { + "description": "Patch mode: grants to add or merge into existing", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "grants": { + "description": "Replace mode: full list of grants (replaces all existing). Use [] to clear.", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "remove": { + "description": "Patch mode: principals to remove from existing", + "items": { + "description": "API schema for identifying a principal to remove from an ACL.", + "properties": { + "principal": { + "description": "Principal identifier to remove (omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type to remove", + "type": "string" + } + }, + "required": [ + "principal_type" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "description": { + "description": "Team description", + "type": "string" + }, + "metadata": { + "description": "Arbitrary key-value metadata", + "type": "object" + }, + "name": { + "description": "Team name", + "type": "string" + }, + "profile_picture": { + "description": "Base64-encoded profile picture", + "properties": { + "data": { + "description": "Base64 encoded image data", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "mime_type": { + "description": "MIME type of the image", + "type": "string" + } + }, + "type": "object" + }, + "team": { + "description": "Team ID", + "type": "string" + } + }, + "required": [ + "team" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Team" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden - app scope required" + }, + "404": { + "description": "Team not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/teams/{team}/artifacts": { + "get": { + "operationId": "get_api_v1_teams__team_artifacts", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "List of artifacts", + "properties": { + "data": { + "description": "The artifacts", + "items": { + "description": "API schema for an artifact.", + "properties": { + "agent": { + "description": "Agent", + "type": "string" + }, + "content_type": { + "description": "MIME content type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "current_version": { + "description": "Current version ID", + "type": "string" + }, + "description": { + "description": "Artifact description", + "type": "string" + }, + "file": { + "description": "Storage file", + "type": "string" + }, + "file_name": { + "description": "Original filename", + "type": "string" + }, + "file_url": { + "description": "Signed file URL", + "type": "string" + }, + "id": { + "description": "Artifact ID", + "type": "string" + }, + "image_source": { + "description": "Image source metadata", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "description": "Artifact name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "thread": { + "description": "Thread", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User", + "type": "string" + }, + "version": { + "description": "Current version number", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Team not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_teams__team_artifacts", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "artifact": { + "description": "Artifact attributes", + "type": "object" + } + }, + "required": [ + "artifact" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Artifact" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "422": { + "description": "Validation error" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/teams/{team}/custom_objects": { + "get": { + "operationId": "get_api_v1_teams__team_custom_objects", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Object schema type (lookup_key)", + "in": "query", + "name": "type", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Maximum number of results", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Offset for pagination", + "in": "query", + "name": "offset", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Filter by row_key", + "in": "query", + "name": "row_key", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Filter by sort_key", + "in": "query", + "name": "sort_key", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "List of custom objects", + "properties": { + "data": { + "description": "The objects", + "items": { + "description": "API schema for a custom object.", + "properties": { + "created_at": { + "description": "Created timestamp", + "format": "date-time", + "type": "string" + }, + "fields": { + "description": "Object field values", + "type": "object" + }, + "id": { + "description": "Public ID (cobj_...)", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "row_key": { + "description": "Row key", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "schema_type": { + "description": "Schema type (lookup_key)", + "type": "string" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "updated_at": { + "description": "Updated timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + }, + "version": { + "description": "Aggregate version for OCC", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + }, + "meta": { + "description": "Pagination metadata", + "type": "object" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Team not found or schema type not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_teams__team_custom_objects", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "fields": { + "description": "Object field values", + "type": "object" + }, + "team": { + "description": "Team ID", + "type": "string" + }, + "type": { + "description": "Object schema type (lookup_key)", + "type": "string" + } + }, + "required": [ + "team", + "type", + "fields" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomObject" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Team not found or schema type not found" + }, + "422": { + "description": "Validation error" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/teams/{team}/invite": { + "post": { + "operationId": "post_api_v1_teams__team_invite", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "team": { + "description": "Team ID", + "type": "string" + } + }, + "required": [ + "team" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamInvite" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Team not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/teams/{team}/invites": { + "post": { + "operationId": "post_api_v1_teams__team_invites", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "team": { + "description": "Team ID", + "type": "string" + } + }, + "required": [ + "team" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "The invite", + "properties": { + "code": { + "description": "6-character join code", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Team not found" + } + }, + "tags": [ + "s2s" + ], + "x-auth": [ + "secret_key" + ] + } + }, + "/api/v1/teams/{team}/join": { + "post": { + "description": "Joins a specific visible team by team ID.\n\nFor standard user requests, the current user is added to the team. When\n`agent` is provided, the current user must already be a team member and the\nagent is added instead.\n", + "operationId": "post_api_v1_teams__team_join", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent": { + "description": "Optional agent ID — when provided, adds the agent to the team instead of the user", + "type": "string" + }, + "team": { + "description": "Team ID", + "type": "string" + } + }, + "required": [ + "team" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "No content" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "summary": "Join a team the current user can see", + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/teams/{team}/leave": { + "delete": { + "operationId": "delete_api_v1_teams__team_leave", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not a member of this team" + }, + "422": { + "description": "Failed to leave team" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/teams/{team}/members": { + "get": { + "operationId": "get_api_v1_teams__team_members", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "List of team members", + "properties": { + "data": { + "description": "The members", + "items": { + "description": "API schema for a team membership.", + "properties": { + "agent": { + "description": "Agent object (when loaded)", + "properties": { + "acl": { + "description": "Access control list. Contains grants array with principal_type, principal, and actions. Null when no ACL restrictions are applied.", + "properties": { + "add": { + "description": "Patch mode: grants to add or merge into existing", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "grants": { + "description": "Replace mode: full list of grants (replaces all existing). Use [] to clear.", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "remove": { + "description": "Patch mode: principals to remove from existing", + "items": { + "description": "API schema for identifying a principal to remove from an ACL.", + "properties": { + "principal": { + "description": "Principal identifier to remove (omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type to remove", + "type": "string" + } + }, + "required": [ + "principal_type" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "app": { + "description": "Application", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "default_model": { + "description": "Default AI model", + "type": "string" + }, + "email": { + "description": "Agent email", + "type": "string" + }, + "id": { + "description": "Agent ID (agi_...)", + "type": "string" + }, + "identity": { + "description": "Identity prompt", + "type": "string" + }, + "lookup_key": { + "description": "Unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Agent name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "phone_number": { + "description": "Agent phone number", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Membership ID", + "type": "string" + }, + "joined_at": { + "description": "Join timestamp", + "format": "date-time", + "type": "string" + }, + "metadata": { + "description": "Membership metadata", + "type": "object" + }, + "name": { + "description": "Member name", + "type": "string" + }, + "profile_picture": { + "description": "Profile picture", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "role": { + "description": "Role in team", + "type": "string" + }, + "team": { + "description": "Team object (when loaded)", + "type": "object" + }, + "type": { + "description": "Member type (user, agent, unknown)", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User object (when loaded)", + "properties": { + "alias": { + "description": "User alias/handle", + "type": "string" + }, + "email": { + "description": "User email address", + "type": "string" + }, + "id": { + "description": "User ID", + "type": "string" + }, + "metadata": { + "description": "User metadata", + "type": "object" + }, + "name": { + "description": "User display name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Team not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_teams__team_members", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent": { + "description": "Agent ID (provide exactly one of user_id or agent_id)", + "type": "string" + }, + "app": { + "description": "App ID", + "type": "string" + }, + "role": { + "description": "Member role (default: member)", + "type": "string" + }, + "team": { + "description": "Team ID", + "type": "string" + }, + "user": { + "description": "User ID (provide exactly one of user_id or agent_id)", + "type": "string" + } + }, + "required": [ + "team" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamMembership" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Team or member not found" + }, + "422": { + "description": "Validation failed" + } + }, + "tags": [ + "s2s" + ], + "x-auth": [ + "secret_key" + ] + } + }, + "/api/v1/teams/{team}/threads": { + "get": { + "operationId": "get_api_v1_teams__team_threads", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "List of threads response", + "properties": { + "data": { + "description": "The threads", + "items": { + "description": "API schema for a chat thread.", + "properties": { + "agent_user": { + "description": "Owning agent user", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "creator": { + "description": "Creator user object", + "properties": { + "alias": { + "description": "User alias/handle", + "type": "string" + }, + "email": { + "description": "User email address", + "type": "string" + }, + "id": { + "description": "User ID", + "type": "string" + }, + "metadata": { + "description": "User metadata", + "type": "object" + }, + "name": { + "description": "User display name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "description": { + "description": "Thread description", + "type": "string" + }, + "id": { + "description": "Thread ID (thr_...)", + "type": "string" + }, + "is_channel": { + "description": "Whether this is a channel", + "type": "boolean" + }, + "is_default": { + "description": "Whether this is the default thread", + "type": "boolean" + }, + "is_transient": { + "description": "Whether this thread is transient", + "type": "boolean" + }, + "is_unlisted": { + "description": "Whether this thread is unlisted", + "type": "boolean" + }, + "key": { + "description": "Thread key", + "type": "string" + }, + "last_activity": { + "description": "Last activity timestamp", + "format": "date-time", + "type": "string" + }, + "metadata": { + "description": "Thread metadata", + "type": "object" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "parent_message": { + "description": "Parent message object", + "properties": { + "actors": { + "description": "Message actors", + "items": { + "description": "Schema for a message actor (user or agent).\n\nActors represent the entity that sent a message.\nMaps to the actor format from MessageActorHelper.build_actor/1.\n", + "properties": { + "alias": { + "description": "Actor alias/handle", + "type": "string" + }, + "id": { + "description": "Actor ID (format: user-xxx or agent-xxx)", + "type": "string" + }, + "name": { + "description": "Actor display name", + "type": "string" + }, + "profile_picture": { + "description": "Profile picture", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "agent": { + "description": "Agent if sent by an agent user", + "type": "string" + }, + "attachments": { + "description": "Message attachments", + "items": { + "description": "Schema for a message attachment.\n\nAttachments can be of various types (file, scraped_link, artifact, task, media, action).\nFields present depend on the attachment type.\nMaps to format_attachments_for_client/1 output.\n", + "properties": { + "content_type": { + "description": "MIME content type (file, artifact, media types)", + "type": "string" + }, + "description": { + "description": "Description (scraped_link, artifact, task types)", + "type": "string" + }, + "filename": { + "description": "File name (file, artifact, media types)", + "type": "string" + }, + "height": { + "description": "Media height (media type)", + "type": "integer" + }, + "id": { + "description": "Attachment ID", + "type": "string" + }, + "image_height": { + "description": "Preview image height (scraped_link type)", + "type": "integer" + }, + "image_source": { + "description": "Image metadata (file, scraped_link, artifact, media types)", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "image_url": { + "description": "Preview image URL (scraped_link type)", + "type": "string" + }, + "image_width": { + "description": "Preview image width (scraped_link type)", + "type": "integer" + }, + "media_type": { + "description": "Media type (media type)", + "type": "string" + }, + "name": { + "description": "Media name (media type)", + "type": "string" + }, + "object": { + "description": "Embedded object (task, action types)", + "type": "object" + }, + "title": { + "description": "Title (scraped_link, artifact, task types)", + "type": "string" + }, + "type": { + "description": "Attachment type: file, scraped_link, artifact, task, media, action", + "type": "string" + }, + "url": { + "description": "URL to the resource (file, scraped_link, artifact, media types)", + "type": "string" + }, + "variants": { + "description": "Media variants (media type)", + "items": { + "description": "API schema for a media variant.", + "properties": { + "content_type": { + "description": "File content type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "file": { + "description": "Storage file", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "height": { + "description": "Height in pixels", + "type": "integer" + }, + "id": { + "description": "Variant ID", + "type": "string" + }, + "image_source": { + "description": "Image source metadata", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "url": { + "description": "Signed download URL", + "type": "string" + }, + "variant_key": { + "description": "Variant key (original, thumbnail, etc)", + "type": "string" + }, + "width": { + "description": "Width in pixels", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + }, + "version": { + "description": "Artifact version number (artifact type)", + "type": "integer" + }, + "width": { + "description": "Media width (media type)", + "type": "integer" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + "type": "array" + }, + "branched_thread": { + "description": "Branched thread (if message spawned a thread)", + "type": "string" + }, + "content": { + "description": "Message content", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "has_replies": { + "description": "Whether message has replies", + "type": "boolean" + }, + "id": { + "description": "Message ID (msg_...)", + "type": "string" + }, + "idempotency_key": { + "description": "Client-provided idempotency key", + "type": "string" + }, + "legacy_agent": { + "description": "Legacy agent if sent by legacy chat agent", + "type": "string" + }, + "metadata": { + "description": "Message metadata", + "type": "object" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "reactions": { + "description": "Message reactions", + "items": { + "description": "Schema for inline message reactions.\n\nThis is the compact format used in Message.reactions[], which differs from\nthe full Reaction schema used in standalone reaction endpoints.\nMaps to format_reactions_for_client/1 output.\n", + "properties": { + "payload": { + "description": "Reaction payload (e.g., {emoji: '👍'})", + "type": "object" + }, + "type": { + "description": "Reaction type (e.g., emoji_reaction)", + "type": "string" + }, + "user": { + "description": "User who added the reaction", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "type": "array" + }, + "rendering_mode": { + "description": "Rendering mode hint", + "type": "string" + }, + "replies": { + "description": "Inline replies (if loaded)", + "items": { + "type": "object" + }, + "type": "array" + }, + "replies_after_cursor": { + "description": "Cursor for replies pagination", + "type": "string" + }, + "replies_before_cursor": { + "description": "Cursor for replies pagination", + "type": "string" + }, + "reply_count": { + "description": "Number of replies", + "type": "integer" + }, + "reply_to": { + "description": "Parent message object (if loaded)", + "type": "object" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "thread": { + "description": "Parent thread", + "type": "string" + }, + "user": { + "description": "Author user (public ID or expanded object when loaded)", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "participant": { + "description": "Participant users", + "items": { + "type": "string" + }, + "type": "array" + }, + "participants": { + "description": "Participant user objects", + "items": { + "description": "API schema for a user.", + "properties": { + "alias": { + "description": "User alias/handle", + "type": "string" + }, + "email": { + "description": "User email address", + "type": "string" + }, + "id": { + "description": "User ID", + "type": "string" + }, + "metadata": { + "description": "User metadata", + "type": "object" + }, + "name": { + "description": "User display name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + }, + "participating_actor": { + "description": "Actors participating in thread", + "items": { + "type": "string" + }, + "type": "array" + }, + "participating_agents": { + "description": "Agents participating in thread", + "items": { + "description": "API schema for an agent.", + "properties": { + "acl": { + "description": "Access control list. Contains grants array with principal_type, principal, and actions. Null when no ACL restrictions are applied.", + "properties": { + "add": { + "description": "Patch mode: grants to add or merge into existing", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "grants": { + "description": "Replace mode: full list of grants (replaces all existing). Use [] to clear.", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "remove": { + "description": "Patch mode: principals to remove from existing", + "items": { + "description": "API schema for identifying a principal to remove from an ACL.", + "properties": { + "principal": { + "description": "Principal identifier to remove (omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type to remove", + "type": "string" + } + }, + "required": [ + "principal_type" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "app": { + "description": "Application", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "default_model": { + "description": "Default AI model", + "type": "string" + }, + "email": { + "description": "Agent email", + "type": "string" + }, + "id": { + "description": "Agent ID (agi_...)", + "type": "string" + }, + "identity": { + "description": "Identity prompt", + "type": "string" + }, + "lookup_key": { + "description": "Unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Agent name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "phone_number": { + "description": "Agent phone number", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + }, + "role": { + "description": "User's role in the thread", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "settings": { + "description": "Thread settings", + "properties": { + "agent_enabled": { + "description": "Whether the agent is enabled for this thread", + "type": "boolean" + } + }, + "type": "object" + }, + "slug": { + "description": "Thread slug", + "type": "string" + }, + "sub_threads": { + "description": "Sub-threads", + "items": { + "type": "object" + }, + "type": "array" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "title": { + "description": "Thread title", + "type": "string" + }, + "ttl": { + "description": "Time-to-live in seconds", + "type": "integer" + }, + "unread_count": { + "description": "Unread message count", + "type": "integer" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Team not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_teams__team_threads", + "parameters": [ + { + "description": "Team ID", + "in": "path", + "name": "team", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "skip_welcome_message": { + "description": "Skip automatic welcome message", + "type": "boolean" + }, + "team": { + "description": "Team ID", + "type": "string" + }, + "thread": { + "description": "Thread attributes", + "properties": { + "create_legacy_agent": { + "description": "Create a legacy chat agent for this thread", + "type": "boolean" + }, + "description": { + "description": "Thread description", + "type": "string" + }, + "is_unlisted": { + "description": "Whether the thread is unlisted", + "type": "boolean" + }, + "key": { + "description": "Unique key for the thread", + "type": "string" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "org_id": { + "description": "Organization ID", + "type": "string" + }, + "profile_picture": { + "description": "Base64 encoded profile picture", + "properties": { + "data": { + "description": "Base64 encoded image data", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "mime_type": { + "description": "MIME type of the image", + "type": "string" + } + }, + "type": "object" + }, + "settings": { + "description": "Thread settings", + "properties": { + "agent_enabled": { + "description": "Whether the agent is enabled for this thread", + "type": "boolean" + } + }, + "type": "object" + }, + "title": { + "description": "Thread title", + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "team", + "thread" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Thread" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Team not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/thread_messages/{message}": { + "delete": { + "operationId": "delete_api_v1_thread_messages__message", + "parameters": [ + { + "description": "Message ID", + "in": "path", + "name": "message", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Message not found" + }, + "422": { + "description": "Delete failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "put": { + "operationId": "put_api_v1_thread_messages__message", + "parameters": [ + { + "description": "Message ID", + "in": "path", + "name": "message", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "content": { + "description": "New message content", + "type": "string" + }, + "message": { + "description": "Message ID", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Message not found" + }, + "422": { + "description": "Update failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/thread_messages/{message}/reactions": { + "delete": { + "operationId": "delete_api_v1_thread_messages__message_reactions", + "parameters": [ + { + "description": "Message ID", + "in": "path", + "name": "message", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Message not found" + }, + "422": { + "description": "Remove reaction failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_thread_messages__message_reactions", + "parameters": [ + { + "description": "Message ID", + "in": "path", + "name": "message", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "emoji": { + "description": "Emoji to add as reaction", + "type": "string" + }, + "message": { + "description": "Message ID", + "type": "string" + } + }, + "required": [ + "message", + "emoji" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Created reaction response", + "properties": { + "data": { + "description": "The created reaction", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "feedback_type": { + "description": "Type of feedback (e.g., emoji_reaction)", + "type": "string" + }, + "id": { + "description": "Reaction ID (umf_...)", + "type": "string" + }, + "message": { + "description": "Message the reaction is on", + "type": "string" + }, + "payload": { + "description": "Reaction payload (e.g., {emoji: ...})", + "type": "object" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User who added the reaction", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Message not found" + }, + "422": { + "description": "Add reaction failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/thread_messages/{message}/replies": { + "get": { + "operationId": "get_api_v1_thread_messages__message_replies", + "parameters": [ + { + "description": "Message ID", + "in": "path", + "name": "message", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Cursor for fetching items before this point", + "in": "query", + "name": "before_cursor", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Cursor for fetching items after this point", + "in": "query", + "name": "after_cursor", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Maximum number of items to return (default 20)", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "If true, returns all replies in the tree (default: direct replies)", + "in": "query", + "name": "tree", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedReplies" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Message not found" + }, + "422": { + "description": "Query failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/threads/{thread}": { + "delete": { + "operationId": "delete_api_v1_threads__thread", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Thread not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_threads__thread", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Thread" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Thread not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "put": { + "operationId": "put_api_v1_threads__thread", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "description": { + "description": "Thread description", + "type": "string" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "profile_picture": { + "description": "Base64 encoded profile picture", + "properties": { + "data": { + "description": "Base64 encoded image data", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "mime_type": { + "description": "MIME type of the image", + "type": "string" + } + }, + "type": "object" + }, + "thread": { + "description": "Thread ID", + "type": "string" + }, + "title": { + "description": "Thread title", + "type": "string" + } + }, + "required": [ + "thread" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Thread" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Thread not found" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/threads/{thread}/agents": { + "get": { + "operationId": "get_api_v1_threads__thread_agents", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "List of agents", + "properties": { + "data": { + "description": "The agents", + "items": { + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Thread not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/threads/{thread}/artifacts": { + "get": { + "operationId": "get_api_v1_threads__thread_artifacts", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "List of thread artifacts", + "properties": { + "data": { + "description": "The artifacts", + "items": { + "description": "API schema for an artifact.", + "properties": { + "agent": { + "description": "Agent", + "type": "string" + }, + "content_type": { + "description": "MIME content type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "current_version": { + "description": "Current version ID", + "type": "string" + }, + "description": { + "description": "Artifact description", + "type": "string" + }, + "file": { + "description": "Storage file", + "type": "string" + }, + "file_name": { + "description": "Original filename", + "type": "string" + }, + "file_url": { + "description": "Signed file URL", + "type": "string" + }, + "id": { + "description": "Artifact ID", + "type": "string" + }, + "image_source": { + "description": "Image source metadata", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "description": "Artifact name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "thread": { + "description": "Thread", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User", + "type": "string" + }, + "version": { + "description": "Current version number", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Thread not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/threads/{thread}/mark_read": { + "post": { + "operationId": "post_api_v1_threads__thread_mark_read", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "last_read_message": { + "description": "The message ID to mark as the last read", + "type": "string" + }, + "thread": { + "description": "Thread ID", + "type": "string" + }, + "use_latest_message": { + "description": "If true, uses the latest message in the thread", + "type": "boolean" + }, + "user": { + "description": "User ID to mark as read for (required for S2S)", + "type": "string" + } + }, + "required": [ + "thread" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Thread not found" + }, + "422": { + "description": "Validation error" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/threads/{thread}/members": { + "delete": { + "operationId": "delete_api_v1_threads__thread_members", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Thread or member not found" + }, + "422": { + "description": "Failed to remove member" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "get": { + "operationId": "get_api_v1_threads__thread_members", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "List of thread members", + "properties": { + "data": { + "description": "The members", + "items": { + "description": "API schema for a thread member.", + "properties": { + "membership_type": { + "description": "Membership type (owner or member)", + "type": "string" + }, + "thread": { + "description": "Thread", + "type": "string" + }, + "user": { + "description": "User details (when loaded)", + "properties": { + "alias": { + "description": "User alias/handle", + "type": "string" + }, + "email": { + "description": "User email address", + "type": "string" + }, + "id": { + "description": "User ID", + "type": "string" + }, + "metadata": { + "description": "User metadata", + "type": "object" + }, + "name": { + "description": "User display name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Thread not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_threads__thread_members", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "agent": { + "description": "Agent ID (required when type is \"agent\")", + "type": "string" + }, + "membership_type": { + "description": "Membership type: \"owner\" or \"member\" (defaults to \"member\")", + "type": "string" + }, + "thread": { + "description": "Thread ID", + "type": "string" + }, + "type": { + "description": "Member type: \"user\" or \"agent\"", + "type": "string" + }, + "user": { + "description": "User ID (required when type is \"user\")", + "type": "string" + } + }, + "required": [ + "thread", + "type" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ThreadMember" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Thread not found" + }, + "422": { + "description": "Failed to add member" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/threads/{thread}/messages": { + "get": { + "operationId": "get_api_v1_threads__thread_messages", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Cursor for fetching items before this point", + "in": "query", + "name": "before_cursor", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Cursor for fetching items after this point", + "in": "query", + "name": "after_cursor", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Maximum number of items to return (default 20)", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Whether to include reply counts (default false)", + "in": "query", + "name": "include_reply_counts", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Paginated list of messages", + "properties": { + "data": { + "description": "Message data wrapper", + "properties": { + "after_cursor": { + "description": "Cursor for fetching items after this point", + "type": "string" + }, + "before_cursor": { + "description": "Cursor for fetching items before this point", + "type": "string" + }, + "messages": { + "description": "List of message objects", + "items": { + "description": "API schema for a chat message.", + "properties": { + "actors": { + "description": "Message actors", + "items": { + "description": "Schema for a message actor (user or agent).\n\nActors represent the entity that sent a message.\nMaps to the actor format from MessageActorHelper.build_actor/1.\n", + "properties": { + "alias": { + "description": "Actor alias/handle", + "type": "string" + }, + "id": { + "description": "Actor ID (format: user-xxx or agent-xxx)", + "type": "string" + }, + "name": { + "description": "Actor display name", + "type": "string" + }, + "profile_picture": { + "description": "Profile picture", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "agent": { + "description": "Agent if sent by an agent user", + "type": "string" + }, + "attachments": { + "description": "Message attachments", + "items": { + "description": "Schema for a message attachment.\n\nAttachments can be of various types (file, scraped_link, artifact, task, media, action).\nFields present depend on the attachment type.\nMaps to format_attachments_for_client/1 output.\n", + "properties": { + "content_type": { + "description": "MIME content type (file, artifact, media types)", + "type": "string" + }, + "description": { + "description": "Description (scraped_link, artifact, task types)", + "type": "string" + }, + "filename": { + "description": "File name (file, artifact, media types)", + "type": "string" + }, + "height": { + "description": "Media height (media type)", + "type": "integer" + }, + "id": { + "description": "Attachment ID", + "type": "string" + }, + "image_height": { + "description": "Preview image height (scraped_link type)", + "type": "integer" + }, + "image_source": { + "description": "Image metadata (file, scraped_link, artifact, media types)", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "image_url": { + "description": "Preview image URL (scraped_link type)", + "type": "string" + }, + "image_width": { + "description": "Preview image width (scraped_link type)", + "type": "integer" + }, + "media_type": { + "description": "Media type (media type)", + "type": "string" + }, + "name": { + "description": "Media name (media type)", + "type": "string" + }, + "object": { + "description": "Embedded object (task, action types)", + "type": "object" + }, + "title": { + "description": "Title (scraped_link, artifact, task types)", + "type": "string" + }, + "type": { + "description": "Attachment type: file, scraped_link, artifact, task, media, action", + "type": "string" + }, + "url": { + "description": "URL to the resource (file, scraped_link, artifact, media types)", + "type": "string" + }, + "variants": { + "description": "Media variants (media type)", + "items": { + "description": "API schema for a media variant.", + "properties": { + "content_type": { + "description": "File content type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "file": { + "description": "Storage file", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "height": { + "description": "Height in pixels", + "type": "integer" + }, + "id": { + "description": "Variant ID", + "type": "string" + }, + "image_source": { + "description": "Image source metadata", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "url": { + "description": "Signed download URL", + "type": "string" + }, + "variant_key": { + "description": "Variant key (original, thumbnail, etc)", + "type": "string" + }, + "width": { + "description": "Width in pixels", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + }, + "version": { + "description": "Artifact version number (artifact type)", + "type": "integer" + }, + "width": { + "description": "Media width (media type)", + "type": "integer" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + "type": "array" + }, + "branched_thread": { + "description": "Branched thread (if message spawned a thread)", + "type": "string" + }, + "content": { + "description": "Message content", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "has_replies": { + "description": "Whether message has replies", + "type": "boolean" + }, + "id": { + "description": "Message ID (msg_...)", + "type": "string" + }, + "idempotency_key": { + "description": "Client-provided idempotency key", + "type": "string" + }, + "legacy_agent": { + "description": "Legacy agent if sent by legacy chat agent", + "type": "string" + }, + "metadata": { + "description": "Message metadata", + "type": "object" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "reactions": { + "description": "Message reactions", + "items": { + "description": "Schema for inline message reactions.\n\nThis is the compact format used in Message.reactions[], which differs from\nthe full Reaction schema used in standalone reaction endpoints.\nMaps to format_reactions_for_client/1 output.\n", + "properties": { + "payload": { + "description": "Reaction payload (e.g., {emoji: '👍'})", + "type": "object" + }, + "type": { + "description": "Reaction type (e.g., emoji_reaction)", + "type": "string" + }, + "user": { + "description": "User who added the reaction", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "type": "array" + }, + "rendering_mode": { + "description": "Rendering mode hint", + "type": "string" + }, + "replies": { + "description": "Inline replies (if loaded)", + "items": { + "type": "object" + }, + "type": "array" + }, + "replies_after_cursor": { + "description": "Cursor for replies pagination", + "type": "string" + }, + "replies_before_cursor": { + "description": "Cursor for replies pagination", + "type": "string" + }, + "reply_count": { + "description": "Number of replies", + "type": "integer" + }, + "reply_to": { + "description": "Parent message object (if loaded)", + "type": "object" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "thread": { + "description": "Parent thread", + "type": "string" + }, + "user": { + "description": "Author user (public ID or expanded object when loaded)", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "messages" + ], + "type": "object" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Thread not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/threads/{thread}/picture": { + "put": { + "operationId": "put_api_v1_threads__thread_picture", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "picture": { + "description": "Picture data to upload", + "properties": { + "data": { + "description": "Base64 encoded image data", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "mime_type": { + "description": "MIME type of the image", + "type": "string" + } + }, + "required": [ + "data", + "mime_type", + "filename" + ], + "type": "object" + }, + "thread": { + "description": "Thread ID", + "type": "string" + } + }, + "required": [ + "thread", + "picture" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Thread" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Thread not found" + }, + "422": { + "description": "Invalid base64 data" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/threads/{thread}/read_status": { + "get": { + "operationId": "get_api_v1_threads__thread_read_status", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User ID to check read status for (required for S2S)", + "in": "query", + "name": "user", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ThreadReadStatus" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Thread not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/threads/{thread}/search": { + "get": { + "operationId": "get_api_v1_threads__thread_search", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Search query", + "in": "query", + "name": "q", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Filter by context source type (e.g. thread/messages)", + "in": "query", + "name": "type", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Search results", + "properties": { + "data": { + "description": "Matching context items (tagged objects)", + "items": { + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Thread not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/threads/{thread}/settings": { + "get": { + "operationId": "get_api_v1_threads__thread_settings", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "The settings object", + "properties": { + "agent_enabled": { + "description": "Whether the agent is enabled for this thread", + "type": "boolean" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Thread not found" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "put": { + "operationId": "put_api_v1_threads__thread_settings", + "parameters": [ + { + "description": "Thread ID", + "in": "path", + "name": "thread", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "settings": { + "description": "Settings to update", + "type": "object" + }, + "thread": { + "description": "Thread ID", + "type": "string" + } + }, + "required": [ + "thread", + "settings" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ThreadSettings" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Thread not found" + }, + "422": { + "description": "Validation error" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/users/me": { + "get": { + "operationId": "get_api_v1_users_me", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/users/{user}": { + "get": { + "operationId": "get_api_v1_users__user", + "parameters": [ + { + "description": "User ID", + "in": "path", + "name": "user", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/users/{user}/artifacts": { + "get": { + "operationId": "get_api_v1_users__user_artifacts", + "parameters": [ + { + "description": "User ID", + "in": "path", + "name": "user", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "List of artifacts", + "properties": { + "data": { + "description": "The artifacts", + "items": { + "description": "API schema for an artifact.", + "properties": { + "agent": { + "description": "Agent", + "type": "string" + }, + "content_type": { + "description": "MIME content type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "current_version": { + "description": "Current version ID", + "type": "string" + }, + "description": { + "description": "Artifact description", + "type": "string" + }, + "file": { + "description": "Storage file", + "type": "string" + }, + "file_name": { + "description": "Original filename", + "type": "string" + }, + "file_url": { + "description": "Signed file URL", + "type": "string" + }, + "id": { + "description": "Artifact ID", + "type": "string" + }, + "image_source": { + "description": "Image source metadata", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "description": "Artifact name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "thread": { + "description": "Thread", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "User", + "type": "string" + }, + "version": { + "description": "Current version number", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_users__user_artifacts", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "artifact": { + "description": "Artifact attributes", + "type": "object" + } + }, + "required": [ + "artifact" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Artifact" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "422": { + "description": "Validation error" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/users/{user}/orgs": { + "get": { + "operationId": "get_api_v1_users__user_orgs", + "parameters": [ + { + "description": "User ID", + "in": "path", + "name": "user", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Current user's organizations", + "properties": { + "data": { + "description": "Organization list", + "items": { + "description": "API schema for an organization.", + "properties": { + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "Description", + "type": "string" + }, + "domain": { + "description": "Domain", + "type": "string" + }, + "id": { + "description": "Organization ID (org_...)", + "type": "string" + }, + "industry": { + "description": "Industry", + "type": "string" + }, + "name": { + "description": "Organization name", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "slug": { + "description": "URL slug", + "type": "string" + }, + "status": { + "description": "Status", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "website": { + "description": "Website URL", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/users/{user}/profile": { + "put": { + "operationId": "put_api_v1_users__user_profile", + "parameters": [ + { + "description": "User ID", + "in": "path", + "name": "user", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "alias": { + "description": "Display alias", + "type": "string" + }, + "full_name": { + "description": "Full name", + "type": "string" + }, + "metadata": { + "description": "User metadata", + "type": "object" + }, + "profile_picture": { + "description": "Base64 encoded profile picture", + "properties": { + "data": { + "description": "Base64 encoded image data", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "mime_type": { + "description": "MIME type of the image", + "type": "string" + } + }, + "type": "object" + }, + "user": { + "description": "User ID", + "type": "string" + } + }, + "required": [ + "user" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "422": { + "description": "Validation error" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/api/v1/users/{user}/threads": { + "get": { + "operationId": "get_api_v1_users__user_threads", + "parameters": [ + { + "description": "User ID", + "in": "path", + "name": "user", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional: only return threads where AT LEAST ONE of the listed agent users is also a member. Used by Agent Network to show all threads the current user shares with a specific agent (backing thread, Slack DMs, channel sub-threads, etc.).", + "in": "query", + "name": "agent", + "required": false, + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Array of metadata filter objects", + "in": "query", + "name": "filter", + "required": false, + "schema": { + "items": { + "description": "Filter object for matching thread metadata.", + "properties": { + "key": { + "description": "Metadata key to match", + "type": "string" + }, + "type": { + "description": "Filter type (must be \"metadata\")", + "type": "string" + }, + "value": { + "description": "Metadata value to match", + "type": "string" + } + }, + "required": [ + "type", + "key", + "value" + ], + "type": "object" + }, + "type": "array" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "List of threads response", + "properties": { + "data": { + "description": "The threads", + "items": { + "description": "API schema for a chat thread.", + "properties": { + "agent_user": { + "description": "Owning agent user", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "creator": { + "description": "Creator user object", + "properties": { + "alias": { + "description": "User alias/handle", + "type": "string" + }, + "email": { + "description": "User email address", + "type": "string" + }, + "id": { + "description": "User ID", + "type": "string" + }, + "metadata": { + "description": "User metadata", + "type": "object" + }, + "name": { + "description": "User display name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "description": { + "description": "Thread description", + "type": "string" + }, + "id": { + "description": "Thread ID (thr_...)", + "type": "string" + }, + "is_channel": { + "description": "Whether this is a channel", + "type": "boolean" + }, + "is_default": { + "description": "Whether this is the default thread", + "type": "boolean" + }, + "is_transient": { + "description": "Whether this thread is transient", + "type": "boolean" + }, + "is_unlisted": { + "description": "Whether this thread is unlisted", + "type": "boolean" + }, + "key": { + "description": "Thread key", + "type": "string" + }, + "last_activity": { + "description": "Last activity timestamp", + "format": "date-time", + "type": "string" + }, + "metadata": { + "description": "Thread metadata", + "type": "object" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "parent_message": { + "description": "Parent message object", + "properties": { + "actors": { + "description": "Message actors", + "items": { + "description": "Schema for a message actor (user or agent).\n\nActors represent the entity that sent a message.\nMaps to the actor format from MessageActorHelper.build_actor/1.\n", + "properties": { + "alias": { + "description": "Actor alias/handle", + "type": "string" + }, + "id": { + "description": "Actor ID (format: user-xxx or agent-xxx)", + "type": "string" + }, + "name": { + "description": "Actor display name", + "type": "string" + }, + "profile_picture": { + "description": "Profile picture", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "agent": { + "description": "Agent if sent by an agent user", + "type": "string" + }, + "attachments": { + "description": "Message attachments", + "items": { + "description": "Schema for a message attachment.\n\nAttachments can be of various types (file, scraped_link, artifact, task, media, action).\nFields present depend on the attachment type.\nMaps to format_attachments_for_client/1 output.\n", + "properties": { + "content_type": { + "description": "MIME content type (file, artifact, media types)", + "type": "string" + }, + "description": { + "description": "Description (scraped_link, artifact, task types)", + "type": "string" + }, + "filename": { + "description": "File name (file, artifact, media types)", + "type": "string" + }, + "height": { + "description": "Media height (media type)", + "type": "integer" + }, + "id": { + "description": "Attachment ID", + "type": "string" + }, + "image_height": { + "description": "Preview image height (scraped_link type)", + "type": "integer" + }, + "image_source": { + "description": "Image metadata (file, scraped_link, artifact, media types)", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "image_url": { + "description": "Preview image URL (scraped_link type)", + "type": "string" + }, + "image_width": { + "description": "Preview image width (scraped_link type)", + "type": "integer" + }, + "media_type": { + "description": "Media type (media type)", + "type": "string" + }, + "name": { + "description": "Media name (media type)", + "type": "string" + }, + "object": { + "description": "Embedded object (task, action types)", + "type": "object" + }, + "title": { + "description": "Title (scraped_link, artifact, task types)", + "type": "string" + }, + "type": { + "description": "Attachment type: file, scraped_link, artifact, task, media, action", + "type": "string" + }, + "url": { + "description": "URL to the resource (file, scraped_link, artifact, media types)", + "type": "string" + }, + "variants": { + "description": "Media variants (media type)", + "items": { + "description": "API schema for a media variant.", + "properties": { + "content_type": { + "description": "File content type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "file": { + "description": "Storage file", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "height": { + "description": "Height in pixels", + "type": "integer" + }, + "id": { + "description": "Variant ID", + "type": "string" + }, + "image_source": { + "description": "Image source metadata", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "url": { + "description": "Signed download URL", + "type": "string" + }, + "variant_key": { + "description": "Variant key (original, thumbnail, etc)", + "type": "string" + }, + "width": { + "description": "Width in pixels", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + }, + "version": { + "description": "Artifact version number (artifact type)", + "type": "integer" + }, + "width": { + "description": "Media width (media type)", + "type": "integer" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + "type": "array" + }, + "branched_thread": { + "description": "Branched thread (if message spawned a thread)", + "type": "string" + }, + "content": { + "description": "Message content", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "has_replies": { + "description": "Whether message has replies", + "type": "boolean" + }, + "id": { + "description": "Message ID (msg_...)", + "type": "string" + }, + "idempotency_key": { + "description": "Client-provided idempotency key", + "type": "string" + }, + "legacy_agent": { + "description": "Legacy agent if sent by legacy chat agent", + "type": "string" + }, + "metadata": { + "description": "Message metadata", + "type": "object" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "reactions": { + "description": "Message reactions", + "items": { + "description": "Schema for inline message reactions.\n\nThis is the compact format used in Message.reactions[], which differs from\nthe full Reaction schema used in standalone reaction endpoints.\nMaps to format_reactions_for_client/1 output.\n", + "properties": { + "payload": { + "description": "Reaction payload (e.g., {emoji: '👍'})", + "type": "object" + }, + "type": { + "description": "Reaction type (e.g., emoji_reaction)", + "type": "string" + }, + "user": { + "description": "User who added the reaction", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "type": "array" + }, + "rendering_mode": { + "description": "Rendering mode hint", + "type": "string" + }, + "replies": { + "description": "Inline replies (if loaded)", + "items": { + "type": "object" + }, + "type": "array" + }, + "replies_after_cursor": { + "description": "Cursor for replies pagination", + "type": "string" + }, + "replies_before_cursor": { + "description": "Cursor for replies pagination", + "type": "string" + }, + "reply_count": { + "description": "Number of replies", + "type": "integer" + }, + "reply_to": { + "description": "Parent message object (if loaded)", + "type": "object" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "thread": { + "description": "Parent thread", + "type": "string" + }, + "user": { + "description": "Author user (public ID or expanded object when loaded)", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "participant": { + "description": "Participant users", + "items": { + "type": "string" + }, + "type": "array" + }, + "participants": { + "description": "Participant user objects", + "items": { + "description": "API schema for a user.", + "properties": { + "alias": { + "description": "User alias/handle", + "type": "string" + }, + "email": { + "description": "User email address", + "type": "string" + }, + "id": { + "description": "User ID", + "type": "string" + }, + "metadata": { + "description": "User metadata", + "type": "object" + }, + "name": { + "description": "User display name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + }, + "participating_actor": { + "description": "Actors participating in thread", + "items": { + "type": "string" + }, + "type": "array" + }, + "participating_agents": { + "description": "Agents participating in thread", + "items": { + "description": "API schema for an agent.", + "properties": { + "acl": { + "description": "Access control list. Contains grants array with principal_type, principal, and actions. Null when no ACL restrictions are applied.", + "properties": { + "add": { + "description": "Patch mode: grants to add or merge into existing", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "grants": { + "description": "Replace mode: full list of grants (replaces all existing). Use [] to clear.", + "items": { + "description": "API schema for a single ACL grant entry.", + "properties": { + "actions": { + "description": "List of allowed actions (e.g. read, write)", + "items": { + "type": "string" + }, + "type": "array" + }, + "principal": { + "description": "Principal identifier (UUID for user/team/org/agent_user, role name for org_role, omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type: user, team, org, org_role, agent, or everyone", + "type": "string" + } + }, + "required": [ + "principal_type", + "actions" + ], + "type": "object" + }, + "type": "array" + }, + "remove": { + "description": "Patch mode: principals to remove from existing", + "items": { + "description": "API schema for identifying a principal to remove from an ACL.", + "properties": { + "principal": { + "description": "Principal identifier to remove (omit for everyone)", + "type": "string" + }, + "principal_type": { + "description": "Principal type to remove", + "type": "string" + } + }, + "required": [ + "principal_type" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "app": { + "description": "Application", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "default_model": { + "description": "Default AI model", + "type": "string" + }, + "email": { + "description": "Agent email", + "type": "string" + }, + "id": { + "description": "Agent ID (agi_...)", + "type": "string" + }, + "identity": { + "description": "Identity prompt", + "type": "string" + }, + "lookup_key": { + "description": "Unique lookup key", + "type": "string" + }, + "metadata": { + "description": "Arbitrary metadata", + "type": "object" + }, + "name": { + "description": "Agent name", + "type": "string" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "phone_number": { + "description": "Agent phone number", + "type": "string" + }, + "sandbox": { + "description": "Sandbox", + "type": "string" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + }, + "role": { + "description": "User's role in the thread", + "type": "string" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "settings": { + "description": "Thread settings", + "properties": { + "agent_enabled": { + "description": "Whether the agent is enabled for this thread", + "type": "boolean" + } + }, + "type": "object" + }, + "slug": { + "description": "Thread slug", + "type": "string" + }, + "sub_threads": { + "description": "Sub-threads", + "items": { + "type": "object" + }, + "type": "array" + }, + "team": { + "description": "Owning team", + "type": "string" + }, + "title": { + "description": "Thread title", + "type": "string" + }, + "ttl": { + "description": "Time-to-live in seconds", + "type": "integer" + }, + "unread_count": { + "description": "Unread message count", + "type": "integer" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "user": { + "description": "Owning user", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + }, + "post": { + "operationId": "post_api_v1_users__user_threads", + "parameters": [ + { + "description": "User ID", + "in": "path", + "name": "user", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "skip_welcome_message": { + "description": "Skip automatic welcome message", + "type": "boolean" + }, + "thread": { + "description": "Thread attributes", + "properties": { + "create_legacy_agent": { + "description": "Create a legacy chat agent for this thread", + "type": "boolean" + }, + "description": { + "description": "Thread description", + "type": "string" + }, + "is_unlisted": { + "description": "Whether the thread is unlisted", + "type": "boolean" + }, + "key": { + "description": "Unique key for the thread", + "type": "string" + }, + "metadata": { + "description": "Additional metadata", + "type": "object" + }, + "org_id": { + "description": "Organization ID", + "type": "string" + }, + "profile_picture": { + "description": "Base64 encoded profile picture", + "properties": { + "data": { + "description": "Base64 encoded image data", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "mime_type": { + "description": "MIME type of the image", + "type": "string" + } + }, + "type": "object" + }, + "settings": { + "description": "Thread settings", + "properties": { + "agent_enabled": { + "description": "Whether the agent is enabled for this thread", + "type": "boolean" + } + }, + "type": "object" + }, + "title": { + "description": "Thread title", + "type": "string" + } + }, + "type": "object" + }, + "user": { + "description": "User ID", + "type": "string" + } + }, + "required": [ + "user", + "thread" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Thread" + } + } + }, + "description": "Successful response" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "422": { + "description": "Validation failed" + } + }, + "x-auth": [ + "publishable_key", + "bearer" + ] + } + }, + "/oauth/device/approve": { + "post": { + "operationId": "post_oauth_device_approve", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "thread": { + "description": "Thread ID to bind to the authorization", + "type": "string" + }, + "user_code": { + "description": "Device authorization user code", + "type": "string" + } + }, + "required": [ + "user_code" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceAuthorizationStatusResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + } + } + } + }, + "/oauth/device/authorize": { + "post": { + "operationId": "post_oauth_device_authorize", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "client": { + "description": "OAuth client ID", + "type": "string" + }, + "scope": { + "description": "Space-separated list of requested scopes", + "type": "string" + } + }, + "required": [ + "client" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceAuthorizationResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request" + }, + "403": { + "description": "Forbidden" + }, + "429": { + "description": "Rate limited" + } + } + } + }, + "/oauth/device/deny": { + "post": { + "operationId": "post_oauth_device_deny", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "user_code": { + "description": "Device authorization user code", + "type": "string" + } + }, + "required": [ + "user_code" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceAuthorizationStatusResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + } + } + } + }, + "/oauth/scopes": { + "get": { + "operationId": "get_oauth_scopes", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "OAuth scope definitions", + "properties": { + "scopes": { + "description": "Map of scope name to scope definition", + "type": "object" + } + }, + "required": [ + "scopes" + ], + "type": "object" + } + } + }, + "description": "Successful response" + } + } + } + }, + "/oauth/token": { + "post": { + "operationId": "post_oauth_token", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "client": { + "description": "OAuth client ID", + "type": "string" + }, + "code": { + "description": "Authorization code (for authorization_code grant)", + "type": "string" + }, + "code_verifier": { + "description": "PKCE code verifier (for authorization_code grant)", + "type": "string" + }, + "device_code": { + "description": "Device code (for device_code grant)", + "type": "string" + }, + "grant_type": { + "description": "OAuth grant type", + "type": "string" + }, + "redirect_uri": { + "description": "Redirect URI (for authorization_code grant)", + "type": "string" + }, + "refresh_token": { + "description": "Refresh token (for refresh_token grant)", + "type": "string" + } + }, + "required": [ + "grant_type" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthTokenResponse" + } + } + }, + "description": "Successful response" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "429": { + "description": "Rate limited" + } + } + } + } + }, + "x-auth-schemes": { + "bearer": { + "description": "User JWT in Authorization header", + "scheme": "bearer", + "type": "http" + }, + "device_flow": { + "description": "Third-party device flow token — requires per-action opt-in", + "scheme": "bearer", + "type": "http", + "x-token-use": "third_party" + }, + "publishable_key": { + "description": "Publishable API key — identifies the app", + "in": "header", + "name": "x-archastro-api-key", + "prefix": "pk_", + "type": "api_key" + }, + "secret_key": { + "description": "Secret API key — full admin access, no user JWT required", + "in": "header", + "name": "x-archastro-api-key", + "prefix": "sk_", + "type": "api_key" + } + }, + "x-channel-auth": [ + "bearer" + ], + "x-channels": [ + { + "description": "Phoenix channel for real-time activity feed updates.\n\nClients join a topic scoped to an agent or org and receive\n`new_entry` events as feed entries are created.\n\n## Topics\n\n * `\"api:activity_feed:agent:{agent_user_id}\"` — entries for a specific agent\n * `\"api:activity_feed:org:{org_id}\"` — entries for an entire org/tenant\n", + "joins": [ + { + "description": "Join an agent-scoped activity feed", + "name": "join_agent", + "params": { + "properties": { + "agent_id": { + "type": "string" + } + }, + "required": [ + "agent_id" + ], + "type": "object" + }, + "pattern": "api:activity_feed:agent:{agent_id}", + "returns": { + "type": "object" + } + }, + { + "description": "Join an org-scoped activity feed", + "name": "join_org", + "params": { + "properties": { + "org_id": { + "type": "string" + } + }, + "required": [ + "org_id" + ], + "type": "object" + }, + "pattern": "api:activity_feed:org:{org_id}", + "returns": { + "type": "object" + } + } + ], + "messages": [ + { + "description": "List activity feed entries with cursor-based pagination", + "event": "list_entries", + "params": { + "properties": { + "after_cursor": { + "type": "string" + }, + "before_cursor": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "level": { + "type": "string" + }, + "limit": { + "type": "integer" + } + }, + "type": "object" + }, + "returns": { + "type": "object" + } + } + ], + "name": "ApiActivityFeedChannel", + "pushes": [ + { + "description": null, + "event": "new_entry", + "payload": { + "properties": { + "entry": { + "type": "object" + } + }, + "type": "object" + } + } + ], + "x-auth": [ + "bearer" + ] + }, + { + "description": "Channel for real-time chat messaging.\n\nSupports team-scoped and user-scoped threads with keyed, transient, and direct\nthread access patterns.\n", + "joins": [ + { + "description": "Join a team-scoped thread by ID", + "name": "join_team_thread", + "params": { + "properties": { + "after_cursor": { + "type": "string" + }, + "before_cursor": { + "type": "string" + }, + "include_metadata": { + "type": "boolean" + }, + "limit": { + "type": "integer" + }, + "team_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + } + }, + "required": [ + "team_id", + "thread_id" + ], + "type": "object" + }, + "pattern": "api:chat:team:{team_id}:thread:{thread_id}", + "returns": { + "type": "object" + } + }, + { + "description": "Join or create a team-scoped keyed thread", + "name": "join_team_keyed", + "params": { + "properties": { + "after_cursor": { + "type": "string" + }, + "before_cursor": { + "type": "string" + }, + "include_metadata": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "limit": { + "type": "integer" + }, + "team_id": { + "type": "string" + } + }, + "required": [ + "team_id", + "key" + ], + "type": "object" + }, + "pattern": "api:chat:team:{team_id}:key:{key}", + "returns": { + "type": "object" + } + }, + { + "description": "Join a team-scoped transient (ephemeral) thread", + "name": "join_team_transient", + "params": { + "properties": { + "after_cursor": { + "type": "string" + }, + "before_cursor": { + "type": "string" + }, + "include_metadata": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "limit": { + "type": "integer" + }, + "team_id": { + "type": "string" + } + }, + "required": [ + "team_id", + "key" + ], + "type": "object" + }, + "pattern": "api:chat:team:{team_id}:transient:{key}", + "returns": { + "type": "object" + } + }, + { + "description": "Join a user-scoped thread by ID", + "name": "join_user_thread", + "params": { + "properties": { + "after_cursor": { + "type": "string" + }, + "before_cursor": { + "type": "string" + }, + "include_metadata": { + "type": "boolean" + }, + "limit": { + "type": "integer" + }, + "thread_id": { + "type": "string" + } + }, + "required": [ + "thread_id" + ], + "type": "object" + }, + "pattern": "api:chat:user:thread:{thread_id}", + "returns": { + "type": "object" + } + }, + { + "description": "Join or create a user-scoped keyed thread", + "name": "join_user_keyed", + "params": { + "properties": { + "after_cursor": { + "type": "string" + }, + "before_cursor": { + "type": "string" + }, + "include_metadata": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "limit": { + "type": "integer" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "pattern": "api:chat:user:key:{key}", + "returns": { + "type": "object" + } + }, + { + "description": "Join a user-scoped transient (ephemeral) thread", + "name": "join_user_transient", + "params": { + "properties": { + "after_cursor": { + "type": "string" + }, + "before_cursor": { + "type": "string" + }, + "include_metadata": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "limit": { + "type": "integer" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "pattern": "api:chat:user:transient:{key}", + "returns": { + "type": "object" + } + } + ], + "messages": [ + { + "description": "Fork a sub-thread from an existing message", + "event": "api:chat:fork_thread", + "params": { + "properties": { + "message_id": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "required": [ + "message_id" + ], + "type": "object" + }, + "returns": { + "type": "object" + } + }, + { + "description": "Mark a thread as read up to a given message", + "event": "api:chat:mark_thread_read", + "params": { + "properties": { + "message_id": { + "type": "string" + } + }, + "required": [ + "message_id" + ], + "type": "object" + }, + "returns": { + "type": "object" + } + }, + { + "description": "List all messages in the current thread", + "event": "api:chat:list_messages", + "params": { + "properties": {}, + "type": "object" + }, + "returns": { + "type": "object" + } + }, + { + "description": "Load additional messages with cursor-based pagination", + "event": "api:chat:load_more_messages", + "params": { + "properties": { + "after_cursor": { + "type": "string" + }, + "before_cursor": { + "type": "string" + }, + "include_metadata": { + "type": "boolean" + }, + "limit": { + "type": "integer" + } + }, + "type": "object" + }, + "returns": { + "type": "object" + } + }, + { + "description": "Post a new message with optional uploads and reply-to", + "event": "api:chat:post_message", + "params": { + "properties": { + "content": { + "type": "string" + }, + "idempotency_key": { + "type": "string" + }, + "reply_to": { + "type": "string" + }, + "uploads": { + "items": { + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "returns": { + "type": "object" + } + }, + { + "description": "Post a simple text message", + "event": "api:chat:post_simple_message", + "params": { + "properties": { + "content": { + "type": "string" + }, + "idempotency_key": { + "type": "string" + }, + "reply_to": { + "type": "string" + } + }, + "type": "object" + }, + "returns": { + "type": "object" + } + }, + { + "description": "Edit an existing message's content", + "event": "api:chat:edit_message", + "params": { + "properties": { + "content": { + "type": "string" + }, + "message_id": { + "type": "string" + } + }, + "required": [ + "message_id", + "content" + ], + "type": "object" + }, + "returns": { + "type": "object" + } + }, + { + "description": "Delete a message", + "event": "api:chat:delete_message", + "params": { + "properties": { + "message_id": { + "type": "string" + } + }, + "required": [ + "message_id" + ], + "type": "object" + }, + "returns": { + "type": "object" + } + }, + { + "description": "Add an emoji reaction to a message", + "event": "api:chat:add_reaction", + "params": { + "properties": { + "emoji": { + "type": "string" + }, + "message_id": { + "type": "string" + } + }, + "required": [ + "message_id", + "emoji" + ], + "type": "object" + }, + "returns": { + "type": "object" + } + }, + { + "description": "Remove an emoji reaction from a message", + "event": "api:chat:remove_reaction", + "params": { + "properties": { + "emoji": { + "type": "string" + }, + "message_id": { + "type": "string" + } + }, + "required": [ + "message_id", + "emoji" + ], + "type": "object" + }, + "returns": { + "type": "object" + } + } + ], + "name": "ApiChatChannel", + "pushes": [ + { + "description": "Broadcast when a new message is added to a thread", + "event": "message_added", + "payload": { + "properties": { + "after_cursor": { + "type": "string" + }, + "before_cursor": { + "type": "string" + }, + "message": { + "description": "API schema for a chat message.", + "properties": { + "actors": { + "description": "Message actors", + "items": { + "description": "Schema for a message actor (user or agent).\n\nActors represent the entity that sent a message.\nMaps to the actor format from MessageActorHelper.build_actor/1.\n", + "properties": { + "alias": { + "description": "Actor alias/handle", + "type": "string" + }, + "id": { + "description": "Actor ID (format: user-xxx or agent-xxx)", + "type": "string" + }, + "name": { + "description": "Actor display name", + "type": "string" + }, + "profile_picture": { + "description": "Profile picture", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "agent": { + "description": "Agent if sent by an agent user", + "type": "string" + }, + "attachments": { + "description": "Message attachments", + "items": { + "description": "Schema for a message attachment.\n\nAttachments can be of various types (file, scraped_link, artifact, task, media, action).\nFields present depend on the attachment type.\nMaps to format_attachments_for_client/1 output.\n", + "properties": { + "content_type": { + "description": "MIME content type (file, artifact, media types)", + "type": "string" + }, + "description": { + "description": "Description (scraped_link, artifact, task types)", + "type": "string" + }, + "filename": { + "description": "File name (file, artifact, media types)", + "type": "string" + }, + "height": { + "description": "Media height (media type)", + "type": "integer" + }, + "id": { + "description": "Attachment ID", + "type": "string" + }, + "image_height": { + "description": "Preview image height (scraped_link type)", + "type": "integer" + }, + "image_source": { + "description": "Image metadata (file, scraped_link, artifact, media types)", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "image_url": { + "description": "Preview image URL (scraped_link type)", + "type": "string" + }, + "image_width": { + "description": "Preview image width (scraped_link type)", + "type": "integer" + }, + "media_type": { + "description": "Media type (media type)", + "type": "string" + }, + "name": { + "description": "Media name (media type)", + "type": "string" + }, + "object": { + "description": "Embedded object (task, action types)", + "type": "object" + }, + "title": { + "description": "Title (scraped_link, artifact, task types)", + "type": "string" + }, + "type": { + "description": "Attachment type: file, scraped_link, artifact, task, media, action", + "type": "string" + }, + "url": { + "description": "URL to the resource (file, scraped_link, artifact, media types)", + "type": "string" + }, + "variants": { + "description": "Media variants (media type)", + "items": { + "description": "API schema for a media variant.", + "properties": { + "content_type": { + "description": "File content type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "file": { + "description": "Storage file", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "height": { + "description": "Height in pixels", + "type": "integer" + }, + "id": { + "description": "Variant ID", + "type": "string" + }, + "image_source": { + "description": "Image source metadata", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "url": { + "description": "Signed download URL", + "type": "string" + }, + "variant_key": { + "description": "Variant key (original, thumbnail, etc)", + "type": "string" + }, + "width": { + "description": "Width in pixels", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + }, + "version": { + "description": "Artifact version number (artifact type)", + "type": "integer" + }, + "width": { + "description": "Media width (media type)", + "type": "integer" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + "type": "array" + }, + "branched_thread": { + "description": "Branched thread (if message spawned a thread)", + "type": "string" + }, + "content": { + "description": "Message content", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "has_replies": { + "description": "Whether message has replies", + "type": "boolean" + }, + "id": { + "description": "Message ID (msg_...)", + "type": "string" + }, + "idempotency_key": { + "description": "Client-provided idempotency key", + "type": "string" + }, + "legacy_agent": { + "description": "Legacy agent if sent by legacy chat agent", + "type": "string" + }, + "metadata": { + "description": "Message metadata", + "type": "object" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "reactions": { + "description": "Message reactions", + "items": { + "description": "Schema for inline message reactions.\n\nThis is the compact format used in Message.reactions[], which differs from\nthe full Reaction schema used in standalone reaction endpoints.\nMaps to format_reactions_for_client/1 output.\n", + "properties": { + "payload": { + "description": "Reaction payload (e.g., {emoji: '👍'})", + "type": "object" + }, + "type": { + "description": "Reaction type (e.g., emoji_reaction)", + "type": "string" + }, + "user": { + "description": "User who added the reaction", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "type": "array" + }, + "rendering_mode": { + "description": "Rendering mode hint", + "type": "string" + }, + "replies": { + "description": "Inline replies (if loaded)", + "items": { + "type": "object" + }, + "type": "array" + }, + "replies_after_cursor": { + "description": "Cursor for replies pagination", + "type": "string" + }, + "replies_before_cursor": { + "description": "Cursor for replies pagination", + "type": "string" + }, + "reply_count": { + "description": "Number of replies", + "type": "integer" + }, + "reply_to": { + "description": "Parent message object (if loaded)", + "type": "object" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "thread": { + "description": "Parent thread", + "type": "string" + }, + "user": { + "description": "Author user (public ID or expanded object when loaded)", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "thread_id": { + "type": "string" + } + }, + "type": "object" + } + }, + { + "description": "Broadcast when a message is updated or removed", + "event": "message_updated", + "payload": { + "properties": { + "message": { + "description": "API schema for a chat message.", + "properties": { + "actors": { + "description": "Message actors", + "items": { + "description": "Schema for a message actor (user or agent).\n\nActors represent the entity that sent a message.\nMaps to the actor format from MessageActorHelper.build_actor/1.\n", + "properties": { + "alias": { + "description": "Actor alias/handle", + "type": "string" + }, + "id": { + "description": "Actor ID (format: user-xxx or agent-xxx)", + "type": "string" + }, + "name": { + "description": "Actor display name", + "type": "string" + }, + "profile_picture": { + "description": "Profile picture", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "agent": { + "description": "Agent if sent by an agent user", + "type": "string" + }, + "attachments": { + "description": "Message attachments", + "items": { + "description": "Schema for a message attachment.\n\nAttachments can be of various types (file, scraped_link, artifact, task, media, action).\nFields present depend on the attachment type.\nMaps to format_attachments_for_client/1 output.\n", + "properties": { + "content_type": { + "description": "MIME content type (file, artifact, media types)", + "type": "string" + }, + "description": { + "description": "Description (scraped_link, artifact, task types)", + "type": "string" + }, + "filename": { + "description": "File name (file, artifact, media types)", + "type": "string" + }, + "height": { + "description": "Media height (media type)", + "type": "integer" + }, + "id": { + "description": "Attachment ID", + "type": "string" + }, + "image_height": { + "description": "Preview image height (scraped_link type)", + "type": "integer" + }, + "image_source": { + "description": "Image metadata (file, scraped_link, artifact, media types)", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "image_url": { + "description": "Preview image URL (scraped_link type)", + "type": "string" + }, + "image_width": { + "description": "Preview image width (scraped_link type)", + "type": "integer" + }, + "media_type": { + "description": "Media type (media type)", + "type": "string" + }, + "name": { + "description": "Media name (media type)", + "type": "string" + }, + "object": { + "description": "Embedded object (task, action types)", + "type": "object" + }, + "title": { + "description": "Title (scraped_link, artifact, task types)", + "type": "string" + }, + "type": { + "description": "Attachment type: file, scraped_link, artifact, task, media, action", + "type": "string" + }, + "url": { + "description": "URL to the resource (file, scraped_link, artifact, media types)", + "type": "string" + }, + "variants": { + "description": "Media variants (media type)", + "items": { + "description": "API schema for a media variant.", + "properties": { + "content_type": { + "description": "File content type", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "file": { + "description": "Storage file", + "type": "string" + }, + "filename": { + "description": "Original filename", + "type": "string" + }, + "height": { + "description": "Height in pixels", + "type": "integer" + }, + "id": { + "description": "Variant ID", + "type": "string" + }, + "image_source": { + "description": "Image source metadata", + "properties": { + "file": { + "description": "Storage file", + "type": "string" + }, + "height": { + "description": "Image height in pixels", + "type": "integer" + }, + "media": { + "description": "Media", + "type": "string" + }, + "mime_type": { + "description": "Image MIME type", + "type": "string" + }, + "refresh_url": { + "description": "URL to refresh signed URL", + "type": "string" + }, + "url": { + "description": "Image URL", + "type": "string" + }, + "width": { + "description": "Image width in pixels", + "type": "integer" + } + }, + "type": "object" + }, + "updated_at": { + "description": "Last update timestamp", + "format": "date-time", + "type": "string" + }, + "url": { + "description": "Signed download URL", + "type": "string" + }, + "variant_key": { + "description": "Variant key (original, thumbnail, etc)", + "type": "string" + }, + "width": { + "description": "Width in pixels", + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + }, + "version": { + "description": "Artifact version number (artifact type)", + "type": "integer" + }, + "width": { + "description": "Media width (media type)", + "type": "integer" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + "type": "array" + }, + "branched_thread": { + "description": "Branched thread (if message spawned a thread)", + "type": "string" + }, + "content": { + "description": "Message content", + "type": "string" + }, + "created_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "has_replies": { + "description": "Whether message has replies", + "type": "boolean" + }, + "id": { + "description": "Message ID (msg_...)", + "type": "string" + }, + "idempotency_key": { + "description": "Client-provided idempotency key", + "type": "string" + }, + "legacy_agent": { + "description": "Legacy agent if sent by legacy chat agent", + "type": "string" + }, + "metadata": { + "description": "Message metadata", + "type": "object" + }, + "org": { + "description": "Organization", + "type": "string" + }, + "reactions": { + "description": "Message reactions", + "items": { + "description": "Schema for inline message reactions.\n\nThis is the compact format used in Message.reactions[], which differs from\nthe full Reaction schema used in standalone reaction endpoints.\nMaps to format_reactions_for_client/1 output.\n", + "properties": { + "payload": { + "description": "Reaction payload (e.g., {emoji: '👍'})", + "type": "object" + }, + "type": { + "description": "Reaction type (e.g., emoji_reaction)", + "type": "string" + }, + "user": { + "description": "User who added the reaction", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "type": "array" + }, + "rendering_mode": { + "description": "Rendering mode hint", + "type": "string" + }, + "replies": { + "description": "Inline replies (if loaded)", + "items": { + "type": "object" + }, + "type": "array" + }, + "replies_after_cursor": { + "description": "Cursor for replies pagination", + "type": "string" + }, + "replies_before_cursor": { + "description": "Cursor for replies pagination", + "type": "string" + }, + "reply_count": { + "description": "Number of replies", + "type": "integer" + }, + "reply_to": { + "description": "Parent message object (if loaded)", + "type": "object" + }, + "sandbox": { + "description": "Sandbox identifier", + "type": "string" + }, + "team": { + "description": "Team", + "type": "string" + }, + "thread": { + "description": "Parent thread", + "type": "string" + }, + "user": { + "description": "Author user (public ID or expanded object when loaded)", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "thread_id": { + "type": "string" + } + }, + "type": "object" + } + }, + { + "description": "Broadcast thread-level events (agent updates, read state, unread counts)", + "event": "thread_event", + "payload": { + "properties": { + "payload": { + "type": "object" + }, + "thread_id": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object" + } + }, + { + "description": "Broadcast system-wide events", + "event": "system_event", + "payload": { + "properties": { + "event": { + "type": "object" + } + }, + "type": "object" + } + } + ], + "x-auth": [ + "bearer" + ] + }, + { + "description": "Channel for real-time custom object collaboration.\n\nClients join `api:object:{object_id}` to receive the current object state\nand subscribe to field-level updates. Mutations are sent as key:value maps.\n", + "joins": [ + { + "description": null, + "name": "join_by_id", + "params": { + "properties": { + "object_id": { + "type": "string" + } + }, + "required": [ + "object_id" + ], + "type": "object" + }, + "pattern": "api:object:{object_id}", + "returns": { + "type": "object" + } + }, + { + "description": null, + "name": "join_by_row_key", + "params": { + "properties": { + "row_key": { + "type": "string" + }, + "schema_type": { + "type": "string" + } + }, + "required": [ + "schema_type", + "row_key" + ], + "type": "object" + }, + "pattern": "api:object:{schema_type}:{row_key}", + "returns": { + "type": "object" + } + } + ], + "messages": [ + { + "description": null, + "event": "update_fields", + "params": { + "properties": { + "fields": { + "type": "object" + } + }, + "required": [ + "fields" + ], + "type": "object" + }, + "returns": { + "type": "object" + } + }, + { + "description": null, + "event": "save", + "params": { + "properties": {}, + "type": "object" + }, + "returns": { + "type": "object" + } + } + ], + "name": "ApiObjectChannel", + "pushes": [ + { + "description": null, + "event": "object_updated", + "payload": { + "properties": { + "fields": { + "type": "object" + }, + "id": { + "type": "string" + } + }, + "type": "object" + } + }, + { + "description": null, + "event": "object_created", + "payload": { + "properties": { + "fields": { + "type": "object" + }, + "id": { + "type": "string" + } + }, + "type": "object" + } + } + ], + "x-auth": [ + "bearer" + ] + } + ], + "x-token-flows": { + "login": { + "constructor": "with_credentials", + "description": "Create a client by logging in with email/password", + "operation_name": "login", + "operation_tag": "auth", + "requires": [ + "publishable_key" + ] + }, + "refresh": { + "description": "Refresh an expired access token", + "operation_name": "refresh", + "operation_tag": "auth" + } + } +} \ No newline at end of file diff --git a/src/archastro/__init__.py b/src/archastro/__init__.py new file mode 100644 index 0000000..669267d --- /dev/null +++ b/src/archastro/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: e3b0c44298fc + diff --git a/src/archastro/platform/__init__.py b/src/archastro/platform/__init__.py new file mode 100644 index 0000000..277f121 --- /dev/null +++ b/src/archastro/platform/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 2f93d08a58dd + +from importlib.metadata import version as _pkg_version + +from .auth import AuthClient, AuthTokens # noqa: F401 +from .client import PlatformClient # noqa: F401 +from .v1 import V1 # noqa: F401 + +__version__ = _pkg_version("archastro-platform-sdk") diff --git a/src/archastro/platform/auth.py b/src/archastro/platform/auth.py new file mode 100644 index 0000000..5178e81 --- /dev/null +++ b/src/archastro/platform/auth.py @@ -0,0 +1,105 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: c6bacc8fe3b7 + +from __future__ import annotations + +from dataclasses import dataclass + +from .runtime.http_client import HttpClient + + +@dataclass +class AuthTokens: + token_expiry: int | None = None + refresh_token: str | None = None + access_token: str | None = None + + +class AuthClient: + def __init__(self, http: HttpClient): + self._http = http + + async def login(self, email: str, password: str) -> AuthTokens: + body: dict[str, object] = {} + body["email"] = email + body["password"] = password + + data = await self._http.request( + "/api/v1/auth/login", + method="POST", + body=body, + ) + return AuthTokens( + token_expiry=data.get("expires_in"), + refresh_token=data.get("refresh_token"), + access_token=data.get("token"), + ) + + async def refresh(self, refresh_token: str) -> AuthTokens: + body: dict[str, object] = {} + body["refresh_token"] = refresh_token + + data = await self._http.request( + "/api/v1/auth/refresh", + method="POST", + body=body, + ) + return AuthTokens( + token_expiry=data.get("expires_in"), + refresh_token=data.get("refresh_token"), + access_token=data.get("token"), + ) + + async def register( + self, + email: str, + alias: str | None = None, + full_name: str | None = None, + invite_code: str | None = None, + password: str | None = None, + team_invite: str | None = None, + timezone: str | None = None, + ) -> AuthTokens: + body: dict[str, object] = {} + body["email"] = email + if alias is not None: + body["alias"] = alias + if full_name is not None: + body["full_name"] = full_name + if invite_code is not None: + body["invite_code"] = invite_code + if password is not None: + body["password"] = password + if team_invite is not None: + body["team_invite"] = team_invite + if timezone is not None: + body["timezone"] = timezone + + data = await self._http.request( + "/api/v1/auth/register", + method="POST", + body=body, + ) + return AuthTokens( + token_expiry=data.get("expires_in"), + refresh_token=data.get("refresh_token"), + access_token=data.get("token"), + ) + + async def exchange_login_token(self, token: str, timezone: str | None = None) -> AuthTokens: + body: dict[str, object] = {} + body["token"] = token + if timezone is not None: + body["timezone"] = timezone + + data = await self._http.request( + "/api/v1/auth/token", + method="POST", + body=body, + ) + return AuthTokens( + token_expiry=data.get("expires_in"), + refresh_token=data.get("refresh_token"), + access_token=data.get("token"), + ) diff --git a/src/archastro/platform/channels/__init__.py b/src/archastro/platform/channels/__init__.py new file mode 100644 index 0000000..d902108 --- /dev/null +++ b/src/archastro/platform/channels/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: faf7321571f8 + +from .api_activity_feed_channel import ApiActivityFeedChannel # noqa: F401 +from .api_chat_channel import ApiChatChannel # noqa: F401 +from .api_object_channel import ApiObjectChannel # noqa: F401 diff --git a/src/archastro/platform/channels/api_activity_feed_channel.py b/src/archastro/platform/channels/api_activity_feed_channel.py new file mode 100644 index 0000000..5bdf83b --- /dev/null +++ b/src/archastro/platform/channels/api_activity_feed_channel.py @@ -0,0 +1,57 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 38296a1885db + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from phx_channel.socket import Socket + + +# Phoenix channel for real-time activity feed updates. +# Clients join a topic scoped to an agent or org and receive +# `new_entry` events as feed entries are created. +# ## Topics +# * `"api:activity_feed:agent:{agent_user_id}"` entries for a specific agent +# * `"api:activity_feed:org:{org_id}"` entries for an entire org/tenant +class ApiActivityFeedChannel: + def __init__(self, channel, join_response=None): + self._channel = channel + self.join_response = join_response + + # Join an agent-scoped activity feed + @staticmethod + def topic_agent(agent_id: str) -> str: + return f"api:activity_feed:agent:{agent_id}" + + # Join an agent-scoped activity feed + @classmethod + async def join_agent(cls, socket: "Socket", agent_id: str) -> "ApiActivityFeedChannel": + topic = cls.topic_agent(agent_id) + channel = socket.channel(topic) + join_response = await channel.join() + return cls(channel, join_response) + + # Join an org-scoped activity feed + @staticmethod + def topic_org(org_id: str) -> str: + return f"api:activity_feed:org:{org_id}" + + # Join an org-scoped activity feed + @classmethod + async def join_org(cls, socket: "Socket", org_id: str) -> "ApiActivityFeedChannel": + topic = cls.topic_org(org_id) + channel = socket.channel(topic) + join_response = await channel.join() + return cls(channel, join_response) + + # Leave the underlying channel. + async def leave(self): + await self._channel.leave() + + # List activity feed entries with cursor-based pagination + async def list_entries(self, payload: dict) -> dict: + return await self._channel.push("list_entries", payload) + + def on_new_entry(self, callback): + return self._channel.on("new_entry", callback) diff --git a/src/archastro/platform/channels/api_chat_channel.py b/src/archastro/platform/channels/api_chat_channel.py new file mode 100644 index 0000000..a77186a --- /dev/null +++ b/src/archastro/platform/channels/api_chat_channel.py @@ -0,0 +1,266 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 92ff6b6531d7 + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from phx_channel.socket import Socket + + +# Channel for real-time chat messaging. +# Supports team-scoped and user-scoped threads with keyed, transient, and direct +# thread access patterns. +class ApiChatChannel: + def __init__(self, channel, join_response=None): + self._channel = channel + self.join_response = join_response + + # Join a team-scoped thread by ID + @staticmethod + def topic_team_thread(team_id: str, thread_id: str) -> str: + return f"api:chat:team:{team_id}:thread:{thread_id}" + + # Join a team-scoped thread by ID + @classmethod + async def join_team_thread( + cls, + socket: "Socket", + team_id: str, + thread_id: str, + *, + after_cursor: str | None = None, + before_cursor: str | None = None, + include_metadata: bool | None = None, + limit: int | None = None, + ) -> "ApiChatChannel": + topic = cls.topic_team_thread(team_id, thread_id) + channel = socket.channel(topic) + payload: dict[str, object] = {} + if after_cursor is not None: + payload["after_cursor"] = after_cursor + if before_cursor is not None: + payload["before_cursor"] = before_cursor + if include_metadata is not None: + payload["include_metadata"] = include_metadata + if limit is not None: + payload["limit"] = limit + join_response = await channel.join(payload) + return cls(channel, join_response) + + # Join or create a team-scoped keyed thread + @staticmethod + def topic_team_keyed(team_id: str, key: str) -> str: + return f"api:chat:team:{team_id}:key:{key}" + + # Join or create a team-scoped keyed thread + @classmethod + async def join_team_keyed( + cls, + socket: "Socket", + team_id: str, + key: str, + *, + after_cursor: str | None = None, + before_cursor: str | None = None, + include_metadata: bool | None = None, + limit: int | None = None, + ) -> "ApiChatChannel": + topic = cls.topic_team_keyed(team_id, key) + channel = socket.channel(topic) + payload: dict[str, object] = {} + if after_cursor is not None: + payload["after_cursor"] = after_cursor + if before_cursor is not None: + payload["before_cursor"] = before_cursor + if include_metadata is not None: + payload["include_metadata"] = include_metadata + if limit is not None: + payload["limit"] = limit + join_response = await channel.join(payload) + return cls(channel, join_response) + + # Join a team-scoped transient (ephemeral) thread + @staticmethod + def topic_team_transient(team_id: str, key: str) -> str: + return f"api:chat:team:{team_id}:transient:{key}" + + # Join a team-scoped transient (ephemeral) thread + @classmethod + async def join_team_transient( + cls, + socket: "Socket", + team_id: str, + key: str, + *, + after_cursor: str | None = None, + before_cursor: str | None = None, + include_metadata: bool | None = None, + limit: int | None = None, + ) -> "ApiChatChannel": + topic = cls.topic_team_transient(team_id, key) + channel = socket.channel(topic) + payload: dict[str, object] = {} + if after_cursor is not None: + payload["after_cursor"] = after_cursor + if before_cursor is not None: + payload["before_cursor"] = before_cursor + if include_metadata is not None: + payload["include_metadata"] = include_metadata + if limit is not None: + payload["limit"] = limit + join_response = await channel.join(payload) + return cls(channel, join_response) + + # Join a user-scoped thread by ID + @staticmethod + def topic_user_thread(thread_id: str) -> str: + return f"api:chat:user:thread:{thread_id}" + + # Join a user-scoped thread by ID + @classmethod + async def join_user_thread( + cls, + socket: "Socket", + thread_id: str, + *, + after_cursor: str | None = None, + before_cursor: str | None = None, + include_metadata: bool | None = None, + limit: int | None = None, + ) -> "ApiChatChannel": + topic = cls.topic_user_thread(thread_id) + channel = socket.channel(topic) + payload: dict[str, object] = {} + if after_cursor is not None: + payload["after_cursor"] = after_cursor + if before_cursor is not None: + payload["before_cursor"] = before_cursor + if include_metadata is not None: + payload["include_metadata"] = include_metadata + if limit is not None: + payload["limit"] = limit + join_response = await channel.join(payload) + return cls(channel, join_response) + + # Join or create a user-scoped keyed thread + @staticmethod + def topic_user_keyed(key: str) -> str: + return f"api:chat:user:key:{key}" + + # Join or create a user-scoped keyed thread + @classmethod + async def join_user_keyed( + cls, + socket: "Socket", + key: str, + *, + after_cursor: str | None = None, + before_cursor: str | None = None, + include_metadata: bool | None = None, + limit: int | None = None, + ) -> "ApiChatChannel": + topic = cls.topic_user_keyed(key) + channel = socket.channel(topic) + payload: dict[str, object] = {} + if after_cursor is not None: + payload["after_cursor"] = after_cursor + if before_cursor is not None: + payload["before_cursor"] = before_cursor + if include_metadata is not None: + payload["include_metadata"] = include_metadata + if limit is not None: + payload["limit"] = limit + join_response = await channel.join(payload) + return cls(channel, join_response) + + # Join a user-scoped transient (ephemeral) thread + @staticmethod + def topic_user_transient(key: str) -> str: + return f"api:chat:user:transient:{key}" + + # Join a user-scoped transient (ephemeral) thread + @classmethod + async def join_user_transient( + cls, + socket: "Socket", + key: str, + *, + after_cursor: str | None = None, + before_cursor: str | None = None, + include_metadata: bool | None = None, + limit: int | None = None, + ) -> "ApiChatChannel": + topic = cls.topic_user_transient(key) + channel = socket.channel(topic) + payload: dict[str, object] = {} + if after_cursor is not None: + payload["after_cursor"] = after_cursor + if before_cursor is not None: + payload["before_cursor"] = before_cursor + if include_metadata is not None: + payload["include_metadata"] = include_metadata + if limit is not None: + payload["limit"] = limit + join_response = await channel.join(payload) + return cls(channel, join_response) + + # Leave the underlying channel. + async def leave(self): + await self._channel.leave() + + # Fork a sub-thread from an existing message + async def api_chat_fork_thread(self, payload: dict) -> dict: + return await self._channel.push("api:chat:fork_thread", payload) + + # Mark a thread as read up to a given message + async def api_chat_mark_thread_read(self, payload: dict) -> dict: + return await self._channel.push("api:chat:mark_thread_read", payload) + + # List all messages in the current thread + async def api_chat_list_messages(self, payload: dict) -> dict: + return await self._channel.push("api:chat:list_messages", payload) + + # Load additional messages with cursor-based pagination + async def api_chat_load_more_messages(self, payload: dict) -> dict: + return await self._channel.push("api:chat:load_more_messages", payload) + + # Post a new message with optional uploads and reply-to + async def api_chat_post_message(self, payload: dict) -> dict: + return await self._channel.push("api:chat:post_message", payload) + + # Post a simple text message + async def api_chat_post_simple_message(self, payload: dict) -> dict: + return await self._channel.push("api:chat:post_simple_message", payload) + + # Edit an existing message's content + async def api_chat_edit_message(self, payload: dict) -> dict: + return await self._channel.push("api:chat:edit_message", payload) + + # Delete a message + async def api_chat_delete_message(self, payload: dict) -> dict: + return await self._channel.push("api:chat:delete_message", payload) + + # Add an emoji reaction to a message + async def api_chat_add_reaction(self, payload: dict) -> dict: + return await self._channel.push("api:chat:add_reaction", payload) + + # Remove an emoji reaction from a message + async def api_chat_remove_reaction(self, payload: dict) -> dict: + return await self._channel.push("api:chat:remove_reaction", payload) + + # Broadcast when a new message is added to a thread + def on_message_added(self, callback): + return self._channel.on("message_added", callback) + + # Broadcast when a message is updated or removed + def on_message_updated(self, callback): + return self._channel.on("message_updated", callback) + + # Broadcast thread-level events (agent updates, read state, unread counts) + def on_thread_event(self, callback): + return self._channel.on("thread_event", callback) + + # Broadcast system-wide events + def on_system_event(self, callback): + return self._channel.on("system_event", callback) diff --git a/src/archastro/platform/channels/api_object_channel.py b/src/archastro/platform/channels/api_object_channel.py new file mode 100644 index 0000000..8626112 --- /dev/null +++ b/src/archastro/platform/channels/api_object_channel.py @@ -0,0 +1,57 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 2e14189cffac + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from phx_channel.socket import Socket + + +# Channel for real-time custom object collaboration. +# Clients join `api:object:{object_id}` to receive the current object state +# and subscribe to field-level updates. Mutations are sent as key:value maps. +class ApiObjectChannel: + def __init__(self, channel, join_response=None): + self._channel = channel + self.join_response = join_response + + @staticmethod + def topic_by_id(object_id: str) -> str: + return f"api:object:{object_id}" + + @classmethod + async def join_by_id(cls, socket: "Socket", object_id: str) -> "ApiObjectChannel": + topic = cls.topic_by_id(object_id) + channel = socket.channel(topic) + join_response = await channel.join() + return cls(channel, join_response) + + @staticmethod + def topic_by_row_key(schema_type: str, row_key: str) -> str: + return f"api:object:{schema_type}:{row_key}" + + @classmethod + async def join_by_row_key( + cls, socket: "Socket", schema_type: str, row_key: str + ) -> "ApiObjectChannel": + topic = cls.topic_by_row_key(schema_type, row_key) + channel = socket.channel(topic) + join_response = await channel.join() + return cls(channel, join_response) + + # Leave the underlying channel. + async def leave(self): + await self._channel.leave() + + async def update_fields(self, payload: dict) -> dict: + return await self._channel.push("update_fields", payload) + + async def save(self, payload: dict) -> dict: + return await self._channel.push("save", payload) + + def on_object_updated(self, callback): + return self._channel.on("object_updated", callback) + + def on_object_created(self, callback): + return self._channel.on("object_created", callback) diff --git a/src/archastro/platform/client.py b/src/archastro/platform/client.py new file mode 100644 index 0000000..e29f83d --- /dev/null +++ b/src/archastro/platform/client.py @@ -0,0 +1,121 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 838be0139962 + +from .auth import AuthClient +from .runtime.http_client import HttpClient +from .v1 import V1 + + +class PlatformClient: + def __init__( + self, + *, + base_url: str = "https://platform.archastro.ai", + access_token: str | None = None, + get_access_token=None, + on_refresh_token=None, + path_prefix: str | None = None, + default_headers: dict[str, str] | None = None, + ): + self._http = HttpClient( + base_url=base_url, + access_token=access_token, + get_access_token=get_access_token, + on_refresh_token=on_refresh_token, + path_prefix=path_prefix, + default_headers=default_headers, + ) + self.auth = AuthClient(self._http) + self.v1 = V1(self._http) + self.agent_computers = self.v1.agent_computers + self.agent_installations = self.v1.agent_installations + self.agent_routines = self.v1.agent_routines + self.agent_sessions = self.v1.agent_sessions + self.agent_skills = self.v1.agent_skills + self.agent_tools = self.v1.agent_tools + self.agents = self.v1.agents + self.artifacts = self.v1.artifacts + self.automation_runs = self.v1.automation_runs + self.automations = self.v1.automations + self.config = self.v1.config + self.custom_objects = self.v1.custom_objects + self.installation_sources = self.v1.installation_sources + self.kv = self.v1.kv + self.orgs = self.v1.orgs + self.team_memberships = self.v1.team_memberships + self.teams = self.v1.teams + self.thread_messages = self.v1.thread_messages + self.threads = self.v1.threads + self.users = self.v1.users + self.ai = self.v1.ai + self._refresh_token: str | None = None + + @property + def refresh_token(self) -> str | None: + return self._refresh_token + + def set_access_token(self, token: str): + self._http.set_access_token(token) + + def set_refresh_token(self, token: str): + self._refresh_token = token + + # ─── Factory constructors (generated from auth schemes) ─── + + @classmethod + def with_secret_key(cls, key: str, base_url: str | None = None) -> "PlatformClient": + """Secret API key — full admin access, no user JWT required""" + kwargs = {} + if base_url: + kwargs["base_url"] = base_url + return cls(default_headers={"x-archastro-api-key": key}, **kwargs) + + @classmethod + def with_token( + cls, api_key: str, access_token: str, base_url: str | None = None + ) -> "PlatformClient": + """Create a client with a publishable key and pre-existing access token.""" + kwargs = {} + if base_url: + kwargs["base_url"] = base_url + return cls( + access_token=access_token, default_headers={"x-archastro-api-key": api_key}, **kwargs + ) + + @classmethod + async def with_credentials( + cls, api_key: str, email: str, password: str, base_url: str | None = None + ) -> "PlatformClient": + """Create a client by logging in with email/password""" + kwargs = {} + if base_url: + kwargs["base_url"] = base_url + client = cls(default_headers={"x-archastro-api-key": api_key}, **kwargs) + tokens = await client.auth.login(email, password) + if not tokens.access_token: + raise ValueError("Login did not return an access token") + client.set_access_token(tokens.access_token) + if tokens.refresh_token: + client.set_refresh_token(tokens.refresh_token) + refresh_http = HttpClient( + base_url=base_url or "https://platform.archastro.ai", + default_headers={"x-archastro-api-key": api_key}, + refresh_only=True, + ) + refresh_auth = AuthClient(refresh_http) + + async def _refresh() -> str: + rt = client.refresh_token + if not rt: + raise ValueError("No refresh token available") + refreshed = await refresh_auth.refresh(rt) + if not refreshed.access_token: + raise ValueError("Refresh did not return an access token") + client.set_access_token(refreshed.access_token) + if refreshed.refresh_token: + client.set_refresh_token(refreshed.refresh_token) + return refreshed.access_token + + client._http.set_refresh_handler(_refresh) + return client diff --git a/src/archastro/platform/runtime/__init__.py b/src/archastro/platform/runtime/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/archastro/platform/runtime/http_client.py b/src/archastro/platform/runtime/http_client.py new file mode 100644 index 0000000..c86082a --- /dev/null +++ b/src/archastro/platform/runtime/http_client.py @@ -0,0 +1,210 @@ +# Runtime: async HTTP client for the generated Platform SDK. +# This file is hand-maintained, not generated. + +from __future__ import annotations + +import asyncio +from collections.abc import Callable, Coroutine +from typing import Any + +import httpx + +DEFAULT_API_PREFIX = "/api/v1" + + +class ApiError(Exception): + """Structured API error with status code, error code, and message.""" + + def __init__( + self, + status: int, + error_code: str, + message: str, + body: dict[str, Any] | None = None, + ): + super().__init__(message) + self.status = status + self.error_code = error_code + self.body = body + + +class HttpClient: + def __init__( + self, + *, + base_url: str, + access_token: str | None = None, + get_access_token: Callable[[], str | None] | None = None, + on_refresh_token: Callable[[], Coroutine[Any, Any, str]] | None = None, + path_prefix: str | None = None, + default_headers: dict[str, str] | None = None, + refresh_only: bool = False, + ): + self._base_url = base_url.rstrip("/") + self._access_token = access_token + self._get_access_token = get_access_token + self._on_refresh_token = on_refresh_token + self._path_prefix = path_prefix + self._default_headers = default_headers or {} + self._client = httpx.AsyncClient(timeout=30.0) + self._refresh_task: asyncio.Task[str] | None = None + self._refresh_only = refresh_only + + def _get_token(self) -> str | None: + if self._get_access_token: + return self._get_access_token() + return self._access_token + + def _transform_path(self, path: str) -> str: + if self._path_prefix is None: + return path + if path.startswith(DEFAULT_API_PREFIX): + return self._path_prefix + path[len(DEFAULT_API_PREFIX) :] + return path + + def set_access_token(self, token: str) -> None: + self._access_token = token + + def set_refresh_handler(self, handler: Callable[[], Coroutine[Any, Any, str]]) -> None: + self._on_refresh_token = handler + + async def _do_fetch( + self, + path: str, + *, + method: str = "GET", + body: Any = None, + headers: dict[str, str] | None = None, + query: dict[str, Any] | None = None, + ) -> httpx.Response: + token = self._get_token() + url = f"{self._base_url}{self._transform_path(path)}" + + req_headers = { + **self._default_headers, + "Content-Type": "application/json", + } + if token: + req_headers["Authorization"] = f"Bearer {token}" + if headers: + req_headers.update(headers) + + params = None + if query: + params = {k: v for k, v in query.items() if v is not None} + + return await self._client.request( + method, + url, + json=body if body is not None and method not in ("GET", "HEAD") else None, + headers=req_headers, + params=params, + ) + + async def _execute( + self, + path: str, + *, + method: str = "GET", + body: Any = None, + headers: dict[str, str] | None = None, + query: dict[str, Any] | None = None, + ) -> httpx.Response: + """Fetch with auth gate, 401 auto-refresh, and error handling. + + Returns the successful response for callers to interpret (JSON, raw bytes, etc.). + """ + auth_prefix = f"{DEFAULT_API_PREFIX}/auth/" + if self._refresh_only and not path.startswith(auth_prefix): + raise RuntimeError( + f"Refresh-only HTTP client cannot make requests outside {auth_prefix}" + ) + + response = await self._do_fetch( + path, method=method, body=body, headers=headers, query=query + ) + + # Auto-refresh: on 401, attempt one token refresh and retry. + # The refresh handler runs on a separate HttpClient (refresh_only), + # so it cannot re-enter this block. Concurrent 401s piggyback on + # the same _refresh_task. + if ( + response.status_code == 401 + and self._on_refresh_token + and not path.startswith(auth_prefix) + ): + if self._refresh_task is None: + + async def _do_refresh() -> str: + try: + return await self._on_refresh_token() # type: ignore[misc] + finally: + self._refresh_task = None + + self._refresh_task = asyncio.create_task(_do_refresh()) + try: + new_token = await self._refresh_task + except Exception: + pass # refresh failed — fall through to throw original 401 + else: + self._access_token = new_token + response = await self._do_fetch( + path, method=method, body=body, headers=headers, query=query + ) + + if response.status_code >= 400: + raw_data: dict[str, Any] = {} + try: + raw_data = response.json() + except Exception: + pass + error_code, message = _parse_error(raw_data, response.status_code) + raise ApiError(response.status_code, error_code, message, raw_data) + + return response + + async def request( + self, + path: str, + *, + method: str = "GET", + body: Any = None, + headers: dict[str, str] | None = None, + query: dict[str, Any] | None = None, + ) -> Any: + response = await self._execute(path, method=method, body=body, headers=headers, query=query) + + if response.status_code == 204: + return None + + return response.json() + + async def request_raw( + self, + path: str, + *, + method: str = "GET", + body: Any = None, + headers: dict[str, str] | None = None, + query: dict[str, Any] | None = None, + ) -> dict[str, Any]: + response = await self._execute(path, method=method, body=body, headers=headers, query=query) + + return { + "content": response.content, + "mime_type": response.headers.get("content-type", "text/plain"), + } + + async def close(self) -> None: + await self._client.aclose() + + +def _parse_error(raw_data: dict[str, Any], status: int) -> tuple[str, str]: + error = raw_data.get("error") + if isinstance(error, dict): + code = error.get("code") or error.get("type") or "unknown_error" + message = error.get("message") or f"HTTP {status}" + return code, message + error_str = error if isinstance(error, str) else None + message = raw_data.get("message") or error_str or f"HTTP {status}" + return error_str or "unknown_error", message diff --git a/src/archastro/platform/types/__init__.py b/src/archastro/platform/types/__init__.py new file mode 100644 index 0000000..22d5532 --- /dev/null +++ b/src/archastro/platform/types/__init__.py @@ -0,0 +1,20 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 8f970598bc0c + +from .agents import * # noqa: F401,F403 +from .ai import * # noqa: F401,F403 +from .artifacts import * # noqa: F401,F403 +from .automations import * # noqa: F401,F403 +from .chat import * # noqa: F401,F403 +from .common import * # noqa: F401,F403 +from .config import * # noqa: F401,F403 +from .image import * # noqa: F401,F403 +from .members import * # noqa: F401,F403 +from .orgs import * # noqa: F401,F403 +from .reactions import * # noqa: F401,F403 +from .schedules import * # noqa: F401,F403 +from .system import * # noqa: F401,F403 +from .teams import * # noqa: F401,F403 +from .threads import * # noqa: F401,F403 +from .users import * # noqa: F401,F403 diff --git a/src/archastro/platform/types/agents.py b/src/archastro/platform/types/agents.py new file mode 100644 index 0000000..e579fc6 --- /dev/null +++ b/src/archastro/platform/types/agents.py @@ -0,0 +1,276 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 4a4313dd8d9e + + +from pydantic import BaseModel + +from .common import Acl, ResolvedTool, WorkerStatus +from .config import Config + + +# API schema for an agent. +class Agent(BaseModel): + acl: Acl | None = None + app: str | None = None # Application + created_at: str | None = None # Creation timestamp + default_model: str | None = None # Default AI model + email: str | None = None # Agent email + id: str # Agent ID (agi_...) + identity: str | None = None # Identity prompt + lookup_key: str | None = None # Unique lookup key + metadata: dict[str, object] | None = None # Arbitrary metadata + name: str | None = None # Agent name + org: str | None = None # Organization + phone_number: str | None = None # Agent phone number + sandbox: str | None = None # Sandbox + team: str | None = None # Owning team + updated_at: str | None = None # Last update timestamp + user: str | None = None # Owning user + + +# API schema for an agent computer. +class AgentComputer(BaseModel): + agent: str | None = None # Owning agent + app: str | None = None # Application ID + config: dict[str, object] | None = None # Configuration + created_at: str | None = None # Creation timestamp + error_message: str | None = None # Error message + id: str # Computer ID (cmp_...) + last_active_at: str | None = None # Last active timestamp + lookup_key: str | None = None # Unique lookup key + metadata: dict[str, object] | None = None # Arbitrary metadata + name: str | None = None # Computer name + region: str | None = None # Region + sprite_url: str | None = None # Sprite URL + status: str | None = None # Computer status + updated_at: str | None = None # Last update timestamp + + +# List response for agent computers. +class AgentComputerListResponse(BaseModel): + data: list[AgentComputer] # List of agent computers + + +# API schema for an agent conversation history session. +class AgentConversationHistory(BaseModel): + created_at: str | None = None # Creation timestamp + last_interaction_at: str | None = None # Last interaction timestamp + name: str | None = None # Session name + org: str | None = None # Organization + sandbox: str | None = None # Sandbox + session: str | None = None # Session ID (ach_...) + + +# API schema for an agent export response. +# Contains the reconstructed AgentTemplate and all dependent config files +# needed for a fully self-contained re-deploy. +class AgentExport(BaseModel): + configs: list[Config] # Dependent config files + template: dict[str, object] # AgentTemplate config object + + +# API schema for an agent impersonation skill summary. +class AgentImpersonationSkillSummary(BaseModel): + description: str | None = None # Skill description + id: str # Skill config ID + name: str # Skill display name + slug: str # Skill slug + + +# API schema for an agent tool. +class AgentTool(BaseModel): + agent: str | None = None # Owning agent ID + app: str | None = None # Application ID + builtin_tool_config: dict[str, object] | None = None # Builtin tool configuration + builtin_tool_key: str | None = None # Builtin tool key + config: str | None = None # Config ID + created_at: str | None = None # Creation timestamp + description: str | None = None # Tool description + handler_type: str | None = None # Handler type + id: str # Tool ID (atl_...) + instruction: str | None = None # Tool instruction + kind: str | None = None # Tool kind + lookup_key: str | None = None # Unique lookup key + metadata: dict[str, object] | None = None # Arbitrary metadata + name: str | None = None # Tool name + parameters: dict[str, object] | None = None # Tool parameters + parameters_config: str | None = None # Parameters config ID + status: str | None = None # Tool status + updated_at: str | None = None # Last update timestamp + + +# API schema for a full agent impersonation manifest. +class AgentImpersonationManifest(BaseModel): + agent: Agent # Agent identity and metadata + skills: list[AgentImpersonationSkillSummary] # Linked skills + tools: list[AgentTool] # Active tools + + +# API schema for an agent impersonation skill file entry. +class AgentImpersonationSkillFile(BaseModel): + content_type: str | None = None # Raw content type for the file + download_url: str # URL to fetch raw file contents + path: str # Relative path within the skill bundle + + +# API schema for an agent impersonation skill list response. +class AgentImpersonationSkillList(BaseModel): + data: list[AgentImpersonationSkillSummary] # Agent-linked skills + + +# API schema for an agent impersonation skill manifest. +class AgentImpersonationSkillManifest(BaseModel): + description: str | None = None # Skill description + entrypoint: str # Primary file path for the skill + files: list[AgentImpersonationSkillFile] # Skill files + id: str # Skill config ID + name: str # Skill display name + slug: str # Skill slug + + +# API schema for the resolved callable tools list response. +class AgentImpersonationToolList(BaseModel): + data: list[ResolvedTool] # Resolved callable tools + + +# API schema for the result of running an agent tool via impersonation. +class AgentImpersonationToolRunResult(BaseModel): + duration_ms: int # Execution duration in milliseconds + result: dict[str, object] # Tool execution result + + +# Paginated list response for agents. +class AgentListResponse(BaseModel): + data: list[Agent] # List of agents + has_next: bool | None = None # Whether a next page exists + has_prev: bool | None = None # Whether a previous page exists + page: int | None = None # Current page number + page_size: int | None = None # Results per page + total_entries: int | None = None # Total number of entries + total_pages: int | None = None # Total number of pages + + +# API schema for an agent routine. +class AgentRoutine(BaseModel): + acl: Acl | None = None + agent: str | None = None # Owning agent ID + app: str | None = None # Application ID + config: str | None = None # Config ID + created_at: str | None = None # Creation timestamp + description: str | None = None # Routine description + event_config: dict[str, object] | None = None # Event configuration + event_type: str | None = None # Event type + handler_type: str | None = None # Handler type + id: str # Routine ID (arn_...) + lookup_key: str | None = None # Unique lookup key + metadata: dict[str, object] | None = None # Arbitrary metadata + name: str | None = None # Routine name + preset_config: dict[str, object] | None = None # Preset configuration + preset_name: str | None = None # Preset name + schedule: str | None = None # Schedule expression + script: str | None = None # Script content + status: str | None = None # Routine status + trigger_context: str | None = None # Trigger context + updated_at: str | None = None # Last update timestamp + + +# List response for agent routines. +class AgentRoutineListResponse(BaseModel): + data: list[AgentRoutine] # List of agent routines + + +# API schema for an agent routine run. +class AgentRoutineRun(BaseModel): + acl: Acl | None = None + agent: str | None = None # Agent + app: str | None = None # Application + created_at: str | None = None # Creation timestamp + duration_ms: int | None = None # Duration in milliseconds + event_id: str | None = None # Event ID + id: str # Run ID (arr_...) + metadata: dict[str, object] | None = None # Run metadata + payload: dict[str, object] | None = None # Event payload + result: dict[str, object] | None = None # Run result + routine: str | None = None # Routine + status: str | None = None # Run status + structured_response: dict[str, object] | None = None + updated_at: str | None = None # Last update timestamp + worker: WorkerStatus | None = None + + +# Cursor-paginated list response for agent routine runs. +class AgentRoutineRunListResponse(BaseModel): + after_cursor: str | None = None # Cursor for fetching items after this point + before_cursor: str | None = None # Cursor for fetching items before this point + data: list[AgentRoutineRun] # List of routine runs + + +# API schema for an agent schedule. +class AgentSchedule(BaseModel): + agent: str | None = None # Owning agent ID + app: str | None = None # Application ID + created_at: str | None = None # Creation timestamp + cron_expression: str | None = None # Cron expression (recurring only) + id: str # Schedule ID (asc_...) + instructions: str | None = None # Task instructions + last_run_at: str | None = None # Last execution time + max_runs: int | None = None # Maximum runs (recurring only) + metadata: dict[str, object] | None = None # Arbitrary metadata + next_run_at: str | None = None # Next scheduled execution + run_count: int | None = None # Number of times executed + schedule_type: str | None = None # Schedule type (once or recurring) + scheduled_at: str | None = None # One-time execution time + status: str | None = None # Schedule status + thread: str | None = None # Thread ID (if thread-bound) + timezone: str | None = None # Schedule timezone + updated_at: str | None = None # Last update timestamp + + +# API schema for an agent session. +class AgentSession(BaseModel): + agent: str | None = None # Owning agent ID (agi_...) + completed_at: str | None = None # When the session completed + created_at: str | None = None # Creation timestamp + error: str | None = None # Error message if failed + id: str # Agent session ID (ase_...) + inbox: list[dict[str, object]] | None = None # Inbox messages + instructions: str | None = None # Task description for the session + is_system_session: bool | None = None # Whether this is a system-created session + max_runs_per_turn: int | None = None # Max tool runs per turn + max_tokens: int | None = None # Max tokens + max_turns: int | None = None # Max turns + metadata: dict[str, object] | None = None # Arbitrary metadata + name: str | None = None # Optional display name for the session + result: dict[str, object] | None = None # Session result + started_at: str | None = None # When the session started running + status: str | None = None + trajectory: str | None = None # Trajectory ID for the durable session transcript + + +# List response for agent sessions. +class AgentSessionListResponse(BaseModel): + data: list[AgentSession] # List of agent sessions + + +# API schema for an agent skill. +class AgentSkill(BaseModel): + agent: str | None = None # Owning agent ID + app: str | None = None # Application ID + config: str | None = None # Skill config ID + created_at: str | None = None # Creation timestamp + id: str # Agent skill ID (ask_...) + instruction: str | None = None # Instruction override + metadata: dict[str, object] | None = None # Arbitrary metadata + status: str | None = None # Skill status + updated_at: str | None = None # Last update timestamp + + +# API schema for agent skills list response. +class AgentSkillList(BaseModel): + data: list[AgentSkill] # List of agent skills + + +# List response for agent tools. +class AgentToolListResponse(BaseModel): + data: list[AgentTool] # List of agent tools diff --git a/src/archastro/platform/types/ai.py b/src/archastro/platform/types/ai.py new file mode 100644 index 0000000..f043db3 --- /dev/null +++ b/src/archastro/platform/types/ai.py @@ -0,0 +1,80 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: dd157bff62eb + + +from pydantic import BaseModel + + +# Tool call from assistant message. +class AIToolCall(BaseModel): + arguments: dict[str, object] # Tool arguments + id: str # Tool call ID + name: str # Tool/function name + thought_signature: str | None = None # Optional thought signature + + +# Tool result from tool execution. +class AIToolResult(BaseModel): + content: str | None = None # Tool result content + id: str # Tool call ID this result responds to + name: str # Tool/function name + resolution: object | None = None # Structured tool resolution + + +# AI chat message (OpenAI-compatible format). +class AIMessage(BaseModel): + content: str | None = None # Message text content + content_parts: list[dict[str, object]] | None = None # Multimodal content parts + resume_token: str | None = None # Resume token for continuing conversations + role: str # Message role (system, user, assistant, tool) + structured_output: object | None = None # Structured output data + tool_calls: list[AIToolCall] | None = None # Tool calls from assistant + tool_results: list[AIToolResult] | None = None # Tool results from tool execution + + +# Schema for AI chat completion results. +class AICompletionResult(BaseModel): + finish_reason: str # Completion stop reason + message: AIMessage # Final assistant message + messages: list[AIMessage] # Full message history + token_usage: dict[str, object] | None = None # Token usage keyed by model + + +# Schema for an input image (base64-encoded) used in image editing. +class AIImageInput(BaseModel): + image_data: str # Base64-encoded image data + image_type: str # MIME type (e.g. image/png, image/jpeg) + + +# Schema for an AI image generation result. +class AIImageResult(BaseModel): + aspect_ratio: str | None = None # Aspect ratio (e.g. 16:9) + height: int | None = None # Image height in pixels + image_data: str | None = None # Base64-encoded image data + image_size: str | None = None # Image size tier (e.g. 1K, 2K) + image_type: str | None = None # MIME type (e.g. image/png) + image_url: str | None = None # URL to the generated image + model: str # Model used for generation + revised_prompt: str | None = None # Provider-revised prompt + size: str | None = None # Size string (e.g. 1024x1024) + usage: dict[str, object] | None = None # Token/usage information + width: int | None = None # Image width in pixels + + +# Schema for AI model information. +class AIModel(BaseModel): + id: str # Model identifier + + +# OpenAI-style function tool definition. +class AIToolFunction(BaseModel): + description: str | None = None # Function description + name: str # Function name + parameters: dict[str, object] # JSON Schema for function parameters + + +# OpenAI-style tool definition. +class AITool(BaseModel): + function: AIToolFunction # Function tool definition + type: str # Tool type (function) diff --git a/src/archastro/platform/types/artifacts.py b/src/archastro/platform/types/artifacts.py new file mode 100644 index 0000000..5a6e254 --- /dev/null +++ b/src/archastro/platform/types/artifacts.py @@ -0,0 +1,45 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 9249f05697f5 + + +from pydantic import BaseModel + +from .image import ImageSource + + +# API schema for an artifact. +class Artifact(BaseModel): + agent: str | None = None # Agent + content_type: str | None = None # MIME content type + created_at: str | None = None # Creation timestamp + current_version: str | None = None # Current version ID + description: str | None = None # Artifact description + file: str | None = None # Storage file + file_name: str | None = None # Original filename + file_url: str | None = None # Signed file URL + id: str # Artifact ID + image_source: ImageSource | None = None # Image source metadata + name: str | None = None # Artifact name + org: str | None = None # Organization + sandbox: str | None = None # Sandbox identifier + team: str | None = None # Team + thread: str | None = None # Thread + updated_at: str | None = None # Last update timestamp + user: str | None = None # User + version: int | None = None # Current version number + + +# API schema for an artifact version. +class ArtifactVersion(BaseModel): + artifact: str | None = None # Parent artifact + change_description: str | None = None # Description of changes + content_type: str | None = None # MIME content type + created_at: str | None = None # Creation timestamp + file: str | None = None # Storage file + file_name: str | None = None # Original filename + file_url: str | None = None # Signed file URL + id: str # Artifact version ID + image_source: ImageSource | None = None # Image source metadata + updated_at: str | None = None # Last update timestamp + version_number: int | None = None # Version number diff --git a/src/archastro/platform/types/automations.py b/src/archastro/platform/types/automations.py new file mode 100644 index 0000000..0cc8328 --- /dev/null +++ b/src/archastro/platform/types/automations.py @@ -0,0 +1,46 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: cf4220547a44 + + +from pydantic import BaseModel + + +# Schema for a developer automation. +# Maps to serialized automation output from developer portal API. +class Automation(BaseModel): + app: str # App (dap_...) + config: str | None = None # Associated config (cfg_...) + created_at: str | None = None # Created timestamp + created_by_user: str | None = None # Creator user (usr_...) + creator: str | None = None # Creator account (dac_...) + description: str | None = None # Optional description + id: str # Public ID (aut_...) + input_schema_config: str | None = None # Input schema config (cfg_...) + invoke_auth: str | None = None # Auth mode: secret_key or user + lookup_key: str | None = None # Optional unique lookup key + metadata: dict[str, object] | None = None # Arbitrary metadata + name: str # Automation name + run_as_agent: str | None = None # Agent to run as (agt_...) + run_as_user: str | None = None # User to run as (usr_...) + schedule: str | None = None # Cron expression for scheduled type + status: str # Status: draft, running, or paused + trigger: str | None = None # Event name for trigger type + type: str # Type: trigger, scheduled, or invoked + updated_at: str | None = None # Updated timestamp + + +# Schema for an automation run. +# Maps to serialized automation run output from developer portal API. +class AutomationRun(BaseModel): + app: str # App (dap_...) + automation: str # Automation (aut_...) + created_at: str | None = None # Created timestamp + event_id: str | None = None # Triggering event ID + id: str # Public ID (atr_...) + payload: dict[str, object] | None = None # Event payload + result: dict[str, object] | None = None # Workflow execution result (payload and output) + status: str # Status: pending, running, completed, failed, cancelled + team: str | None = None # Team if team-owned + updated_at: str | None = None # Updated timestamp + user: str | None = None # User if user-owned diff --git a/src/archastro/platform/types/chat.py b/src/archastro/platform/types/chat.py new file mode 100644 index 0000000..8ac9c39 --- /dev/null +++ b/src/archastro/platform/types/chat.py @@ -0,0 +1,17 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 073cdd4840cf + + +from pydantic import BaseModel + +from .agents import Agent +from .users import User + + +# API schema for a chat member (user or agent). +class ChatMember(BaseModel): + agent: Agent | None = None # Agent object (for agent members) + membership_type: str | None = None # Membership type + type: str # Member type (user or agent) + user: User | None = None # User object (for user members) diff --git a/src/archastro/platform/types/common.py b/src/archastro/platform/types/common.py new file mode 100644 index 0000000..9cbd12f --- /dev/null +++ b/src/archastro/platform/types/common.py @@ -0,0 +1,1383 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 1a52d465f51e + + +from pydantic import BaseModel + +from .image import ImageSource +from .users import User + + +# Schema for the current developer account. +class Account(BaseModel): + alias: str | None = None # Developer alias + created_at: str # Account creation date + email: str # Email address + email_verified: bool # Whether email is verified + full_name: str | None = None # Full name + id: str # Account ID + system_role: str # System role + timezone: str | None = None # IANA timezone + + +# API schema for a single ACL grant entry. +class AclGrant(BaseModel): + actions: list[str] # List of allowed actions (e.g. read, write) + principal: str | None = None + principal_type: str # Principal type: user, team, org, org_role, agent, or everyone + + +# API schema for identifying a principal to remove from an ACL. +class AclRemoveTarget(BaseModel): + principal: str | None = None # Principal identifier to remove (omit for everyone) + principal_type: str # Principal type to remove + + +# Reusable API schema for access control lists. +# Supports two modes (mutually exclusive): +# **Replace mode** send `grants` to replace all entries: +# {"grants": [{"principal_type": "user", "principal": "...", "actions": ["read"]}]} +# Use `{"grants": []}` to clear all entries. +# **Patch mode** send `add` and/or `remove`: +# {"add": [...grants...], "remove": [{"principal_type": "user", "principal": "..."}]} +# Cannot mix `grants` with `add`/`remove`. +class Acl(BaseModel): + add: list[AclGrant] | None = None # Patch mode: grants to add or merge into existing + grants: list[AclGrant] | None = None + remove: list[AclRemoveTarget] | None = None # Patch mode: principals to remove from existing + + +# API schema for an activity feed entry. +class ActivityFeedEntry(BaseModel): + agent: str | dict[str, object] | None = None + app: str | None = None # Application + attachments: list[dict[str, object]] | None = None # Entry attachments + automation_run: str | None = None # Automation run + content: str | None = None # Longer explanation (markdown) + correlation_id: str | None = None # Correlation ID for grouped entries + created_at: str | None = None # Creation timestamp + id: str # Entry ID (afe_...) + kind: str | None = None # Entry kind + level: str | None = None # Severity level + metadata: dict[str, object] | None = None # Entry metadata + org: str | None = None # Organization + routine_run: str | None = None # Routine run + sandbox: str | None = None # Sandbox identifier + session_record: str | None = None # Agent session + team: str | None = None # Team + thread: str | None = None # Thread + title: str | None = None # One-line summary + updated_at: str | None = None # Last update timestamp + user: str | dict[str, object] | None = None + + +# Schema for a message actor (user or agent). +# Actors represent the entity that sent a message. +# Maps to the actor format from MessageActorHelper.build_actor/1. +class Actor(BaseModel): + alias: str | None = None # Actor alias/handle + id: str | None = None # Actor ID (format: user-xxx or agent-xxx) + name: str | None = None # Actor display name + profile_picture: ImageSource | None = None # Profile picture + + +class ResolvedTool(BaseModel): + description: str | None = None # What this tool does + name: str # Callable tool function name + parameters: dict[str, object] | None = None # JSON Schema describing the expected input + + +# API schema for background worker status on a routine run. +class WorkerStatus(BaseModel): + attempt: int # Current attempt number (0 = not yet attempted) + max_attempts: int # Maximum allowed attempts + status: str # Worker state: queued, executing, retrying, completed, discarded, or cancelled + + +# Schema for an API call record. +# Maps to serialized API call output from developer portal API. +class ApiCall(BaseModel): + api_key_type: str | None = None # API key type (publishable or secret) + created_at: str | None = None # Created timestamp + error_message: str | None = None # Error message if request failed + event_type: str | None = None # Webhook event type (e.g. push, pull_request) + full_url: str # Full request URL with query string + handler_module: str | None = None # ApiDsl action module + id: str # Public ID (aac_...) + ip_address: str | None = None # Client IP address + latency_ms: int # Latency in milliseconds + metadata: dict[str, object] | None = None + method: str # HTTP method + org: str | None = None # Org if org-scoped + path: str # Sanitized path with param placeholders + query_string: str | None = None # Query string + request_body: dict[str, object] | None = None # Request body payload (webhooks only) + request_headers: dict[str, object] | None = None # Request headers (webhooks only) + request_id: str | None = None # Request ID from Plug.RequestId + status_code: int # HTTP status code + team: str | None = None # Team if team-scoped + thread: str | None = None # Thread if thread-scoped + user: str | None = None # User if user-scoped + + +# Schema for a documented API endpoint in the developer API explorer. +class ApiExplorerEndpoint(BaseModel): + deprecated: bool # Whether the route is deprecated + description: str | None = None # Endpoint description + errors: list[dict[str, object]] # Documented error responses + method: str # HTTP method + params: list[dict[str, object]] # Documented params + path: str # Route path + returns: dict[str, object] | None = None # Return schema description + scope: str # Endpoint scope + tags: list[str] # Route tags + + +# Schema for the API explorer response. +class ApiExplorerIndex(BaseModel): + data: list[ApiExplorerEndpoint] # Documented API endpoints + schemas: dict[str, object] | None = None # Collected schema definitions + + +# Schema for a sandbox API key. +class SandboxKey(BaseModel): + created_at: str | None = None # Created timestamp + expires_at: str | None = None # Expiry timestamp + full_key: str | None = None # Full key shown only once at creation + id: str # Public ID (dsk_...) + key_hint: str | None = None # Last 4 chars hint + key_value: str | None = None # Full key (publishable only) + last_used_at: str | None = None # Last used timestamp + status: str # Status (active, revoked) + type: str # Key type (publishable, secret) + + +# Schema for a developer sandbox. +class Sandbox(BaseModel): + created_at: str | None = None # Created timestamp + id: str # Public ID (dsb_...) + keys: list[SandboxKey] | None = None # Sandbox API keys + name: str # Sandbox name + slug: str # Sandbox slug (unique per app) + updated_at: str | None = None # Updated timestamp + + +# Schema for a developer app. +# Maps to serialized app output from developer portal API. +class App(BaseModel): + app_slug: str | None = None # Workspace slug (if set) + app_url: str | None = None # App URL + brand_name: str | None = None # Brand name for emails + created_at: str | None = None # Created timestamp + description: str | None = None # App description + from_name: str | None = None # From name for emails + id: str # Public ID (dap_...) + marketing_url: str | None = None # Marketing URL + muted_color: str | None = None # Muted hex color + name: str # App name + primary_color: str | None = None # Primary hex color + sandboxes: list[Sandbox] | None = None # App sandboxes with keys + status: str # Status (active, suspended) + support_email: str | None = None # Support email address + third_party_oauth_enabled: bool | None = None # Third-party OAuth enabled + updated_at: str | None = None # Updated timestamp + + +# Schema for an app environment variable response with a masked value. +class AppEnvVarMasked(BaseModel): + created_at: str | None = None # Created timestamp + description: str | None = None # Optional description + id: str # Environment variable ID + key: str # Environment variable key + masked_value: str # Masked environment variable value + updated_at: str | None = None # Updated timestamp + + +# Schema for masked app environment variable list responses. +class AppEnvVarMaskedList(BaseModel): + data: list[AppEnvVarMasked] # Environment variables + + +# Schema for an app environment variable response that includes the plaintext value. +class AppEnvVarPlaintext(BaseModel): + created_at: str | None = None # Created timestamp + description: str | None = None # Optional description + id: str # Environment variable ID + key: str # Environment variable key + updated_at: str | None = None # Updated timestamp + value: str # Plaintext environment variable value + + +# Schema for an API key. +# Maps to serialized app key output from developer portal API. +class AppKey(BaseModel): + created_at: str | None = None # Created timestamp + full_key: str | None = None # Full key shown only once at creation + id: str # Public ID (dak_...) + key_hint: str | None = None # Last 4 chars hint + key_value: str | None = None # Full key (publishable only) + last_used_at: str | None = None # Last used timestamp + status: str # Status (active, revoked) + type: str # Key type (publishable, secret) + + +# Schema for an app slug mapping. +class AppSlug(BaseModel): + app: str # App identifier + created_at: str | None = None # Created timestamp + creator: str | None = None # Creator identifier + id: str # Slug record ID + slug: str # Globally unique slug + updated_at: str | None = None # Updated timestamp + + +# API schema for a media variant. +class MediaVariant(BaseModel): + content_type: str | None = None # File content type + created_at: str | None = None # Creation timestamp + file: str | None = None # Storage file + filename: str | None = None # Original filename + height: int | None = None # Height in pixels + id: str # Variant ID + image_source: ImageSource | None = None # Image source metadata + updated_at: str | None = None # Last update timestamp + url: str | None = None # Signed download URL + variant_key: str | None = None # Variant key (original, thumbnail, etc) + width: int | None = None # Width in pixels + + +# Schema for a message attachment. +# Attachments can be of various types (file, scraped_link, artifact, task, media, action). +# Fields present depend on the attachment type. +# Maps to format_attachments_for_client/1 output. +class Attachment(BaseModel): + content_type: str | None = None # MIME content type (file, artifact, media types) + description: str | None = None # Description (scraped_link, artifact, task types) + filename: str | None = None # File name (file, artifact, media types) + height: int | None = None # Media height (media type) + id: str # Attachment ID + image_height: int | None = None # Preview image height (scraped_link type) + image_source: ImageSource | None = None + image_url: str | None = None # Preview image URL (scraped_link type) + image_width: int | None = None # Preview image width (scraped_link type) + media_type: str | None = None # Media type (media type) + name: str | None = None # Media name (media type) + object: dict[str, object] | None = None # Embedded object (task, action types) + title: str | None = None # Title (scraped_link, artifact, task types) + type: str # Attachment type: file, scraped_link, artifact, task, media, action + url: str | None = None # URL to the resource (file, scraped_link, artifact, media types) + variants: list[MediaVariant] | None = None # Media variants (media type) + version: int | None = None # Artifact version number (artifact type) + width: int | None = None # Media width (media type) + + +# API schema for authentication token responses. +class AuthTokens(BaseModel): + expires_in: int # Token TTL in seconds + metadata: dict[str, object] | None = None # Additional metadata (e.g., onboarding_job_id) + refresh_token: str # Refresh token + token: str # Access token (JWT) + token_type: str # Token type (Bearer) + user: User # Authenticated user + + +# Schema for a billing plan. +class BillingPlan(BaseModel): + description: str | None = None # Plan description + id: str | None = None # Plan identifier (e.g. paygo) + name: str | None = None # Human-readable plan name + target: str | None = None # Entity type this plan applies to (developer or org) + + +# Schema for app billing settings. +class BillingSettingsApp(BaseModel): + billing_account: str | None = None # Developer account that owns billing (public ID) + org_billing_enabled: bool | None = None # Whether orgs get their own Stripe customers + + +# Schema for org billing settings. +class BillingSettingsOrg(BaseModel): + auto_reup_amount_cents: int | None = None + auto_reup_enabled: bool | None = None # Whether automatic credit reup is enabled + auto_reup_threshold_cents: int | None = None + billing_provider_environment: str | None = None # Stripe environment (live or test) + billing_provider_id: str | None = None # Stripe customer ID + pending_plan: str | None = None + plan: str | None = None # Active billing plan (e.g. enterprise-pilot) + primary_user: str | None = None # Org admin user who is the billing contact (public ID) + + +# An individual tool within a builtin tool catalog entry. +class BuiltinTool(BaseModel): + description: str | None = None # Tool description + name: str # Tool name + + +# A builtin tool catalog entry describing an available tool category. +class BuiltinToolCatalogEntry(BaseModel): + config_schema: dict[str, object] | None = None # JSON schema for tool configuration + description: str | None = None # Tool description + instruction: str | None = None # Tool instruction + key: str # Unique tool key + label: str | None = None # Display label + providers: list[str] | None = None # Supported providers + requires_integration: bool | None = None # Whether an integration is required + server_tool_type: str | None = None # Server tool type identifier + tools: list[BuiltinTool] | None = None # List of individual tools + + +# Schema for Stripe Checkout Session creation result. +class CheckoutSessionResult(BaseModel): + checkout_url: str # Stripe Checkout URL to redirect the user to + + +# Schema for comment creation parameters. +# Used by both Users.Tasks.CreateComment and Teams.Tasks.CreateComment actions. +class CommentCreateParams(BaseModel): + body: str # Comment body text + + +# Result of executing a command on an agent computer. +class ComputerExecResult(BaseModel): + exit_code: int | None = None # Process exit code + output: str | None = None # Command output + + +# Schema for a user credential. +# Maps to serialized credential output from developer portal API. +class ContextCredential(BaseModel): + alt_domains: list[str] | None = None # Alternative domains + created_at: str | None = None # Created timestamp + description: str | None = None # Human-readable description + domain: str # Domain (e.g., app.schoology.com) + id: str # Public ID (ucr_...) + last_accessed_at: str | None = None # Last accessed timestamp + updated_at: str | None = None # Updated timestamp + user: str | None = None # Owning user + + +# API schema for a context entry. +class ContextEntry(BaseModel): + after_cursor: str | None = None # Pagination cursor (after) + agent_user: str | None = None # Agent user + before_cursor: str | None = None # Pagination cursor (before) + created_at: str | None = None # Creation timestamp + files: list[dict[str, object]] | None = None # Loaded file objects + id: str # Context entry ID + links: list[dict[str, object]] | None = None # Loaded link objects + media: list[dict[str, object]] | None = None # Loaded media objects + metadata: dict[str, object] | None = None # Entry metadata + org: str | None = None # Organization + sandbox: str | None = None # Sandbox identifier + team: str | None = None # Team + text: str | None = None # Entry text + updated_at: str | None = None # Last update timestamp + user: str | None = None # User + + +# Schema for a context ingestion. +# Maps to serialized context ingestion output from developer portal API. +class ContextIngestion(BaseModel): + agent: str | None = None # Agent + completed_at: str | None = None # Completed timestamp + created_at: str | None = None # Created timestamp + error: dict[str, object] | None = None # Error details (if failed) + id: str # Public ID (cig_...) + metadata: dict[str, object] | None = None # Additional metadata + source: str | None = None # Source ID + started_at: str | None = None # Started timestamp + status: str # Status (pending, running, awaiting_callback, succeeded, failed) + team: str | None = None # Owning team ID + updated_at: str | None = None # Updated timestamp + user: str | None = None # Owning user ID + + +# Schema for a context integration. +# Maps to serialized integration output from developer portal API. +class ContextIntegration(BaseModel): + agent: str | None = None # Owning agent + auth_type: str # Auth type: oauth or app_installation + connected_at: str | None = None # Connection timestamp + created_at: str | None = None # Created timestamp + expires_at: str | None = None # Token expiration timestamp + id: str # Public ID (int_...) + installation: str | None = None # External installation (e.g. GitHub App installation) + last_refreshed_at: str | None = None # Last token refresh timestamp + metadata: dict[str, object] | None = None # Additional metadata + org: str | None = None # Owning org + provider: str # Provider name (e.g., google, github) + scopes: list[str] | None = None # OAuth scopes + status: str # Connection status: connected, disconnected, or token_expired + team: str | None = None # Owning team + updated_at: str | None = None # Updated timestamp + user: str | None = None # Owning user + workspace_key: str | None = None # Workspace key + + +# Schema for a context item. +# Maps to serialized context item output from developer portal API. +class ContextItem(BaseModel): + agent: str | None = None # Agent + content_type: str | None = None # Content type + created_at: str | None = None # Created timestamp + id: str # Public ID (cim_...) + item_group: str | None = None # Item group if part of a group + metadata: dict[str, object] | None = None # Additional metadata + normalized_content: str | None = None # Normalized content text + raw_content: dict[str, object] | None = None # Raw content data + source: str | None = None # Source + team: str | None = None # Owning team + updated_at: str | None = None # Updated timestamp + user: str | None = None # Owning user + + +# Schema for a context source. +# Maps to serialized context source output from developer portal API. +class ContextSource(BaseModel): + agent: str | None = None # Owning agent + context_installation: str | None = None # Associated installation + created_at: str | None = None # Created timestamp + id: str # Public ID (cso_...) + metadata: dict[str, object] | None = None # Additional metadata + org: str | None = None # Owning organization + parent_source: str | None = None # Parent source + payload: dict[str, object] | None = None # Type-specific configuration + sandbox: str | None = None # Owning sandbox + state: str # State: active or paused + team: str | None = None # Owning team + thread: str | None = None # Associated thread + type: str # Source type (e.g., gmail, github_activity) + updated_at: str | None = None # Updated timestamp + user: str | None = None # Owning user + + +# API schema for a user credential. +class Credential(BaseModel): + alt_domains: dict[str, object] | None = None # Alternative domains + created_at: str | None = None # Creation timestamp + description: str | None = None # Description + domain: str | None = None # Primary domain + id: str # Credential ID (ucr_...) + last_accessed_at: str | None = None # Last accessed timestamp + org: str | None = None # Organization + sandbox: str | None = None # Sandbox + secret_group: str | None = None # Secret group ID + updated_at: str | None = None # Last update timestamp + + +# API schema for a credential with decrypted secret values. +class CredentialWithSecrets(BaseModel): + credential: dict[str, object] # The credential with decrypted values + + +# Schema for credit balance response. +class CreditBalance(BaseModel): + available_cents: int # Available credit balance in cents + currency: str # Currency code (e.g. usd) + has_payment_method: bool # Whether a saved payment method exists + + +# API schema for a custom object. +class CustomObject(BaseModel): + created_at: str | None = None # Created timestamp + fields: dict[str, object] | None = None # Object field values + id: str # Public ID (cobj_...) + org: str | None = None # Organization + row_key: str | None = None # Row key + sandbox: str | None = None # Sandbox identifier + schema_type: str | None = None # Schema type (lookup_key) + team: str | None = None # Owning team + updated_at: str | None = None # Updated timestamp + user: str | None = None # Owning user + version: int | None = None # Aggregate version for OCC + + +# Schema for a dataset dimension field. +class DatasetDimension(BaseModel): + label: str # Human-readable label + name: str # Dimension identifier + type: str # Data type (string, integer, boolean, datetime, date) + + +# Schema for a dataset metric field. +class DatasetMetric(BaseModel): + aggregation: str # Aggregation function (sum, count, avg, min, max, count_distinct) + label: str # Human-readable label + name: str # Metric identifier + type: str # Output type (integer, float) + + +# Schema for an analytics dataset definition. +class Dataset(BaseModel): + dimensions: list[DatasetDimension] # Available dimensions + metrics: list[DatasetMetric] # Available metrics + name: str # Dataset identifier + time_dimension: DatasetDimension | None = None # Time dimension for time series queries + + +# Schema for a dataset query result. +class DatasetQueryResult(BaseModel): + columns: list[dict[str, object]] # Column definitions with name, type, and label + meta: dict[str, object] # Query metadata (total_rows, query_time_ms) + rows: list[dict[str, object]] # Result rows as maps of column_name → value + + +# Schema for delete operations that return a deleted count. +class DeletionConfirmation(BaseModel): + deleted_count: int # Number of deleted records + + +# Schema for developer account billing settings. +class Developer(BaseModel): + auto_reup_amount_cents: int | None = None + auto_reup_enabled: bool | None = None # Whether automatic credit reup is enabled + auto_reup_threshold_cents: int | None = None + billing_provider_environment: str | None = None # Stripe environment (live or test) + billing_provider_id: str | None = None # Stripe customer ID + pending_plan: str | None = None + plan: str | None = None # Active billing plan (e.g. paygo) + + +# Schema for an organization. +# Maps to serialized org output from developer portal API. +class DeveloperOrg(BaseModel): + created_at: str | None = None # Created timestamp + description: str | None = None # Organization description + domain: str # Primary domain + id: str # Public ID (org_...) + industry: str | None = None # Industry category + name: str # Organization name + sandbox: str | None = None # Sandbox identifier (nullable) + slug: str # URL-safe slug + status: str | None = None # Status (active, suspended, trialing) + updated_at: str | None = None # Updated timestamp + website: str | None = None # Website URL + + +# Schema for a system access token (developer admin view). +# Maps to serialized system access token output from developer portal API. +class DeveloperSystemAccessToken(BaseModel): + created_at: str | None = None # Created timestamp + id: str # Public ID (sat_...) + last_used_at: str | None = None # Last used timestamp + name: str | None = None # Optional label for the token + revoked_at: str | None = None # Revoked timestamp + token: str | None = None # Raw JWT (only present on creation) + + +# Schema for a thread in the developer portal. +# Maps to serialized thread output from developer portal API. +class DeveloperThread(BaseModel): + app_name: str | None = None # Associated app name + created_at: str | None = None # Created timestamp + id: str # Public ID (thr_...) + is_channel: bool | None = None # Whether this is a channel thread + is_default: bool | None = None # Whether this is the default thread + is_unlisted: bool | None = None # Whether thread is hidden from listings + key: str | None = None # Unique key within owner scope + org: str | None = None # Organization (nullable) + owner: str | None = None # Owner public + owner_name: str | None = None # Owner display name + owner_type: str | None = None # Owner type: team, user, agent, or nil + sandbox: str | None = None # Sandbox identifier (nullable) + slug: str | None = None # URL-friendly slug + title: str # Thread title + updated_at: str | None = None # Updated timestamp + + +# Schema for a user (developer admin view). +# Maps to serialized user output from developer portal API. +class DeveloperUser(BaseModel): + alias: str | None = None # User alias + confirmed_at: str | None = None # Email confirmed timestamp + created_at: str | None = None # Created timestamp + email: str # Email address + full_name: str | None = None # Full name + id: str # Public ID (usr_...) + is_system_user: bool | None = None # Whether this is a system user + org: str | None = None # Organization (nullable) + org_name: str | None = None # Organization display name (nullable) + org_role: str | None = None # Organization role (admin, member, viewer; nullable) + password: str | None = None # Temporary plaintext password returned on reset + sandbox: str | None = None # Sandbox identifier (nullable) + updated_at: str | None = None # Updated timestamp + + +# API schema for OAuth device authorization responses. +class DeviceAuthorizationResponse(BaseModel): + device_code: str # Device verification code + expires_in: int # TTL in seconds + interval: int # Polling interval in seconds + user_code: str # User-facing verification code + verification_uri: str # Base verification URI + verification_uri_complete: str # Full verification URI with code + + +# API schema for OAuth device authorization approval and denial responses. +class DeviceAuthorizationStatusResponse(BaseModel): + status: str # Authorization status (approved or denied) + + +# Schema for a registered domain. +# Maps to serialized domain output from developer portal API. +class Domain(BaseModel): + created_at: str | None = None # Created timestamp + domain: str # Domain name + id: str # Public ID (dad_...) + updated_at: str | None = None # Updated timestamp + verified: bool | None = None # Whether domain is verified + + +# Schema for a developer portal domain event. +class DomainEvent(BaseModel): + agent: str | None = None # Agent identifier + created_at: str | None = None # Created timestamp + event_name: str # Event name + id: str # Domain event ID + idempotency_key: str | None = None # Idempotency key for the event + payload: dict[str, object] # Event payload + team: str | None = None # Team identifier + user: str | None = None # User identifier + + +# Schema for paginated domain event responses. +class DomainEventPage(BaseModel): + data: list[DomainEvent] # Domain events + has_next: bool # Whether a next page exists + has_prev: bool # Whether a previous page exists + page: int # Current page number + page_size: int # Page size + total_entries: int # Total entries + total_pages: int # Total pages + + +# Schema for an encrypted secret payload. +class EncryptedSecret(BaseModel): + encrypted_value: str # Encrypted ciphertext value + + +# Schema for an eval result. +class EvalResult(BaseModel): + agent_response: str | None = None # Agent response + created_at: str | None = None # Created timestamp + duration_ms: int | None = None # Execution duration + grader_details: dict[str, object] | None = None # Grader details + id: str # Eval result ID + run: str # Parent eval run identifier + score: float | None = None # Result score + status: str # Result status + task: str | None = None # Eval task identifier + task_input: str | None = None # Task input summary + transcript: dict[str, object] | None = None # Execution transcript + updated_at: str | None = None # Updated timestamp + + +# Schema for eval result list responses. +class EvalResultList(BaseModel): + data: list[EvalResult] # Eval results + + +# Schema for an eval run. +class EvalRun(BaseModel): + agent: str | None = None # Agent identifier + agent_name: str | None = None # Agent name + completed_at: str | None = None # Completion timestamp + created_at: str | None = None # Created timestamp + id: str # Eval run ID + results: list[EvalResult] | None = None # Eval results for this run + started_at: str | None = None # Start timestamp + status: str # Run status + suite: str # Eval suite identifier + summary: dict[str, object] | None = None # Aggregate run summary + updated_at: str | None = None # Updated timestamp + + +# Schema for eval run list responses. +class EvalRunList(BaseModel): + data: list[EvalRun] # Eval runs + + +# Schema for an eval task. +class EvalTask(BaseModel): + created_at: str | None = None # Created timestamp + expected_outcome: str # Expected outcome + grading_criteria: list[dict[str, object]] | None = None # Grading criteria + id: str # Eval task ID + input_message: str # Task input message + mock_agent_memory: dict[str, object] | None = None # Mock agent memory + mock_context_items: list[dict[str, object]] | None = None # Mock context items + mock_datetime: str | None = None # Mock datetime + mock_participants: list[dict[str, object]] | None = None # Mock participants + mock_tools: list[dict[str, object]] | None = None # Mock tool definitions + status: str # Task status + suite: str # Parent eval suite identifier + updated_at: str | None = None # Updated timestamp + + +# Schema for an eval suite. +class EvalSuite(BaseModel): + created_at: str | None = None # Created timestamp + description: str | None = None # Suite description + id: str # Eval suite ID + name: str # Suite name + org: str | None = None # Owner organization identifier + status: str # Suite status + tasks: list[EvalTask] | None = None # Eval tasks included in the suite + updated_at: str | None = None # Updated timestamp + user: str | None = None # Owner user identifier + + +# Schema for eval suite list responses. +class EvalSuiteList(BaseModel): + data: list[EvalSuite] # Eval suites + + +# Schema for eval task list responses. +class EvalTaskList(BaseModel): + data: list[EvalTask] # Eval tasks + + +# Schema for an event catalog entry with full payload schema and sample. +class EventCatalogEntry(BaseModel): + description: str # Human-readable description + name: str # Event name (e.g., thread.created) + parent: str | None = None # Parent envelope name (only present for sub_event entries) + sample: dict[str, object] # Sample payload for this event + schema: dict[str, object] # JSON Schema describing the event payload + sub_events: list[str] | None = None + type: str | None = None + + +# Schema for an event type available for automation triggers. +# Represents events from the workflow event catalog. +class EventType(BaseModel): + description: str # Human-readable description + name: str # Event name (e.g., thread.created) + + +# API schema for file URL refresh responses. +class FileRefreshResult(BaseModel): + image_source: ImageSource # Updated image source with fresh URL + success: bool # Whether the refresh succeeded + + +# API schema for an installation. +class Installation(BaseModel): + agent: str | None = None # Owning agent + config: dict[str, object] | None = None # Configuration + created_at: str | None = None # Creation timestamp + id: str # Installation ID (cin_...) + kind: str | None = None # Installation kind + shared_integration: str | None = None # Bound shared integration + state: str | None = None # Installation state + status_payload: dict[str, object] | None = None # Status payload + updated_at: str | None = None # Last update timestamp + + +# API schema for an installation kind. +class InstallationKind(BaseModel): + accepts_sources: bool | None = None # Whether this kind accepts sources + category: str | None = None # Category + config_schema: dict[str, object] | None = None # JSON schema for configuration + description: str | None = None # Description + kind: str # Installation kind identifier + label: str | None = None # Display label + provider: str | None = None # Integration provider + requires_integration: bool | None = None # Whether this kind requires an integration + + +# List response for installation kinds. +class InstallationKindListResponse(BaseModel): + data: list[InstallationKind] # List of installation kinds + + +# List response for installations. +class InstallationListResponse(BaseModel): + data: list[Installation] # List of installations + + +# API schema for an installation source. +class InstallationSource(BaseModel): + agent: str | None = None # Owning agent + context_installation: str | None = None # Installation ID + created_at: str | None = None # Creation timestamp + id: str # Source ID (cso_...) + metadata: dict[str, object] | None = None # Arbitrary metadata + parent_source: str | None = None # Parent source ID + payload: dict[str, object] | None = None # Source payload + state: str | None = None # Source state + team: str | None = None # Team ID + thread: str | None = None # Thread ID + type: str | None = None # Source type + updated_at: str | None = None # Last update timestamp + user: str | None = None # User ID + + +# List response for installation sources. +class InstallationSourceListResponse(BaseModel): + data: list[InstallationSource] # List of installation sources + + +# Schema for integration records with connector state. +class Integration(BaseModel): + id: str # Integration ID + org: str | None = None # Organization (nullable) + provider: str # Provider identifier + sandbox: str | None = None # Sandbox identifier (nullable) + secret_group: str | None = None # Secret group + state: dict[str, object] # Connector state information + team: str | None = None # Team (if team-scoped) + user: str | None = None # User (if user-scoped) + workspace_key: str | None = None # Provider workspace identifier + + +# Schema for integration action metadata. +class IntegrationAction(BaseModel): + description: str | None = None # Action description + json_schema: dict[str, object] # JSON Schema for action parameters + key: str # Action key (e.g., gmail.list_messages) + scopes_any_of: dict[str, object] | None = None # Required scope sets for this action + + +# Integration fields for auto-creating the underlying integration. +# When creating an agent installation for an `integration/*` kind, callers +# can pass this object to auto-create the underlying Integration record. +# Required fields depend on the kind's auth type: +# - `app_installation` kinds (slack_bot, github_app): requires `installation_id` +# - `oauth` kinds (gmail, outlook, slack): requires `access_token` +class IntegrationCreateParams(BaseModel): + access_token: str | None = None # OAuth access token or API key + installation_id: str | None = None + metadata: dict[str, object] | None = None # Provider-specific metadata (e.g. bot_user_id) + refresh_token: str | None = None # OAuth refresh token + workspace_key: str | None = None # Workspace name or identifier + + +# Schema for an integration provider entry. +# Represents an available integration provider (OAuth or MCP) that can be +# used with `create integration --provider `. +class IntegrationProvider(BaseModel): + auth_type: str # Auth mechanism: oauth, bearer, or app_installation + description: str | None = None # Short description of the provider + display_name: str # Human-readable display name + provider: str # Provider key (e.g., google, mcp:system:mcp-github) + type: str # Provider type: oauth, mcp, or app_installation + + +# Schema for a key-value storage entry. +# Maps exactly to render_entry/1 output in ApiStorageController. +class KeyValueStorageEntry(BaseModel): + created_at: str | None = None # Creation timestamp + key: str # Storage key + updated_at: str | None = None # Last update timestamp + user: str # User + value: str # Stored value + + +# List response for key-value storage entries. +class KeyValueStorageEntryList(BaseModel): + data: list[KeyValueStorageEntry] # Storage entries owned by the caller + + +# API schema for a knowledge search result item. +class KnowledgeSearchResult(BaseModel): + content: str | None = None # Normalized content text + content_type: str | None = None # Content MIME type + created_at: str | None = None # Creation timestamp + id: str # Item ID (cim_...) + metadata: dict[str, object] | None = None # Additional metadata + raw_content: dict[str, object] | None = None # Raw content data + type: str | None = None # Source type (requires preloaded :source association) + + +# Schema for an LLM session call. +# Maps to serialized LLM call output from developer portal API. +class LlmCall(BaseModel): + call_id: str # Unique call UUID + completion_tokens: int # Number of completion tokens + created_at: str | None = None # Created timestamp + error_message: str | None = None # Error message if call failed + id: str # Public ID (alc_...) + latency_ms: int # Latency in milliseconds + message_count: int | None = None # Number of messages included in the call metadata + metadata: dict[str, object] | None = None # Additional metadata + model: str | None = None # LLM model name + prompt_tokens: int # Number of prompt tokens + session_id: str # Session UUID grouping related calls + source: str | None = None # Machine-readable call source key + status: str | None = None # Call status (success or error) + team: str | None = None # Team if team-scoped + total_tokens: int # Total tokens (prompt + completion) + trajectory: str | None = None # Trajectory identifier + user: str | None = None # User if user-scoped + + +# Source option metadata for LLM call filtering. +class LlmCallSourceOption(BaseModel): + label: str # Human-friendly source label + source: str # Machine-readable LLM source key + + +# Schema for LLM call trajectory contents. +class LlmCallTrajectory(BaseModel): + download_url: str | None = None # Signed transcript download URL + messages: list[dict[str, object]] # Trajectory messages + + +# API schema for a media item. +class Media(BaseModel): + content_type: str | None = None # Original variant content type + created_at: str | None = None # Creation timestamp + filename: str | None = None # Original variant filename + height: int | None = None # Original variant height + id: str # Media ID + media_type: str | None = None # Media type + name: str | None = None # Media name + org: str | None = None # Organization + sandbox: str | None = None # Sandbox + updated_at: str | None = None # Last update timestamp + url: str | None = None # Original variant URL + variants: list[MediaVariant] | None = None # Media variants + width: int | None = None # Original variant width + + +# Schema for inline message reactions. +# This is the compact format used in Message.reactions[], which differs from +# the full Reaction schema used in standalone reaction endpoints. +# Maps to format_reactions_for_client/1 output. +class MessageReaction(BaseModel): + payload: dict[str, object] | None = None # Reaction payload (e.g., {emoji: '👍'}) + type: str # Reaction type (e.g., emoji_reaction) + user: str | None = None # User who added the reaction + + +# API schema for a chat message. +class Message(BaseModel): + actors: list[Actor] | None = None # Message actors + agent: str | None = None # Agent if sent by an agent user + attachments: list[Attachment] | None = None # Message attachments + branched_thread: str | None = None # Branched thread (if message spawned a thread) + content: str | None = None # Message content + created_at: str | None = None # Creation timestamp + has_replies: bool | None = None # Whether message has replies + id: str # Message ID (msg_...) + idempotency_key: str | None = None # Client-provided idempotency key + legacy_agent: str | None = None # Legacy agent if sent by legacy chat agent + metadata: dict[str, object] | None = None # Message metadata + org: str | None = None # Organization + reactions: list[MessageReaction] | None = None # Message reactions + rendering_mode: str | None = None # Rendering mode hint + replies: list[dict[str, object]] | None = None # Inline replies (if loaded) + replies_after_cursor: str | None = None # Cursor for replies pagination + replies_before_cursor: str | None = None # Cursor for replies pagination + reply_count: int | None = None # Number of replies + reply_to: dict[str, object] | None = None # Parent message object (if loaded) + sandbox: str | None = None # Sandbox identifier + team: str | None = None # Team + thread: str | None = None # Parent thread + user: str | None = None # Author user (public ID or expanded object when loaded) + + +# Schema for message update request parameters. +# Used as the body wrapper when updating a message. +class MessageUpdateParams(BaseModel): + content: str | None = None # New message content + + +# Filter object for matching thread metadata. +class MetadataFilter(BaseModel): + key: str # Metadata key to match + type: str # Filter type (must be "metadata") + value: str # Metadata value to match + + +# Schema for a single notification setting update entry. +class NotificationSettingUpdate(BaseModel): + level: str # Notification level (global, team, thread) + muted: bool # Whether notifications are muted + team: str | None = None # Team (required for team-level settings) + thread: str | None = None # Thread (required for thread-level settings) + + +# Schema for an OAuth client registration. +class OAuthClient(BaseModel): + client_id: str # Public OAuth client ID + client_name: str # Display name for the client + client_secret: str | None = None # Client secret shown only at creation + created_at: str | None = None # Created timestamp + enabled: bool # Whether the client is enabled + id: str # OAuth client registration ID + redirect_uris: list[str] # Allowed redirect URIs + scopes: list[str] # Allowed OAuth scopes + updated_at: str | None = None # Updated timestamp + + +# Schema for OAuth client list responses. +class OAuthClientList(BaseModel): + data: list[OAuthClient] # OAuth clients + + +# OAuth error response per RFC 6749 +class OAuthError(BaseModel): + error: str # Error code (e.g. slow_down, invalid_grant) + error_description: str | None = None # Human-readable error description + + +# Schema for an OAuth provider configuration. +# Maps to serialized OAuth provider output from developer portal API. +class OAuthProvider(BaseModel): + callback_urls: list[str] | None = None # Allowed callback URLs + client_id: str # OAuth client ID + created_at: str | None = None # Created timestamp + display_name: str | None = None # Display name + enabled: bool | None = None # Whether provider is enabled + id: str # Public ID (dop_...) + provider: str # Provider type (github, google) + scopes: list[str] | None = None # OAuth scopes + updated_at: str | None = None # Updated timestamp + + +# API schema for OAuth token endpoint responses. +class OAuthTokenResponse(BaseModel): + access_token: str # OAuth access token + expires_in: int # Token TTL in seconds + refresh_token: str | None = None # OAuth refresh token + scope: str | None = None # Granted scopes (space-separated) + token_type: str # Token type (Bearer) + user: User | None = None # Authenticated user + + +# Schema for paginated messages response. +# Used by thread messages list endpoints. +class PaginatedMessages(BaseModel): + after_cursor: str | None = None # Cursor for fetching items after this point + before_cursor: str | None = None # Cursor for fetching items before this point + messages: list[Message] # List of message objects + + +# Schema for paginated message replies response. +# Used by message replies list endpoints. +# Note: This response is NOT wrapped in a "data" field. +class PaginatedReplies(BaseModel): + after_cursor: str | None = None # Cursor for fetching items after this point + before_cursor: str | None = None # Cursor for fetching items before this point + has_more: bool | None = None # Whether more replies exist beyond the current page + replies: list[Message] # List of reply message objects + total_count: int | None = None # Total number of replies + + +# Schema for password update parameters. +class PasswordUpdateParams(BaseModel): + password: str # New password + password_confirmation: str # New password confirmation + + +# API schema for a persona. +class Persona(BaseModel): + activated: bool | None = None # Whether persona is activated + agent: str | None = None # Associated agent (always nil) + created_at: str | None = None # Creation timestamp + id: str # Persona ID + is_active: bool | None = None # Whether persona is active + is_enabled_for_thread: bool | None = None # Whether persona is enabled for thread + metadata: dict[str, object] | None = None # Persona metadata + name: str | None = None # Persona display name + org: str | None = None # Organization + personality: str | None = None # Persona personality description + sandbox: str | None = None # Sandbox + team: str | None = None # Owning team + updated_at: str | None = None # Last update timestamp + user: str | None = None # Owning user + + +# Schema for persona creation parameters. +class PersonaCreateParams(BaseModel): + name: str # Persona display name + personality: str # Persona personality description + profile_picture_style: str | None = None # Profile picture generation style + + +# Schema for persona update parameters. +class PersonaUpdateParams(BaseModel): + metadata: dict[str, object] | None = None # Additional metadata + name: str | None = None # Persona display name + personality: str | None = None # Persona personality description + + +# Schema for picture upload parameters. +# Used for uploading profile pictures via base64 encoded data. +class PictureParams(BaseModel): + data: str # Base64 encoded image data + filename: str # Original filename + mime_type: str # MIME type of the image + + +# Public org schema for authenticated endpoints. +# Only exposes fields safe for any authenticated user: id, name, domain, +# and logo. Does NOT expose sandbox, status, industry, description, or +# other internal fields that the Developer.Org schema includes. +class PublicOrg(BaseModel): + domain: str # Primary domain + id: str # Public ID (org_...) + name: str # Organization name + + +# API schema for push notification test results. +class PushNotificationResult(BaseModel): + results: list[dict[str, object]] | None = None # Per-device results + success: bool # Whether the notification was sent + total_sent: int | None = None # Number of notifications sent + + +# A routine preset with its metadata. +class RoutinePreset(BaseModel): + config: dict[str, object] | None = None # Default configuration + description: str | None = None # Preset description + event_type: str | None = None # Event type + label: str | None = None # Display label + name: str # Preset name + + +# Schema for runtime environment variable metadata. +class RuntimeEnvVar(BaseModel): + description: str | None = None # Optional description + key: str # Environment variable key + source: str # Source of the env var (app or org) + + +# Schema for runtime environment variable metadata list responses. +class RuntimeEnvVarList(BaseModel): + data: list[RuntimeEnvVar] + + +# Schema for a sandbox-captured email. +class SandboxEmail(BaseModel): + bcc: list[dict[str, object]] | None = None # BCC recipients + cc: list[dict[str, object]] | None = None # CC recipients + created_at: str | None = None # When the email was captured + from_address: str # Sender email address + from_name: str | None = None # Sender display name + headers: dict[str, object] | None = None # Custom email headers + html_body: str | None = None # HTML body + id: str # Public ID (sem_...) + reply_to: dict[str, object] | None = None # Reply-to address + subject: str | None = None # Email subject + text_body: str | None = None # Plain text body + to: list[dict[str, object]] # Recipients [{name, address}] + + +# API schema for a scrape result. +class Scrape(BaseModel): + created_at: str | None = None # Creation timestamp + description: str | None = None # Page description + id: str # Scrape ID (scp_...) + image_height: int | None = None # Image height in pixels + image_url: str | None = None # Image URL + image_width: int | None = None # Image width in pixels + last_scraped_at: str | None = None # Last scraped timestamp + metadata: dict[str, object] | None = None # Scrape metadata + status: str | None = None # Scrape status + title: str | None = None # Page title + updated_at: str | None = None # Last update timestamp + url: str | None = None # Scraped URL + version: int | None = None # Scrape version + + +# API schema for a secret (user, team, or user-team). +class Secret(BaseModel): + created_at: str | None = None # Creation timestamp + description: str | None = None # Secret description + id: str # Secret ID + last_accessed_at: str | None = None # Last accessed timestamp + name: str | None = None # Secret name + org: str | None = None # Organization + sandbox: str | None = None # Sandbox + secret_group: str | None = None # Secret group + team: str | None = None # Team + updated_at: str | None = None # Last update timestamp + + +# API schema for a decrypted secret value response. +class SecretValue(BaseModel): + name: str # Secret name + value: str # Decrypted secret value + + +# API schema for the status/ping health check response. +class StatusPing(BaseModel): + success: bool # Whether the ping succeeded + token: dict[str, object] # Token status details + user: User | None = None # Authenticated user (if token is valid) + + +# API schema for a storage file. +class StorageFile(BaseModel): + content_type: str | None = None # MIME content type + created_at: str | None = None # Creation timestamp + filename: str | None = None # Original filename + id: str # File ID + image_source: ImageSource | None = None # Image source metadata + org: str | None = None # Organization + sandbox: str | None = None # Sandbox + size: int | None = None # File size in bytes + updated_at: str | None = None # Last update timestamp + url: str | None = None # Signed download URL + + +# API schema for a task. +class Task(BaseModel): + closed_at: str | None = None # When the task was closed + comments_count: int | None = None # Number of comments + created_at: str | None = None # Creation timestamp + created_by: str | None = None # Legacy creator + created_by_actor: dict[str, object] | None = None # Creator actor details + created_by_persona: str | None = None # Creator persona + created_by_type: str | None = None # Creator type (user, agent) + created_by_user: str | None = None # Creator user + description: str | None = None # Task description + due_date: str | None = None # Due date + id: str # Task ID (tsk_...) + links: dict[str, object] | None = None # Related links + metadata: dict[str, object] | None = None # Additional metadata + name: str # Task name + org: str | None = None # Organization + owner: str | None = None # Legacy owner + owner_actor: dict[str, object] | None = None # Owner actor details + owner_persona: str | None = None # Owner persona + owner_user: str | None = None # Owner user + priority: int | None = None # Priority level (0-4) + sandbox: str | None = None # Sandbox identifier + status: str # Task status + team: str | None = None # Team + updated_at: str | None = None # Last update timestamp + + +# Schema for a task activity entry. +# Maps exactly to the activity entries produced by TaskActivitySentenceGenerator. +class TaskActivityEntry(BaseModel): + event_id: str | None = None # Event ID + event_type: str | None = None # Type of event + sentence: str | None = None # Human-readable description of the activity + timestamp: str | None = None # When the event occurred + + +# API schema for a task comment. +class TaskComment(BaseModel): + author: str | None = None # Legacy author + author_actor: dict[str, object] | None = None # Author actor details + author_persona: str | None = None # Author persona + author_user: str | None = None # Author user + body: str # Comment body text + created_at: str | None = None # Creation timestamp + id: str # Comment ID + org: str | None = None # Organization + sandbox: str | None = None # Sandbox identifier + task: str | None = None # Task + team: str | None = None # Team + updated_at: str | None = None # Last update timestamp + + +# Schema for task creation parameters. +# Used by both Users.Tasks.Create and Teams.Tasks.Create actions. +class TaskCreateParams(BaseModel): + description: str | None = None # Task description + due_date: str | None = None # Due date + links: dict[str, object] | None = None # Related links + metadata: dict[str, object] | None = None # Additional metadata + name: str # Task name + owner_persona: str | None = None # Owner persona if assigned to agent + owner_user: str | None = None # Owner user if assigned to user + priority: int | None = None # Priority level (0-4) + status: str | None = None # Task status (open, in_progress, done) + task: str | None = None # Custom task ID (optional, auto-generated if not provided) + + +# Schema for task update parameters. +# Used by both Users.Tasks.Update and Teams.Tasks.Update actions. +# All fields are optional since updates only modify provided fields. +class TaskUpdateParams(BaseModel): + description: str | None = None # Task description + due_date: str | None = None # Due date + links: dict[str, object] | None = None # Related links + metadata: dict[str, object] | None = None # Additional metadata + name: str | None = None # Task name + owner_persona: str | None = None # Owner persona if assigned to agent + owner_user: str | None = None # Owner user if assigned to user + priority: int | None = None # Priority level (0-4) + status: str | None = None # Task status (open, in_progress, done) + + +# Filter object for matching personas by template ID. +class TemplateFilter(BaseModel): + id: str # Persona template ID to match + type: str # Filter type (must be "template") + + +# API schema for an AI trajectory. +class Trajectory(BaseModel): + created_at: str | None = None # Creation timestamp + file: str | None = None # Storage file + id: str # Trajectory ID (trj_...) + messages: dict[str, object] | None = None # Trajectory messages + org: str | None = None # Organization + sandbox: str | None = None # Sandbox + team: str | None = None # Team + updated_at: str | None = None # Last update timestamp + + +# Schema for config validation result. +class ValidationResult(BaseModel): + errors: list[str] | None = None # List of validation errors + valid: bool # Whether the config is valid + warnings: list[str] | None = None # Optional warnings emitted during validation + + +# Schema for a developer webhook. +# Maps to serialized webhook output from developer portal API. +class Webhook(BaseModel): + context_installation: str | None = None # Bound context installation + context_sources: list[str] | None = None # Bound context sources + created_at: str | None = None # Created timestamp + enabled: bool # Whether the webhook is enabled + id: str # Public ID (whk_...) + lookup_key: str | None = None # Lookup key for generic webhooks + metadata: dict[str, object] | None = None # Additional metadata + provider: str | None = None + updated_at: str | None = None # Updated timestamp + webhook_url: str # URL to send webhooks to + + +# Schema for a webhook event. +# Maps to serialized webhook event output from developer portal API. +class WebhookEvent(BaseModel): + created_at: str | None = None # Created timestamp + error: str | None = None # Error message if failed + event_type: str | None = None # Event type from the provider + headers: dict[str, object] | None = None # Request headers + id: str # Public ID (whe_...) + payload: dict[str, object] | None = None # Event payload + processed_at: str | None = None # When the event was processed + status: str # Processing status (pending, processed, failed) + + +# API schema for a working memory entry. +class WorkingMemoryEntry(BaseModel): + agent: str | None = None # Owning agent + created_at: str | None = None # Creation timestamp + expires_at: str | None = None # Expiration timestamp + id: str # Memory entry ID (amm_...) + key: str | None = None # Memory key + updated_at: str | None = None # Last update timestamp + value: str | None = None # Memory value + + +# Paginated list response for working memory entries. +class WorkingMemoryEntryListResponse(BaseModel): + data: list[WorkingMemoryEntry] # List of working memory entries + has_next: bool | None = None # Whether a next page exists + has_prev: bool | None = None # Whether a previous page exists + page: int | None = None # Current page number + page_size: int | None = None # Results per page + total_entries: int | None = None # Total number of entries + total_pages: int | None = None # Total number of pages diff --git a/src/archastro/platform/types/config.py b/src/archastro/platform/types/config.py new file mode 100644 index 0000000..bbb935f --- /dev/null +++ b/src/archastro/platform/types/config.py @@ -0,0 +1,69 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 281daa2b3413 + + +from pydantic import BaseModel + + +# API schema for a config version. +class ConfigVersion(BaseModel): + change_description: str | None = None # Description of changes + created_at: str | None = None # Creation timestamp + data: dict[str, object] | None = None # Additional structured data + id: str # Config version ID (cfv_...) + org: str | None = None # Organization + sandbox: str | None = None # Sandbox + version_number: int # Version number + + +# API schema for a config resource. +class Config(BaseModel): + created_at: str | None = None # Creation timestamp + current_version: ConfigVersion | None = None # Current version + id: str # Config ID (cfg_...) + is_archived: bool | None = None # Whether config is archived + kind: str # Config kind (e.g., Agent, APITool) + lookup_key: str | None = None # Optional lookup key + mime_type: str | None = None # Content mime type + org: str | None = None # Organization + raw_content: str | None = None # Raw file content (system configs only) + sandbox: str | None = None # Sandbox identifier + team: str | None = None # Team + updated_at: str | None = None # Last update timestamp + user: str | None = None # User + virtual_path: str | None = None # Unique path within the team + + +# Schema for a config kind in the list response. +class ConfigKind(BaseModel): + classification: str # Kind classification: root or supplemental + description: str | None = None + kind: str # The config kind name (e.g., Agent, APITool) + sample_available: bool # Whether a YAML sample is available + schema_available: bool # Whether a JSON schema is available + + +# Schema for a config kind's JSON schema and sample response. +class ConfigKindSchema(BaseModel): + json_schema: dict[str, object] | None = None + kind: str # The config kind name + sample_yaml: str | None = None + + +# Schema for batch-loaded config content and metadata. +class ConfigLoadResult(BaseModel): + content: dict[str, object] # Fully resolved content tree + metadata: dict[str, object] # Metadata map keyed by JSON path + + +# Schema for a sample config payload. +class ConfigSample(BaseModel): + kind: str # Config kind + mime_type: str # Content MIME type + sample_yaml: str # Sample YAML content + + +# Schema for batch-save config metadata. +class ConfigSaveResult(BaseModel): + metadata: dict[str, object] # Updated metadata with version numbers diff --git a/src/archastro/platform/types/image.py b/src/archastro/platform/types/image.py new file mode 100644 index 0000000..f22b851 --- /dev/null +++ b/src/archastro/platform/types/image.py @@ -0,0 +1,17 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 98ae9c8bfd2f + + +from pydantic import BaseModel + + +# API schema for image source metadata. +class ImageSource(BaseModel): + file: str | None = None # Storage file + height: int | None = None # Image height in pixels + media: str | None = None # Media + mime_type: str | None = None # Image MIME type + refresh_url: str | None = None # URL to refresh signed URL + url: str | None = None # Image URL + width: int | None = None # Image width in pixels diff --git a/src/archastro/platform/types/members.py b/src/archastro/platform/types/members.py new file mode 100644 index 0000000..f89d826 --- /dev/null +++ b/src/archastro/platform/types/members.py @@ -0,0 +1,36 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 312f3a1abc69 + + +from pydantic import BaseModel + + +# Associated account +class MemberAccount(BaseModel): + alias: str | None = None # Account alias + email: str # Account email + full_name: str | None = None # Full name + id: str # Account public ID (dva_...) + + +# Schema for an app member (account with role on an app). +# Maps to serialized membership output from developer portal API. +class Member(BaseModel): + account: MemberAccount # Associated account + created_at: str | None = None # Created timestamp + id: str # Public ID (dar_...) + role: str # Role (admin, developer) + updated_at: str | None = None # Updated timestamp + + +# Schema for a pending member invite. +# Maps to serialized invite output from developer portal API. +class MemberInvite(BaseModel): + email: str # Invitee's email address + expires_at: str # When the invite expires + id: str # Public ID (ami_...) + invited_at: str # When the invite was created + inviter: dict[str, object] | None = None # Account that sent the invite + role: str # Role (admin, developer) + status: str # Invite status (pending or expired) diff --git a/src/archastro/platform/types/orgs.py b/src/archastro/platform/types/orgs.py new file mode 100644 index 0000000..c7b2338 --- /dev/null +++ b/src/archastro/platform/types/orgs.py @@ -0,0 +1,71 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 7dd0f65b6882 + + +from pydantic import BaseModel + + +# API schema for an organization. +class Org(BaseModel): + created_at: str | None = None # Creation timestamp + description: str | None = None # Description + domain: str | None = None # Domain + id: str # Organization ID (org_...) + industry: str | None = None # Industry + name: str | None = None # Organization name + sandbox: str | None = None # Sandbox + slug: str | None = None # URL slug + status: str | None = None # Status + updated_at: str | None = None # Last update timestamp + website: str | None = None # Website URL + + +# Schema for org auth policy response. +class OrgAuthPolicy(BaseModel): + allowed_email_domains: list[str] # Allowed email domains + auth_method: str # Auth method (default, sso) + require_2fa: bool # Whether 2FA is required + sso_providers: list[str] # Enabled SSO providers + + +# Schema for an organization environment variable response that includes the +# plaintext value. +class OrgEnvVar(BaseModel): + created_at: str | None = None # Created timestamp + description: str | None = None # Optional description + id: str # Organization env var ID (oev_...) + key: str # Environment variable key + updated_at: str | None = None # Updated timestamp + value: str # Environment variable value + + +# Schema for an organization environment variable response that masks the +# secret value. +class OrgEnvVarMasked(BaseModel): + created_at: str | None = None # Created timestamp + description: str | None = None # Optional description + id: str # Organization env var ID (oev_...) + key: str # Environment variable key + masked_value: str # Masked environment variable value + updated_at: str | None = None # Updated timestamp + + +# Schema for organization environment variable list responses. +class OrgEnvVarMaskedList(BaseModel): + data: list[OrgEnvVarMasked] # Organization environment variables + + +# Schema for SAML provider settings response. +# Masks certificate contents exposes only boolean flags indicating whether +# a primary/secondary certificate is configured. +class OrgSamlSettings(BaseModel): + created_at: str | None = None # Created timestamp + enabled: bool | None = None # Whether the provider is active + entity_id: str # IdP entity ID + has_certificate: bool | None = None # Whether a primary certificate is configured + has_certificate_secondary: bool | None = None + id: str # SAML provider ID (saml_...) + label: str | None = None # Display label for the SSO button + sso_url: str # IdP SSO URL + updated_at: str | None = None # Updated timestamp diff --git a/src/archastro/platform/types/reactions.py b/src/archastro/platform/types/reactions.py new file mode 100644 index 0000000..2108568 --- /dev/null +++ b/src/archastro/platform/types/reactions.py @@ -0,0 +1,17 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 372147c98176 + + +from pydantic import BaseModel + + +# API schema for a message reaction. +class Reaction(BaseModel): + created_at: str | None = None # Creation timestamp + feedback_type: str | None = None # Type of feedback (e.g., emoji_reaction) + id: str # Reaction ID (umf_...) + message: str | None = None # Message the reaction is on + payload: dict[str, object] | None = None # Reaction payload (e.g., {emoji: ...}) + updated_at: str | None = None # Last update timestamp + user: str | None = None # User who added the reaction diff --git a/src/archastro/platform/types/schedules.py b/src/archastro/platform/types/schedules.py new file mode 100644 index 0000000..c26789b --- /dev/null +++ b/src/archastro/platform/types/schedules.py @@ -0,0 +1,18 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 076a326b1561 + + +from pydantic import BaseModel + + +# API schema for a scheduled job. +class ScheduledJob(BaseModel): + args: dict[str, object] | None = None # Job arguments + description: str | None = None # Job description + id: str # Job ID + recurring: str | None = None # Recurrence pattern + scheduled_at: str | None = None # Scheduled execution time + thread: str | None = None # Thread + title: str | None = None # Job title + worker: str | None = None # Worker module name diff --git a/src/archastro/platform/types/system.py b/src/archastro/platform/types/system.py new file mode 100644 index 0000000..d9d2c70 --- /dev/null +++ b/src/archastro/platform/types/system.py @@ -0,0 +1,15 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 287fea7c998c + + +from pydantic import BaseModel + + +# API schema for a system access token. +class SystemAccessToken(BaseModel): + created_at: str | None = None # Creation timestamp + id: str # Token ID (sat_...) + last_used_at: str | None = None # Last used timestamp + name: str | None = None # Token name + revoked_at: str | None = None # Revocation timestamp diff --git a/src/archastro/platform/types/teams.py b/src/archastro/platform/types/teams.py new file mode 100644 index 0000000..6cc9550 --- /dev/null +++ b/src/archastro/platform/types/teams.py @@ -0,0 +1,93 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 72cfccc727f0 + + +from pydantic import BaseModel + +from .agents import Agent +from .common import Acl +from .image import ImageSource +from .users import User + + +# API schema for a team. +class Team(BaseModel): + acl: Acl | None = None + app: str | None = None # Application + badges: dict[str, object] | None = None # Badge counts by category + created_at: str | None = None # Creation timestamp + description: str | None = None # Team description + id: str # Team ID + membership_status: str | None = None + metadata: dict[str, object] | None = None # Team metadata + name: str | None = None # Team name + org: str | None = None # Organization + sandbox: str | None = None # Sandbox + slug: str | None = None # URL slug + updated_at: str | None = None # Last update timestamp + + +# Schema for team creation parameters. +class TeamCreateParams(BaseModel): + acl: Acl | None = None # Access control list + description: str | None = None # Team description + metadata: dict[str, object] | None = None # Arbitrary key-value metadata + name: str # Team name + + +# Schema for a team invite response. +class TeamInvite(BaseModel): + code: str # 6-character invite code + + +# Schema for a team member. +class TeamMember(BaseModel): + alias: str | None = None # User alias + email: str | None = None # User email + full_name: str | None = None # User full name + id: str # Public user ID + role: str | None = None # Role in the team (owner, admin, member) + + +# API schema for a team membership. +class TeamMembership(BaseModel): + agent: Agent | None = None # Agent object (when loaded) + created_at: str | None = None # Creation timestamp + id: str # Membership ID + joined_at: str | None = None # Join timestamp + metadata: dict[str, object] | None = None # Membership metadata + name: str | None = None # Member name + profile_picture: ImageSource | None = None # Profile picture + role: str | None = None # Role in team + team: dict[str, object] | None = None # Team object (when loaded) + type: str | None = None # Member type (user, agent, unknown) + updated_at: str | None = None # Last update timestamp + user: User | None = None # User object (when loaded) + + +# Paginated list response for team memberships. +class TeamMembershipListResponse(BaseModel): + data: list[TeamMembership] # List of team memberships + has_next: bool | None = None # Whether a next page exists + has_prev: bool | None = None # Whether a previous page exists + page: int | None = None # Current page number + page_size: int | None = None # Results per page + total_entries: int | None = None # Total number of entries + total_pages: int | None = None # Total number of pages + + +# Base64-encoded profile picture +class TeamUpdateParamsProfilePicture(BaseModel): + data: str | None = None # Base64 encoded image data + filename: str | None = None # Original filename + mime_type: str | None = None # MIME type of the image + + +# Schema for team update parameters. +class TeamUpdateParams(BaseModel): + acl: Acl | None = None # Access control list + description: str | None = None # Team description + metadata: dict[str, object] | None = None # Arbitrary key-value metadata + name: str | None = None # Team name + profile_picture: TeamUpdateParamsProfilePicture | None = None diff --git a/src/archastro/platform/types/threads.py b/src/archastro/platform/types/threads.py new file mode 100644 index 0000000..60f3e72 --- /dev/null +++ b/src/archastro/platform/types/threads.py @@ -0,0 +1,170 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 18b91477c258 + + +from pydantic import BaseModel + +from .agents import Agent +from .common import Attachment, Message +from .users import User + + +# Schema for thread settings response. +# Used by thread settings show/update endpoints. +class ThreadSettings(BaseModel): + agent_enabled: bool | None = None # Whether the agent is enabled for this thread + + +# API schema for a chat thread. +class Thread(BaseModel): + agent_user: str | None = None # Owning agent user + created_at: str | None = None # Creation timestamp + creator: User | None = None # Creator user object + description: str | None = None # Thread description + id: str # Thread ID (thr_...) + is_channel: bool | None = None # Whether this is a channel + is_default: bool | None = None # Whether this is the default thread + is_transient: bool | None = None # Whether this thread is transient + is_unlisted: bool | None = None # Whether this thread is unlisted + key: str | None = None # Thread key + last_activity: str | None = None # Last activity timestamp + metadata: dict[str, object] | None = None # Thread metadata + org: str | None = None # Organization + parent_message: Message | None = None # Parent message object + participant: list[str] | None = None # Participant users + participants: list[User] | None = None # Participant user objects + participating_actor: list[str] | None = None # Actors participating in thread + participating_agents: list[Agent] | None = None # Agents participating in thread + role: str | None = None # User's role in the thread + sandbox: str | None = None # Sandbox identifier + settings: ThreadSettings | None = None # Thread settings + slug: str | None = None # Thread slug + sub_threads: list[dict[str, object]] | None = None # Sub-threads + team: str | None = None # Owning team + title: str | None = None # Thread title + ttl: int | None = None # Time-to-live in seconds + unread_count: int | None = None # Unread message count + updated_at: str | None = None # Last update timestamp + user: str | None = None # Owning user + + +# API schema for a thread action. +class ThreadAction(BaseModel): + call_to_action: str | None = None # Call to action text + completion_result: dict[str, object] | None = None # Result after action completion + id: str # Thread action ID (tha_...) + metadata: dict[str, object] | None = None # Action metadata + native_template: dict[str, object] | None = None # Native template for mobile clients + org: str | None = None # Organization + path: str | None = None # URL path for action + sandbox: str | None = None # Sandbox + status: str # Action status (active, canceled, done) + type: str # Action type (connect_google, add_credential, send_email) + + +# Base64 encoded profile picture +class ThreadCreateParamsProfilePicture(BaseModel): + data: str | None = None # Base64 encoded image data + filename: str | None = None # Original filename + mime_type: str | None = None # MIME type of the image + + +# Schema for thread creation parameters. +# Used by both Users.Threads.Create and Teams.Threads.Create actions. +class ThreadCreateParams(BaseModel): + create_legacy_agent: bool | None = None # Create a legacy chat agent for this thread + description: str | None = None # Thread description + is_unlisted: bool | None = None # Whether the thread is unlisted + key: str | None = None # Unique key for the thread + metadata: dict[str, object] | None = None # Additional metadata + org_id: str | None = None # Organization ID + profile_picture: ThreadCreateParamsProfilePicture | None = None + settings: ThreadSettings | None = None # Thread settings + title: str | None = None # Thread title + + +# Schema for thread detail response in the developer portal. +# Extended thread data including members and associated entities. +class ThreadDetail(BaseModel): + agent: dict[str, object] | None = None # Associated agent info (id, name) + app_name: str | None = None # Associated app name + created_at: str | None = None # Created timestamp + creator: dict[str, object] | None = None # Thread creator info (id, email, full_name) + id: str # Public ID (thr_...) + is_channel: bool | None = None # Whether this is a channel thread + is_default: bool | None = None # Whether this is the default thread + is_unlisted: bool | None = None # Whether thread is hidden from listings + key: str | None = None # Unique key within owner scope + members: list[dict[str, object]] | None = None # Thread member list + metadata: dict[str, object] | None = None # Thread metadata + org: str | None = None # Organization (nullable) + owner: str | None = None # Owner public + owner_name: str | None = None # Owner display name + owner_type: str | None = None # Owner type: team, user, agent, or nil + sandbox: str | None = None # Sandbox identifier (nullable) + slug: str | None = None # URL-friendly slug + title: str # Thread title + updated_at: str | None = None # Updated timestamp + + +# API schema for a thread member. +class ThreadMember(BaseModel): + membership_type: str | None = None # Membership type (owner or member) + thread: str | None = None # Thread + user: User | None = None # User details (when loaded) + + +# Schema for a thread message in the developer portal. +# Maps to serialized message output from developer portal API. +class ThreadMessage(BaseModel): + admin: dict[str, object] | None = None # Admin-only metadata and trajectory + agent: str | None = None # Agent identifier (nullable) + app: str | None = None # App identifier + attachments: list[Attachment] | None = None # Message attachments + content: str | None = None # Message content + created_at: str | None = None # Created timestamp + id: str # Public ID (msg_...) + org: str | None = None # Organization (nullable) + sandbox: str | None = None # Sandbox identifier (nullable) + sender: str | None = None # Sender public + sender_name: str | None = None # Display name of sender + sender_type: str | None = None # Type: user, agent, or system + team: str | None = None # Team identifier (nullable) + user: str | None = None # User identifier (nullable) + + +# API schema for a thread message trajectory. +class ThreadMessageTrajectory(BaseModel): + agent_message: str | None = None # Agent message + created_at: str | None = None # Creation timestamp + id: str # Thread message trajectory ID (tmt_...) + org: str | None = None # Organization + sandbox: str | None = None # Sandbox + thread: str | None = None # Thread + trajectory: str | None = None # Trajectory + updated_at: str | None = None # Last update timestamp + user_message: str | None = None # User message + + +# Schema for thread read status response. +class ThreadReadStatus(BaseModel): + last_read_message: str | None = None # Last read message + thread: str # Thread + unread_count: int # Number of unread messages + + +# Base64 encoded profile picture +class ThreadUpdateParamsProfilePicture(BaseModel): + data: str | None = None # Base64 encoded image data + filename: str | None = None # Original filename + mime_type: str | None = None # MIME type of the image + + +# Schema for thread update parameters. +# Used by both Users.Threads.Update and Teams.Threads.Update actions. +class ThreadUpdateParams(BaseModel): + description: str | None = None # Thread description + metadata: dict[str, object] | None = None # Additional metadata + profile_picture: ThreadUpdateParamsProfilePicture | None = None + title: str | None = None # Thread title diff --git a/src/archastro/platform/types/users.py b/src/archastro/platform/types/users.py new file mode 100644 index 0000000..0a12e0c --- /dev/null +++ b/src/archastro/platform/types/users.py @@ -0,0 +1,41 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: c8aa91e7db51 + + +from pydantic import BaseModel + + +# API schema for a user. +class User(BaseModel): + alias: str | None = None # User alias/handle + email: str | None = None # User email address + id: str # User ID + metadata: dict[str, object] | None = None # User metadata + name: str | None = None # User display name + org: str | None = None # Organization + sandbox: str | None = None # Sandbox + + +# API schema for user feedback on messages. +class UserFeedback(BaseModel): + comment: str | None = None # Optional comment + created_at: str | None = None # Creation timestamp + id: str # Feedback ID + message: str # Message + org: str | None = None # Organization + rating: str # Rating (positive/negative) + sandbox: str | None = None # Sandbox + source: str # Feedback source + thread: str # Thread + updated_at: str | None = None # Update timestamp + + +# API schema for a user invite. +class UserInvite(BaseModel): + created_at: str | None = None # Creation timestamp + id: str # Invite ID (uin_...) + key: str | None = None # Invite key + metadata: dict[str, object] | None = None # Invite metadata + thread: str | None = None # Thread + user: User | None = None # Invite creator diff --git a/src/archastro/platform/v1/__init__.py b/src/archastro/platform/v1/__init__.py new file mode 100644 index 0000000..d935a9b --- /dev/null +++ b/src/archastro/platform/v1/__init__.py @@ -0,0 +1,51 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 313121911804 + +from ..runtime.http_client import HttpClient +from .resources.agent_computers import AgentComputerResource +from .resources.agent_installations import AgentInstallationResource +from .resources.agent_routines import AgentRoutineResource +from .resources.agent_sessions import AgentSessionResource +from .resources.agent_skills import AgentSkillResource +from .resources.agent_tools import AgentToolResource +from .resources.agents import AgentResource +from .resources.ai import AiResource +from .resources.artifacts import ArtifactResource +from .resources.automation_runs import AutomationRunResource +from .resources.automations import AutomationResource +from .resources.config import ConfigResource +from .resources.custom_objects import CustomObjectResource +from .resources.installation_sources import InstallationSourceResource +from .resources.kv import KvResource +from .resources.orgs import OrgResource +from .resources.team_memberships import TeamMembershipResource +from .resources.teams import TeamResource +from .resources.thread_messages import ThreadMessageResource +from .resources.threads import ThreadResource +from .resources.users import UserResource + + +class V1: + def __init__(self, http: HttpClient): + self.agent_computers = AgentComputerResource(http) + self.agent_installations = AgentInstallationResource(http) + self.agent_routines = AgentRoutineResource(http) + self.agent_sessions = AgentSessionResource(http) + self.agent_skills = AgentSkillResource(http) + self.agent_tools = AgentToolResource(http) + self.agents = AgentResource(http) + self.artifacts = ArtifactResource(http) + self.automation_runs = AutomationRunResource(http) + self.automations = AutomationResource(http) + self.config = ConfigResource(http) + self.custom_objects = CustomObjectResource(http) + self.installation_sources = InstallationSourceResource(http) + self.kv = KvResource(http) + self.orgs = OrgResource(http) + self.team_memberships = TeamMembershipResource(http) + self.teams = TeamResource(http) + self.thread_messages = ThreadMessageResource(http) + self.threads = ThreadResource(http) + self.users = UserResource(http) + self.ai = AiResource(http) diff --git a/src/archastro/platform/v1/resources/__init__.py b/src/archastro/platform/v1/resources/__init__.py new file mode 100644 index 0000000..1761e5f --- /dev/null +++ b/src/archastro/platform/v1/resources/__init__.py @@ -0,0 +1,25 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 34e16a3c161d + +from .agent_computers import AgentComputerResource # noqa: F401 +from .agent_installations import AgentInstallationResource # noqa: F401 +from .agent_routines import AgentRoutineResource # noqa: F401 +from .agent_sessions import AgentSessionResource # noqa: F401 +from .agent_skills import AgentSkillResource # noqa: F401 +from .agent_tools import AgentToolResource # noqa: F401 +from .agents import AgentResource # noqa: F401 +from .ai import AiResource # noqa: F401 +from .artifacts import ArtifactResource # noqa: F401 +from .automation_runs import AutomationRunResource # noqa: F401 +from .automations import AutomationResource # noqa: F401 +from .config import ConfigResource # noqa: F401 +from .custom_objects import CustomObjectResource # noqa: F401 +from .installation_sources import InstallationSourceResource # noqa: F401 +from .kv import KvResource # noqa: F401 +from .orgs import OrgResource # noqa: F401 +from .team_memberships import TeamMembershipResource # noqa: F401 +from .teams import TeamResource # noqa: F401 +from .thread_messages import ThreadMessageResource # noqa: F401 +from .threads import ThreadResource # noqa: F401 +from .users import UserResource # noqa: F401 diff --git a/src/archastro/platform/v1/resources/agent_computers.py b/src/archastro/platform/v1/resources/agent_computers.py new file mode 100644 index 0000000..d916326 --- /dev/null +++ b/src/archastro/platform/v1/resources/agent_computers.py @@ -0,0 +1,34 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 89ecfaf6798d + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.agents import AgentComputer +from ...types.common import ComputerExecResult + + +class AgentComputerResource: + def __init__(self, http: HttpClient): + self._http = http + + async def delete(self, computer: str) -> None: + await self._http.request(f"/api/v1/agent_computers/{computer}", method="DELETE") + + async def get(self, computer: str) -> AgentComputer: + return await self._http.request(f"/api/v1/agent_computers/{computer}") + + async def exec(self, computer: str, input: dict) -> ComputerExecResult: + return await self._http.request( + f"/api/v1/agent_computers/{computer}/exec", + method="POST", + body=input, + ) + + async def refresh(self, computer: str, input: dict) -> AgentComputer: + return await self._http.request( + f"/api/v1/agent_computers/{computer}/refresh", + method="POST", + body=input, + ) diff --git a/src/archastro/platform/v1/resources/agent_installations.py b/src/archastro/platform/v1/resources/agent_installations.py new file mode 100644 index 0000000..907daf2 --- /dev/null +++ b/src/archastro/platform/v1/resources/agent_installations.py @@ -0,0 +1,66 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 5e1a91eb424d + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.common import ( + Installation, + InstallationListResponse, + InstallationSource, + InstallationSourceListResponse, +) + + +class AgentInstallationInstallationSourceResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, installation: str) -> InstallationSourceListResponse: + return await self._http.request( + f"/api/v1/agent_installations/{installation}/installation_sources", + ) + + async def create(self, installation: str, input: dict) -> InstallationSource: + return await self._http.request( + f"/api/v1/agent_installations/{installation}/installation_sources", + method="POST", + body=input, + ) + + +class AgentInstallationResource: + def __init__(self, http: HttpClient): + self._http = http + self.installation_sources = AgentInstallationInstallationSourceResource(http) + + async def list(self, **params) -> InstallationListResponse: + return await self._http.request("/api/v1/agent_installations", query=params) + + async def delete(self, installation: str) -> None: + await self._http.request(f"/api/v1/agent_installations/{installation}", method="DELETE") + + async def get(self, installation: str) -> Installation: + return await self._http.request(f"/api/v1/agent_installations/{installation}") + + async def activate(self, installation: str, input: dict) -> Installation: + return await self._http.request( + f"/api/v1/agent_installations/{installation}/activate", + method="POST", + body=input, + ) + + async def pause(self, installation: str, input: dict) -> Installation: + return await self._http.request( + f"/api/v1/agent_installations/{installation}/pause", + method="POST", + body=input, + ) + + async def suspend(self, installation: str, input: dict) -> Installation: + return await self._http.request( + f"/api/v1/agent_installations/{installation}/suspend", + method="POST", + body=input, + ) diff --git a/src/archastro/platform/v1/resources/agent_routines.py b/src/archastro/platform/v1/resources/agent_routines.py new file mode 100644 index 0000000..e312554 --- /dev/null +++ b/src/archastro/platform/v1/resources/agent_routines.py @@ -0,0 +1,71 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: a82c9d260196 + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.agents import ( + AgentRoutine, + AgentRoutineListResponse, + AgentRoutineRun, + AgentRoutineRunListResponse, +) +from ...types.common import RoutinePreset + + +class AgentRoutineRunResource: + def __init__(self, http: HttpClient): + self._http = http + + async def get(self, run: str) -> AgentRoutineRun: + return await self._http.request(f"/api/v1/agent_routines/runs/{run}") + + +class AgentRoutineResource: + def __init__(self, http: HttpClient): + self._http = http + self.agent_routine_runs = AgentRoutineRunResource(http) + + async def list(self, **params) -> AgentRoutineListResponse: + return await self._http.request("/api/v1/agent_routines", query=params) + + async def presets(self) -> list[RoutinePreset]: + return await self._http.request("/api/v1/agent_routines/presets") + + async def delete(self, routine: str) -> None: + await self._http.request(f"/api/v1/agent_routines/{routine}", method="DELETE") + + async def get(self, routine: str) -> AgentRoutine: + return await self._http.request(f"/api/v1/agent_routines/{routine}") + + async def update(self, routine: str, input: dict) -> AgentRoutine: + return await self._http.request( + f"/api/v1/agent_routines/{routine}", + method="PATCH", + body=input, + ) + + async def activate(self, routine: str, input: dict) -> AgentRoutine: + return await self._http.request( + f"/api/v1/agent_routines/{routine}/activate", + method="POST", + body=input, + ) + + async def invoke(self, routine: str, input: dict) -> AgentRoutineRun: + return await self._http.request( + f"/api/v1/agent_routines/{routine}/invoke", + method="POST", + body=input, + ) + + async def pause(self, routine: str, input: dict) -> AgentRoutine: + return await self._http.request( + f"/api/v1/agent_routines/{routine}/pause", + method="POST", + body=input, + ) + + async def runs(self, routine: str, **params) -> AgentRoutineRunListResponse: + return await self._http.request(f"/api/v1/agent_routines/{routine}/runs", query=params) diff --git a/src/archastro/platform/v1/resources/agent_sessions.py b/src/archastro/platform/v1/resources/agent_sessions.py new file mode 100644 index 0000000..145d0eb --- /dev/null +++ b/src/archastro/platform/v1/resources/agent_sessions.py @@ -0,0 +1,46 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 80d7f1bf4f68 + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.agents import AgentSession, AgentSessionListResponse + + +class AgentSessionResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, **params) -> AgentSessionListResponse: + return await self._http.request("/api/v1/agent_sessions", query=params) + + async def create(self, input: dict) -> AgentSession: + return await self._http.request("/api/v1/agent_sessions", method="POST", body=input) + + async def delete(self, agent_session: str) -> None: + await self._http.request(f"/api/v1/agent_sessions/{agent_session}", method="DELETE") + + async def get(self, agent_session: str) -> AgentSession: + return await self._http.request(f"/api/v1/agent_sessions/{agent_session}") + + async def update(self, agent_session: str, input: dict) -> AgentSession: + return await self._http.request( + f"/api/v1/agent_sessions/{agent_session}", + method="PATCH", + body=input, + ) + + async def cancel(self, agent_session: str, input: dict) -> AgentSession: + return await self._http.request( + f"/api/v1/agent_sessions/{agent_session}/cancel", + method="POST", + body=input, + ) + + async def message(self, agent_session: str, input: dict) -> AgentSession: + return await self._http.request( + f"/api/v1/agent_sessions/{agent_session}/message", + method="POST", + body=input, + ) diff --git a/src/archastro/platform/v1/resources/agent_skills.py b/src/archastro/platform/v1/resources/agent_skills.py new file mode 100644 index 0000000..3bad517 --- /dev/null +++ b/src/archastro/platform/v1/resources/agent_skills.py @@ -0,0 +1,46 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 9d50fb9aaf2b + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.agents import AgentSkill, AgentSkillList + + +class AgentSkillResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, **params) -> AgentSkillList: + return await self._http.request("/api/v1/agent_skills", query=params) + + async def create(self, input: dict) -> AgentSkill: + return await self._http.request("/api/v1/agent_skills", method="POST", body=input) + + async def delete(self, agent_skill: str) -> None: + await self._http.request(f"/api/v1/agent_skills/{agent_skill}", method="DELETE") + + async def get(self, agent_skill: str) -> AgentSkill: + return await self._http.request(f"/api/v1/agent_skills/{agent_skill}") + + async def update(self, agent_skill: str, input: dict) -> AgentSkill: + return await self._http.request( + f"/api/v1/agent_skills/{agent_skill}", + method="PATCH", + body=input, + ) + + async def activate(self, agent_skill: str, input: dict) -> AgentSkill: + return await self._http.request( + f"/api/v1/agent_skills/{agent_skill}/activate", + method="POST", + body=input, + ) + + async def deactivate(self, agent_skill: str, input: dict) -> AgentSkill: + return await self._http.request( + f"/api/v1/agent_skills/{agent_skill}/deactivate", + method="POST", + body=input, + ) diff --git a/src/archastro/platform/v1/resources/agent_tools.py b/src/archastro/platform/v1/resources/agent_tools.py new file mode 100644 index 0000000..6b8542f --- /dev/null +++ b/src/archastro/platform/v1/resources/agent_tools.py @@ -0,0 +1,43 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 401cf286192c + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.agents import AgentTool, AgentToolListResponse +from ...types.common import BuiltinToolCatalogEntry + + +class AgentToolResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, **params) -> AgentToolListResponse: + return await self._http.request("/api/v1/agent_tools", query=params) + + async def catalog(self) -> list[BuiltinToolCatalogEntry]: + return await self._http.request("/api/v1/agent_tools/catalog") + + async def delete(self, tool: str) -> None: + await self._http.request(f"/api/v1/agent_tools/{tool}", method="DELETE") + + async def get(self, tool: str) -> AgentTool: + return await self._http.request(f"/api/v1/agent_tools/{tool}") + + async def update(self, tool: str, input: dict) -> AgentTool: + return await self._http.request(f"/api/v1/agent_tools/{tool}", method="PATCH", body=input) + + async def activate(self, tool: str, input: dict) -> AgentTool: + return await self._http.request( + f"/api/v1/agent_tools/{tool}/activate", + method="POST", + body=input, + ) + + async def deactivate(self, tool: str, input: dict) -> AgentTool: + return await self._http.request( + f"/api/v1/agent_tools/{tool}/deactivate", + method="POST", + body=input, + ) diff --git a/src/archastro/platform/v1/resources/agents.py b/src/archastro/platform/v1/resources/agents.py new file mode 100644 index 0000000..ff518cc --- /dev/null +++ b/src/archastro/platform/v1/resources/agents.py @@ -0,0 +1,141 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 28e8f3d932d2 + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.agents import ( + Agent, + AgentComputer, + AgentComputerListResponse, + AgentExport, + AgentListResponse, + AgentRoutine, + AgentSchedule, + AgentTool, + AgentToolListResponse, +) +from ...types.common import ( + Installation, + InstallationKindListResponse, + InstallationListResponse, + WorkingMemoryEntryListResponse, +) +from ...types.threads import Thread + + +class AgentAgentComputerResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, agent: str) -> AgentComputerListResponse: + return await self._http.request(f"/api/v1/agents/{agent}/agent_computers") + + async def create(self, agent: str, input: dict) -> AgentComputer: + return await self._http.request( + f"/api/v1/agents/{agent}/agent_computers", + method="POST", + body=input, + ) + + +class AgentAgentInstallationResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, agent: str) -> InstallationListResponse: + return await self._http.request(f"/api/v1/agents/{agent}/agent_installations") + + async def create(self, agent: str, input: dict) -> Installation: + return await self._http.request( + f"/api/v1/agents/{agent}/agent_installations", + method="POST", + body=input, + ) + + async def kinds(self, agent: str) -> InstallationKindListResponse: + return await self._http.request(f"/api/v1/agents/{agent}/agent_installations/kinds") + + +class AgentAgentToolResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, agent: str, **params) -> AgentToolListResponse: + return await self._http.request(f"/api/v1/agents/{agent}/agent_tools", query=params) + + async def create(self, agent: str, input: dict) -> AgentTool: + return await self._http.request( + f"/api/v1/agents/{agent}/agent_tools", + method="POST", + body=input, + ) + + +class ScheduleResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, agent: str, **params) -> dict[str, object]: + return await self._http.request(f"/api/v1/agents/{agent}/schedules", query=params) + + async def get(self, agent: str, schedule: str) -> AgentSchedule: + return await self._http.request(f"/api/v1/agents/{agent}/schedules/{schedule}") + + +class AgentResource: + def __init__(self, http: HttpClient): + self._http = http + self.agent_computers = AgentAgentComputerResource(http) + self.agent_installations = AgentAgentInstallationResource(http) + self.agent_tools = AgentAgentToolResource(http) + self.schedules = ScheduleResource(http) + + async def list(self, **params) -> AgentListResponse: + return await self._http.request("/api/v1/agents", query=params) + + async def create(self, input: dict) -> Agent: + return await self._http.request("/api/v1/agents", method="POST", body=input) + + async def delete(self, agent: str) -> None: + await self._http.request(f"/api/v1/agents/{agent}", method="DELETE") + + async def get(self, agent: str) -> Agent: + return await self._http.request(f"/api/v1/agents/{agent}") + + async def update(self, agent: str, input: dict) -> Agent: + return await self._http.request(f"/api/v1/agents/{agent}", method="PATCH", body=input) + + async def agent_routines(self, agent: str, input: dict) -> AgentRoutine: + return await self._http.request( + f"/api/v1/agents/{agent}/agent_routines", + method="POST", + body=input, + ) + + async def agent_working_memory(self, agent: str, **params) -> WorkingMemoryEntryListResponse: + return await self._http.request( + f"/api/v1/agents/{agent}/agent_working_memory", + query=params, + ) + + async def export(self, agent: str) -> AgentExport: + """ + Export agent as AgentTemplate + Reconstructs an AgentTemplate config from a deployed agent and its sub-resources + (tools, routines, skills, installations). Returns the template plus all dependent + config files (scripts, workflows, skills, schemas) with their raw content for a + fully self-contained export. + """ + return await self._http.request(f"/api/v1/agents/{agent}/export") + + async def search(self, agent: str, input: dict) -> dict[str, object]: + return await self._http.request(f"/api/v1/agents/{agent}/search", method="POST", body=input) + + async def threads(self, agent: str, input: dict) -> Thread: + return await self._http.request( + f"/api/v1/agents/{agent}/threads", + method="POST", + body=input, + ) diff --git a/src/archastro/platform/v1/resources/ai.py b/src/archastro/platform/v1/resources/ai.py new file mode 100644 index 0000000..af9b3f2 --- /dev/null +++ b/src/archastro/platform/v1/resources/ai.py @@ -0,0 +1,40 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 4f4e804e4e5b + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.ai import AICompletionResult, AIImageResult + + +class ChatResource: + def __init__(self, http: HttpClient): + self._http = http + + async def completions(self, input: dict) -> AICompletionResult: + return await self._http.request("/api/v1/ai/chat/completions", method="POST", body=input) + + async def models(self) -> dict[str, object]: + return await self._http.request("/api/v1/ai/chat/models") + + +class ImageResource: + def __init__(self, http: HttpClient): + self._http = http + + async def edits(self, input: dict) -> AIImageResult: + return await self._http.request("/api/v1/ai/image/edits", method="POST", body=input) + + async def generations(self, input: dict) -> AIImageResult: + return await self._http.request("/api/v1/ai/image/generations", method="POST", body=input) + + async def models(self) -> dict[str, object]: + return await self._http.request("/api/v1/ai/image/models") + + +class AiResource: + def __init__(self, http: HttpClient): + self._http = http + self.chat = ChatResource(http) + self.image = ImageResource(http) diff --git a/src/archastro/platform/v1/resources/artifacts.py b/src/archastro/platform/v1/resources/artifacts.py new file mode 100644 index 0000000..985c9a1 --- /dev/null +++ b/src/archastro/platform/v1/resources/artifacts.py @@ -0,0 +1,28 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: ee1ab3825faa + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.artifacts import Artifact + + +class ArtifactResource: + def __init__(self, http: HttpClient): + self._http = http + + async def delete(self, artifact: str) -> None: + await self._http.request(f"/api/v1/artifacts/{artifact}", method="DELETE") + + async def get(self, artifact: str) -> Artifact: + return await self._http.request(f"/api/v1/artifacts/{artifact}") + + async def replace(self, artifact: str, input: dict) -> Artifact: + return await self._http.request(f"/api/v1/artifacts/{artifact}", method="PUT", body=input) + + async def archive(self, artifact: str, input: dict) -> None: + await self._http.request(f"/api/v1/artifacts/{artifact}/archive", method="POST", body=input) + + async def content(self, artifact: str, **params) -> dict[str, str]: + return await self._http.request_raw(f"/api/v1/artifacts/{artifact}/content", query=params) diff --git a/src/archastro/platform/v1/resources/automation_runs.py b/src/archastro/platform/v1/resources/automation_runs.py new file mode 100644 index 0000000..97e507e --- /dev/null +++ b/src/archastro/platform/v1/resources/automation_runs.py @@ -0,0 +1,22 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 31b54eea8bfc + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.automations import AutomationRun + + +class AutomationRunResource: + def __init__(self, http: HttpClient): + self._http = http + + async def get(self, automation_run: str) -> AutomationRun: + """ + Get a single invoked automation run + Fetches one run created by an invoked automation. + This public lookup route only returns runs whose parent automation has + `type: :invoked`. + """ + return await self._http.request(f"/api/v1/automation_runs/{automation_run}") diff --git a/src/archastro/platform/v1/resources/automations.py b/src/archastro/platform/v1/resources/automations.py new file mode 100644 index 0000000..ebce38e --- /dev/null +++ b/src/archastro/platform/v1/resources/automations.py @@ -0,0 +1,20 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: ed7fe8aed161 + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.automations import AutomationRun + + +class AutomationResource: + def __init__(self, http: HttpClient): + self._http = http + + async def invoke(self, automation: str, input: dict) -> AutomationRun: + return await self._http.request( + f"/api/v1/automations/{automation}/invoke", + method="POST", + body=input, + ) diff --git a/src/archastro/platform/v1/resources/config.py b/src/archastro/platform/v1/resources/config.py new file mode 100644 index 0000000..4df8685 --- /dev/null +++ b/src/archastro/platform/v1/resources/config.py @@ -0,0 +1,86 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: d114f5ce8cbc + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.common import ValidationResult +from ...types.config import Config, ConfigKindSchema + + +class KindResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, **params) -> dict[str, object]: + return await self._http.request("/api/v1/config/kinds", query=params) + + async def schema(self, kind: str) -> ConfigKindSchema: + return await self._http.request(f"/api/v1/config/kinds/{kind}/schema") + + +class SystemResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, **params) -> dict[str, object]: + return await self._http.request("/api/v1/config/system", query=params) + + async def get(self, system: str) -> Config: + return await self._http.request(f"/api/v1/config/system/{system}") + + async def clone(self, system: str, input: dict) -> Config: + return await self._http.request( + f"/api/v1/config/system/{system}/clone", + method="POST", + body=input, + ) + + +class ConfigResource: + def __init__(self, http: HttpClient): + self._http = http + self.kinds = KindResource(http) + self.system = SystemResource(http) + + async def list(self, **params) -> dict[str, object]: + return await self._http.request("/api/v1/config", query=params) + + async def create(self, input: dict) -> Config: + return await self._http.request("/api/v1/config", method="POST", body=input) + + async def encrypt_secret(self, input: dict) -> dict[str, object]: + return await self._http.request("/api/v1/config/encrypt_secret", method="POST", body=input) + + async def validate(self, input: dict) -> ValidationResult: + return await self._http.request("/api/v1/config/validate", method="POST", body=input) + + async def delete(self, config: str) -> None: + await self._http.request(f"/api/v1/config/{config}", method="DELETE") + + async def get(self, config: str, **params) -> Config: + return await self._http.request(f"/api/v1/config/{config}", query=params) + + async def replace(self, config: str, input: dict) -> Config: + return await self._http.request(f"/api/v1/config/{config}", method="PUT", body=input) + + async def archive(self, config: str, input: dict) -> Config: + return await self._http.request( + f"/api/v1/config/{config}/archive", + method="POST", + body=input, + ) + + async def content(self, config: str, **params) -> dict[str, str]: + return await self._http.request_raw(f"/api/v1/config/{config}/content", query=params) + + async def unarchive(self, config: str, input: dict) -> Config: + return await self._http.request( + f"/api/v1/config/{config}/unarchive", + method="POST", + body=input, + ) + + async def versions(self, config: str, **params) -> dict[str, object]: + return await self._http.request(f"/api/v1/config/{config}/versions", query=params) diff --git a/src/archastro/platform/v1/resources/custom_objects.py b/src/archastro/platform/v1/resources/custom_objects.py new file mode 100644 index 0000000..78860db --- /dev/null +++ b/src/archastro/platform/v1/resources/custom_objects.py @@ -0,0 +1,26 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 3fac1fdb8135 + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.common import CustomObject + + +class CustomObjectResource: + def __init__(self, http: HttpClient): + self._http = http + + async def delete(self, object: str) -> None: + await self._http.request(f"/api/v1/custom_objects/{object}", method="DELETE") + + async def get(self, object: str, **params) -> CustomObject: + return await self._http.request(f"/api/v1/custom_objects/{object}", query=params) + + async def replace(self, object: str, input: dict) -> dict[str, object]: + return await self._http.request( + f"/api/v1/custom_objects/{object}", + method="PUT", + body=input, + ) diff --git a/src/archastro/platform/v1/resources/installation_sources.py b/src/archastro/platform/v1/resources/installation_sources.py new file mode 100644 index 0000000..c73ebe6 --- /dev/null +++ b/src/archastro/platform/v1/resources/installation_sources.py @@ -0,0 +1,15 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 83dbcfed81d9 + +from __future__ import annotations + +from ...runtime.http_client import HttpClient + + +class InstallationSourceResource: + def __init__(self, http: HttpClient): + self._http = http + + async def delete(self, source: str) -> None: + await self._http.request(f"/api/v1/installation_sources/{source}", method="DELETE") diff --git a/src/archastro/platform/v1/resources/kv.py b/src/archastro/platform/v1/resources/kv.py new file mode 100644 index 0000000..a1b3cf6 --- /dev/null +++ b/src/archastro/platform/v1/resources/kv.py @@ -0,0 +1,28 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 302b2035ea0e + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.common import KeyValueStorageEntry, KeyValueStorageEntryList + + +class KvResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self) -> KeyValueStorageEntryList: + return await self._http.request("/api/v1/kv") + + async def create(self, input: dict) -> KeyValueStorageEntry: + return await self._http.request("/api/v1/kv", method="POST", body=input) + + async def delete(self, key: str) -> None: + await self._http.request(f"/api/v1/kv/{key}", method="DELETE") + + async def get(self, key: str) -> KeyValueStorageEntry: + return await self._http.request(f"/api/v1/kv/{key}") + + async def upsert(self, key: str, input: dict) -> KeyValueStorageEntry: + return await self._http.request(f"/api/v1/kv/{key}", method="PUT", body=input) diff --git a/src/archastro/platform/v1/resources/orgs.py b/src/archastro/platform/v1/resources/orgs.py new file mode 100644 index 0000000..f726e88 --- /dev/null +++ b/src/archastro/platform/v1/resources/orgs.py @@ -0,0 +1,15 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 22cdcd20f836 + +from __future__ import annotations + +from ...runtime.http_client import HttpClient + + +class OrgResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, **params) -> dict[str, object]: + return await self._http.request("/api/v1/orgs", query=params) diff --git a/src/archastro/platform/v1/resources/team_memberships.py b/src/archastro/platform/v1/resources/team_memberships.py new file mode 100644 index 0000000..fa3fcb0 --- /dev/null +++ b/src/archastro/platform/v1/resources/team_memberships.py @@ -0,0 +1,19 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: c579e3ed0f26 + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.teams import TeamMembershipListResponse + + +class TeamMembershipResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, **params) -> TeamMembershipListResponse: + return await self._http.request("/api/v1/team_memberships", query=params) + + async def delete(self, team_membership: str) -> None: + await self._http.request(f"/api/v1/team_memberships/{team_membership}", method="DELETE") diff --git a/src/archastro/platform/v1/resources/teams.py b/src/archastro/platform/v1/resources/teams.py new file mode 100644 index 0000000..233274f --- /dev/null +++ b/src/archastro/platform/v1/resources/teams.py @@ -0,0 +1,116 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: b37aff69b1bd + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.artifacts import Artifact +from ...types.common import CustomObject +from ...types.teams import Team, TeamInvite, TeamMembership +from ...types.threads import Thread + + +class TeamArtifactResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, team: str) -> dict[str, object]: + return await self._http.request(f"/api/v1/teams/{team}/artifacts") + + async def create(self, team: str, input: dict) -> Artifact: + return await self._http.request( + f"/api/v1/teams/{team}/artifacts", + method="POST", + body=input, + ) + + +class TeamCustomObjectResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, team: str, **params) -> dict[str, object]: + return await self._http.request(f"/api/v1/teams/{team}/custom_objects", query=params) + + async def create(self, team: str, input: dict) -> CustomObject: + return await self._http.request( + f"/api/v1/teams/{team}/custom_objects", + method="POST", + body=input, + ) + + +class MemberResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, team: str) -> dict[str, object]: + return await self._http.request(f"/api/v1/teams/{team}/members") + + async def create(self, team: str, input: dict) -> TeamMembership: + return await self._http.request(f"/api/v1/teams/{team}/members", method="POST", body=input) + + +class TeamThreadResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, team: str) -> dict[str, object]: + return await self._http.request(f"/api/v1/teams/{team}/threads") + + async def create(self, team: str, input: dict) -> Thread: + return await self._http.request(f"/api/v1/teams/{team}/threads", method="POST", body=input) + + +class TeamResource: + def __init__(self, http: HttpClient): + self._http = http + self.artifacts = TeamArtifactResource(http) + self.custom_objects = TeamCustomObjectResource(http) + self.members = MemberResource(http) + self.threads = TeamThreadResource(http) + + async def list(self, **params) -> dict[str, object]: + return await self._http.request("/api/v1/teams", query=params) + + async def create(self, input: dict) -> Team: + return await self._http.request("/api/v1/teams", method="POST", body=input) + + async def join_by_code(self, input: dict) -> Team: + """ + Join a team using an invite code + Accepts either `join_code` or `invite_code`. + For user-authenticated requests, the current user joins the team identified by + the invite code. For server-to-server requests, provide `agent` or `user` to + add that principal to the team instead. + """ + return await self._http.request("/api/v1/teams/join_by_code", method="POST", body=input) + + async def delete(self, team: str) -> None: + await self._http.request(f"/api/v1/teams/{team}", method="DELETE") + + async def get(self, team: str) -> Team: + return await self._http.request(f"/api/v1/teams/{team}") + + async def update(self, team: str, input: dict) -> Team: + return await self._http.request(f"/api/v1/teams/{team}", method="PATCH", body=input) + + async def invite(self, team: str, input: dict) -> TeamInvite: + return await self._http.request(f"/api/v1/teams/{team}/invite", method="POST", body=input) + + async def invites(self, team: str, input: dict) -> dict[str, object]: + return await self._http.request(f"/api/v1/teams/{team}/invites", method="POST", body=input) + + async def join(self, team: str, input: dict) -> None: + """ + Join a team the current user can see + Joins a specific visible team by team ID. + For standard user requests, the current user is added to the team. When + `agent` is provided, the current user must already be a team member and the + agent is added instead. + """ + await self._http.request(f"/api/v1/teams/{team}/join", method="POST", body=input) + + async def leave(self, team: str) -> None: + await self._http.request(f"/api/v1/teams/{team}/leave", method="DELETE") diff --git a/src/archastro/platform/v1/resources/thread_messages.py b/src/archastro/platform/v1/resources/thread_messages.py new file mode 100644 index 0000000..07f3654 --- /dev/null +++ b/src/archastro/platform/v1/resources/thread_messages.py @@ -0,0 +1,42 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 6f44448912b6 + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.common import Message, PaginatedReplies + + +class ReactionResource: + def __init__(self, http: HttpClient): + self._http = http + + async def remove(self, message: str) -> None: + await self._http.request(f"/api/v1/thread_messages/{message}/reactions", method="DELETE") + + async def create(self, message: str, input: dict) -> dict[str, object]: + return await self._http.request( + f"/api/v1/thread_messages/{message}/reactions", + method="POST", + body=input, + ) + + +class ThreadMessageResource: + def __init__(self, http: HttpClient): + self._http = http + self.reactions = ReactionResource(http) + + async def delete(self, message: str) -> None: + await self._http.request(f"/api/v1/thread_messages/{message}", method="DELETE") + + async def replace(self, message: str, input: dict) -> Message: + return await self._http.request( + f"/api/v1/thread_messages/{message}", + method="PUT", + body=input, + ) + + async def replies(self, message: str, **params) -> PaginatedReplies: + return await self._http.request(f"/api/v1/thread_messages/{message}/replies", query=params) diff --git a/src/archastro/platform/v1/resources/threads.py b/src/archastro/platform/v1/resources/threads.py new file mode 100644 index 0000000..f627f74 --- /dev/null +++ b/src/archastro/platform/v1/resources/threads.py @@ -0,0 +1,82 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: d068f97dd895 + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.threads import Thread, ThreadMember, ThreadReadStatus, ThreadSettings + + +class ThreadMemberResource: + def __init__(self, http: HttpClient): + self._http = http + + async def remove(self, thread: str) -> None: + await self._http.request(f"/api/v1/threads/{thread}/members", method="DELETE") + + async def list(self, thread: str) -> dict[str, object]: + return await self._http.request(f"/api/v1/threads/{thread}/members") + + async def create(self, thread: str, input: dict) -> ThreadMember: + return await self._http.request( + f"/api/v1/threads/{thread}/members", + method="POST", + body=input, + ) + + +class SettingResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, thread: str) -> dict[str, object]: + return await self._http.request(f"/api/v1/threads/{thread}/settings") + + async def replace(self, thread: str, input: dict) -> ThreadSettings: + return await self._http.request( + f"/api/v1/threads/{thread}/settings", + method="PUT", + body=input, + ) + + +class ThreadResource: + def __init__(self, http: HttpClient): + self._http = http + self.members = ThreadMemberResource(http) + self.settings = SettingResource(http) + + async def delete(self, thread: str) -> None: + await self._http.request(f"/api/v1/threads/{thread}", method="DELETE") + + async def get(self, thread: str) -> Thread: + return await self._http.request(f"/api/v1/threads/{thread}") + + async def replace(self, thread: str, input: dict) -> Thread: + return await self._http.request(f"/api/v1/threads/{thread}", method="PUT", body=input) + + async def agents(self, thread: str) -> dict[str, object]: + return await self._http.request(f"/api/v1/threads/{thread}/agents") + + async def artifacts(self, thread: str) -> dict[str, object]: + return await self._http.request(f"/api/v1/threads/{thread}/artifacts") + + async def mark_read(self, thread: str, input: dict) -> None: + await self._http.request(f"/api/v1/threads/{thread}/mark_read", method="POST", body=input) + + async def messages(self, thread: str, **params) -> dict[str, object]: + return await self._http.request(f"/api/v1/threads/{thread}/messages", query=params) + + async def picture(self, thread: str, input: dict) -> Thread: + return await self._http.request( + f"/api/v1/threads/{thread}/picture", + method="PUT", + body=input, + ) + + async def read_status(self, thread: str, **params) -> ThreadReadStatus: + return await self._http.request(f"/api/v1/threads/{thread}/read_status", query=params) + + async def search(self, thread: str, **params) -> dict[str, object]: + return await self._http.request(f"/api/v1/threads/{thread}/search", query=params) diff --git a/src/archastro/platform/v1/resources/users.py b/src/archastro/platform/v1/resources/users.py new file mode 100644 index 0000000..7b37192 --- /dev/null +++ b/src/archastro/platform/v1/resources/users.py @@ -0,0 +1,55 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 55945071c81b + +from __future__ import annotations + +from ...runtime.http_client import HttpClient +from ...types.artifacts import Artifact +from ...types.threads import Thread +from ...types.users import User + + +class UserArtifactResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, user: str) -> dict[str, object]: + return await self._http.request(f"/api/v1/users/{user}/artifacts") + + async def create(self, user: str, input: dict) -> Artifact: + return await self._http.request( + f"/api/v1/users/{user}/artifacts", + method="POST", + body=input, + ) + + +class UserThreadResource: + def __init__(self, http: HttpClient): + self._http = http + + async def list(self, user: str, **params) -> dict[str, object]: + return await self._http.request(f"/api/v1/users/{user}/threads", query=params) + + async def create(self, user: str, input: dict) -> Thread: + return await self._http.request(f"/api/v1/users/{user}/threads", method="POST", body=input) + + +class UserResource: + def __init__(self, http: HttpClient): + self._http = http + self.artifacts = UserArtifactResource(http) + self.threads = UserThreadResource(http) + + async def me(self) -> User: + return await self._http.request("/api/v1/users/me") + + async def get(self, user: str) -> User: + return await self._http.request(f"/api/v1/users/{user}") + + async def orgs(self, user: str) -> dict[str, object]: + return await self._http.request(f"/api/v1/users/{user}/orgs") + + async def profile(self, user: str, input: dict) -> User: + return await self._http.request(f"/api/v1/users/{user}/profile", method="PUT", body=input) diff --git a/src/phx_channel/__init__.py b/src/phx_channel/__init__.py new file mode 100644 index 0000000..a6c3317 --- /dev/null +++ b/src/phx_channel/__init__.py @@ -0,0 +1,11 @@ +from .channel import Channel +from .harness import HarnessServiceClient, HarnessServiceError +from .socket import Socket + +__all__ = [ + "Socket", + "Channel", + "HarnessServiceClient", + "HarnessServiceError", +] +__version__ = "0.1.0" diff --git a/src/phx_channel/channel.py b/src/phx_channel/channel.py new file mode 100644 index 0000000..8fa5bac --- /dev/null +++ b/src/phx_channel/channel.py @@ -0,0 +1,262 @@ +""" +Phoenix Channel — manages a single topic subscription, push/reply, and events. +""" + +from __future__ import annotations + +import asyncio +import logging +from collections.abc import Callable +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from .socket import Socket + +logger = logging.getLogger("phx_channel") + +DEFAULT_TIMEOUT_S = 10 + + +class Channel: + """ + Represents a single Phoenix Channel subscription on a topic. + + Created via ``socket.channel("topic", params)``. + Call ``await channel.join()`` to subscribe. + """ + + def __init__(self, socket: Socket, topic: str, params: dict[str, Any]): + self._socket = socket + self._topic = topic + self._params = params + + self._state: str = "closed" # closed | joining | joined | leaving | errored + self._join_ref: str | None = None + self._join_push_ref: str | None = None + + self._event_handlers: dict[str, list[Callable[..., Any]]] = {} + self._pending_replies: dict[str, asyncio.Future[dict[str, Any]]] = {} + self._push_buffer: list[tuple[str, Any, asyncio.Future[dict[str, Any]]]] = [] + + @property + def topic(self) -> str: + return self._topic + + @property + def state(self) -> str: + return self._state + + @property + def is_joined(self) -> bool: + return self._state == "joined" + + # ─── Join / Leave ───────────────────────────────────────── + + async def join( + self, + payload: dict[str, Any] | None = None, + *, + timeout: float = DEFAULT_TIMEOUT_S, + ) -> dict[str, Any]: + """ + Join the channel. Returns the join response payload. + + ``payload`` overrides the params passed to ``socket.channel(topic, params)`` + when provided — generated SDK channel classes pass the join payload + directly here, while hand-written callers typically rely on the params + captured at channel creation time. + + Raises ``TimeoutError`` if the server doesn't reply within ``timeout``. + Raises ``ChannelError`` if the server rejects the join. + Raises ``ChannelError`` if the channel is already joined — callers + should ``leave()`` before re-joining. Silently returning an empty + response would let a double-call (e.g. two ``LiveDocChannel.join_*`` + invocations reusing the same underlying channel) masquerade as a + successful join while dropping the real server response. + """ + if self._state == "joined": + raise ChannelError(f"Channel {self._topic} is already joined; call leave() first") + + self._state = "joining" + ref = self._socket._make_ref() + self._join_ref = ref + self._join_push_ref = ref + + future: asyncio.Future[dict[str, Any]] = asyncio.get_event_loop().create_future() + self._pending_replies[ref] = future + + params = self._params if payload is None else payload + await self._socket._send(ref, ref, self._topic, "phx_join", params) + + try: + result = await asyncio.wait_for(future, timeout=timeout) + except TimeoutError: + self._pending_replies.pop(ref, None) + self._state = "errored" + raise TimeoutError(f"Join timed out for {self._topic}") from None + + status = result.get("status") + if status == "ok": + self._state = "joined" + logger.info("Joined %s", self._topic) + # Flush buffered pushes + await self._flush_push_buffer() + return result.get("response", {}) + else: + self._state = "errored" + raise ChannelError(f"Join rejected for {self._topic}: {result.get('response', {})}") + + async def leave(self, timeout: float = DEFAULT_TIMEOUT_S) -> None: + """Leave the channel.""" + if self._state == "closed": + return + + self._state = "leaving" + ref = self._socket._make_ref() + future: asyncio.Future[dict[str, Any]] = asyncio.get_event_loop().create_future() + self._pending_replies[ref] = future + + await self._socket._send(self._join_ref, ref, self._topic, "phx_leave", {}) + + try: + await asyncio.wait_for(future, timeout=timeout) + except TimeoutError: + pass # Leave is best-effort + finally: + self._state = "closed" + self._join_ref = None + self._socket._remove_channel(self._topic) + logger.info("Left %s", self._topic) + + async def _rejoin(self) -> None: + """Rejoin after reconnection.""" + if self._state in ("closed", "leaving"): + return + self._state = "closed" + self._join_ref = None + try: + await self.join() + except Exception as exc: + logger.warning("Rejoin failed for %s: %s", self._topic, exc) + self._state = "errored" + + # ─── Push / Reply ───────────────────────────────────────── + + async def push( + self, event: str, payload: Any = None, timeout: float = DEFAULT_TIMEOUT_S + ) -> dict[str, Any]: + """ + Push an event to the channel and wait for a reply. + + Returns the reply ``{"status": ..., "response": ...}``. + Raises ``TimeoutError`` if no reply within ``timeout``. + """ + if payload is None: + payload = {} + + if self._state != "joined": + # Buffer the push for when we rejoin + future: asyncio.Future[dict[str, Any]] = asyncio.get_event_loop().create_future() + self._push_buffer.append((event, payload, future)) + return await asyncio.wait_for(future, timeout=timeout) + + return await self._do_push(event, payload, timeout) + + async def _do_push(self, event: str, payload: Any, timeout: float) -> dict[str, Any]: + ref = self._socket._make_ref() + future: asyncio.Future[dict[str, Any]] = asyncio.get_event_loop().create_future() + self._pending_replies[ref] = future + + await self._socket._send(self._join_ref, ref, self._topic, event, payload) + + try: + result = await asyncio.wait_for(future, timeout=timeout) + except TimeoutError: + self._pending_replies.pop(ref, None) + raise TimeoutError(f"Push '{event}' timed out on {self._topic}") from None + + return result + + async def _flush_push_buffer(self) -> None: + buffer = self._push_buffer[:] + self._push_buffer.clear() + for event, payload, future in buffer: + try: + result = await self._do_push(event, payload, DEFAULT_TIMEOUT_S) + if not future.done(): + future.set_result(result) + except Exception as exc: + if not future.done(): + future.set_exception(exc) + + # ─── Event handlers ────────────────────────────────────── + + def on(self, event: str, callback: Callable[..., Any]) -> Callable[[], None]: + """ + Register a callback for a channel event. + + Returns an unsubscribe function. + """ + handlers = self._event_handlers.setdefault(event, []) + handlers.append(callback) + + def unsubscribe() -> None: + handlers.remove(callback) + + return unsubscribe + + # ─── Internal message dispatch ──────────────────────────── + + def _on_message( + self, + join_ref: str | None, + ref: str | None, + event: str, + payload: Any, + ) -> None: + # Ignore messages from stale join + if join_ref is not None and join_ref != self._join_ref: + return + + if event == "phx_reply": + self._handle_reply(ref, payload) + elif event == "phx_close": + self._handle_close() + elif event == "phx_error": + self._handle_error(payload) + else: + # User event — dispatch to handlers + handlers = self._event_handlers.get(event, []) + for handler in handlers: + try: + handler(payload) + except Exception: + logger.exception("Error in handler for %s:%s", self._topic, event) + + def _handle_reply(self, ref: str | None, payload: Any) -> None: + if ref and ref in self._pending_replies: + future = self._pending_replies.pop(ref) + if not future.done(): + future.set_result(payload) + + def _handle_close(self) -> None: + logger.info("Channel closed: %s", self._topic) + self._state = "closed" + self._trigger_event("phx_close", {}) + + def _handle_error(self, payload: Any) -> None: + logger.warning("Channel error: %s — %s", self._topic, payload) + self._state = "errored" + self._trigger_event("phx_error", payload) + + def _trigger_event(self, event: str, payload: Any) -> None: + handlers = self._event_handlers.get(event, []) + for handler in handlers: + try: + handler(payload) + except Exception: + logger.exception("Error in handler for %s:%s", self._topic, event) + + +class ChannelError(Exception): + """Raised when a channel operation fails (e.g., join rejected).""" diff --git a/src/phx_channel/harness.py b/src/phx_channel/harness.py new file mode 100644 index 0000000..76d68c5 --- /dev/null +++ b/src/phx_channel/harness.py @@ -0,0 +1,120 @@ +""" +HarnessServiceClient — Python counterpart to the TypeScript client in +``@archastro/sdk-generator/channel-harness``. + +The channel-harness service exposes two surfaces that this client speaks to: + + 1. A **WebSocket** endpoint carrying the real Phoenix channel protocol. + Generated SDKs connect here to exercise their channel classes + end-to-end. + + 2. An **HTTP control** endpoint for scenario management and observation + queries. Callers POST a JSON scenario to set up per-topic behavior, + GET observations to assert on what the SDK actually put on the wire, + and POST ``/reset`` between tests to drop state. + +There is no in-process shortcut. Tests drive the SAME service the TypeScript +tests (or any other language) drive — the only differences are which SDK is +under test and which client library is opening the socket. +""" + +from __future__ import annotations + +from typing import Any + +import httpx + +from .socket import Socket + + +class HarnessServiceClient: + """Thin client over the harness service's HTTP + WebSocket surfaces. + + One instance per test is typical:: + + client = HarnessServiceClient(ws_url, control_url) + try: + await client.reset() + await client.register_scenario({ + "topic": "doc:doc_42", + "onJoin": [{"type": "autoReply"}], + }) + socket = await client.open_socket() + channel = await LiveDocChannel.join_document( + socket, "doc_42", user_id="user_1" + ) + assert channel.join_response is not None + finally: + await client.close() + """ + + def __init__(self, ws_url: str, control_url: str, *, request_timeout: float = 5.0): + self.ws_url = ws_url + self.control_url = control_url.rstrip("/") + self._http = httpx.AsyncClient(timeout=request_timeout) + self._sockets: list[Socket] = [] + + async def close(self) -> None: + """Disconnect every socket opened through this client + close HTTP.""" + for s in self._sockets: + try: + await s.disconnect() + except Exception: + pass + self._sockets.clear() + await self._http.aclose() + + # ─── HTTP control ──────────────────────────────────────────── + + async def reset(self) -> None: + """Clear every scenario, observation, and handler error on the server.""" + r = await self._http.post(f"{self.control_url}/reset") + r.raise_for_status() + + async def register_scenario(self, scenario: dict[str, Any]) -> None: + """Register a scenario for an exact topic. + + See the TS ``ScenarioRequest`` / ``ScenarioAction`` types for the + JSON shape — the server validates and returns 400 on invalid bodies. + """ + r = await self._http.post(f"{self.control_url}/scenarios", json=scenario) + if r.status_code != 201: + raise HarnessServiceError(f"register_scenario failed: {r.status_code} {r.text}") + + async def observations( + self, topic: str | None = None, event: str | None = None + ) -> list[dict[str, Any]]: + """Fetch inbound frames the server validated, optionally filtered.""" + params: dict[str, str] = {} + if topic is not None: + params["topic"] = topic + if event is not None: + params["event"] = event + r = await self._http.get(f"{self.control_url}/observations", params=params) + r.raise_for_status() + return r.json() + + async def handler_errors(self) -> list[dict[str, Any]]: + """Fetch scenario handler errors recorded by the server.""" + r = await self._http.get(f"{self.control_url}/handler-errors") + r.raise_for_status() + return r.json() + + # ─── Socket lifecycle ──────────────────────────────────────── + + async def open_socket(self, *, auto_reconnect: bool = False) -> Socket: + """Open a fresh Phoenix socket to the service's WebSocket endpoint. + + Every call produces a new connection — the generated SDK receives + the same ``Socket`` it would talk to in production. ``auto_reconnect`` + defaults to ``False`` so a disconnected test surfaces immediately + rather than silently retrying in the background. + """ + socket = Socket(self.ws_url, auto_reconnect=auto_reconnect) + await socket.connect() + self._sockets.append(socket) + return socket + + +class HarnessServiceError(RuntimeError): + """Raised when the harness service returns a non-success HTTP status.""" diff --git a/src/phx_channel/socket.py b/src/phx_channel/socket.py new file mode 100644 index 0000000..509700d --- /dev/null +++ b/src/phx_channel/socket.py @@ -0,0 +1,280 @@ +""" +Phoenix Socket — manages the WebSocket connection, heartbeat, and channels. + +Wire protocol: JSON arrays [join_ref, ref, topic, event, payload] +""" + +from __future__ import annotations + +import asyncio +import json +import logging +from collections.abc import Callable +from typing import Any +from urllib.parse import urlencode, urlparse, urlunparse + +import websockets +from websockets.asyncio.client import ClientConnection + +from .channel import Channel + +logger = logging.getLogger("phx_channel") + +# Default backoff schedule (milliseconds) matching the Phoenix JS client +DEFAULT_RECONNECT_BACKOFF_MS = [10, 50, 100, 150, 200, 250, 500, 1000, 2000] +DEFAULT_HEARTBEAT_INTERVAL_S = 30 +DEFAULT_TIMEOUT_S = 10 + + +class Socket: + """ + Manages a WebSocket connection to a Phoenix server. + + Usage:: + + socket = Socket("ws://localhost:4000/socket/websocket", params={"token": "..."}) + await socket.connect() + + channel = socket.channel("room:lobby", {"user_id": "123"}) + resp = await channel.join() + + await channel.push("new_msg", {"body": "hello"}) + channel.on("new_msg", lambda payload: print(payload)) + + await socket.disconnect() + """ + + def __init__( + self, + url: str, + *, + params: dict[str, str] | None = None, + heartbeat_interval: float = DEFAULT_HEARTBEAT_INTERVAL_S, + timeout: float = DEFAULT_TIMEOUT_S, + reconnect_backoff_ms: list[int] | None = None, + auto_reconnect: bool = True, + ): + self._base_url = url + self._params = params or {} + self._heartbeat_interval = heartbeat_interval + self._timeout = timeout + self._backoff = reconnect_backoff_ms or DEFAULT_RECONNECT_BACKOFF_MS + self._auto_reconnect = auto_reconnect + + self._ws: ClientConnection | None = None + self._ref = 0 + self._pending_heartbeat_ref: str | None = None + self._channels: dict[str, Channel] = {} + self._connected = False + self._closing = False + + self._heartbeat_task: asyncio.Task[None] | None = None + self._receive_task: asyncio.Task[None] | None = None + self._reconnect_attempt = 0 + + self._on_open_callbacks: list[Callable[[], Any]] = [] + self._on_close_callbacks: list[Callable[[int, str], Any]] = [] + self._on_error_callbacks: list[Callable[[Exception], Any]] = [] + + @property + def is_connected(self) -> bool: + return self._connected and self._ws is not None + + def _make_ref(self) -> str: + self._ref += 1 + return str(self._ref) + + def _build_url(self) -> str: + parsed = urlparse(self._base_url) + params = {**self._params, "vsn": "2.0.0"} + # Merge any existing query params + existing_qs = parsed.query + if existing_qs: + qs = existing_qs + "&" + urlencode(params) + else: + qs = urlencode(params) + return urlunparse(parsed._replace(query=qs)) + + # ─── Connection lifecycle ───────────────────────────────── + + async def connect(self) -> None: + """Connect to the Phoenix server.""" + self._closing = False + self._reconnect_attempt = 0 + await self._do_connect() + + async def _do_connect(self) -> None: + url = self._build_url() + try: + self._ws = await websockets.connect(url) + self._connected = True + self._reconnect_attempt = 0 + logger.info("Connected to %s", self._base_url) + + for cb in self._on_open_callbacks: + cb() + + # Start heartbeat and receive loops + self._heartbeat_task = asyncio.create_task(self._heartbeat_loop()) + self._receive_task = asyncio.create_task(self._receive_loop()) + + # Rejoin any channels that were previously joined + for channel in self._channels.values(): + if channel._state in ("joined", "errored"): + asyncio.create_task(channel._rejoin()) + + except Exception as exc: + logger.error("Connection failed: %s", exc) + self._connected = False + for cb in self._on_error_callbacks: + cb(exc) + if self._auto_reconnect and not self._closing: + await self._schedule_reconnect() + + async def disconnect(self) -> None: + """Gracefully disconnect from the server.""" + self._closing = True + self._connected = False + + if self._heartbeat_task: + self._heartbeat_task.cancel() + self._heartbeat_task = None + if self._receive_task: + self._receive_task.cancel() + self._receive_task = None + + if self._ws: + await self._ws.close() + self._ws = None + + logger.info("Disconnected") + + async def _schedule_reconnect(self) -> None: + if self._closing: + return + idx = min(self._reconnect_attempt, len(self._backoff) - 1) + delay_ms = self._backoff[idx] + self._reconnect_attempt += 1 + logger.info("Reconnecting in %dms (attempt %d)", delay_ms, self._reconnect_attempt) + await asyncio.sleep(delay_ms / 1000) + if not self._closing: + await self._do_connect() + + # ─── Channel management ─────────────────────────────────── + + def channel(self, topic: str, params: dict[str, Any] | None = None) -> Channel: + """Create a channel for the given topic.""" + if topic in self._channels: + return self._channels[topic] + ch = Channel(self, topic, params or {}) + self._channels[topic] = ch + return ch + + def _remove_channel(self, topic: str) -> None: + self._channels.pop(topic, None) + + # ─── Send ───────────────────────────────────────────────── + + async def _send( + self, + join_ref: str | None, + ref: str | None, + topic: str, + event: str, + payload: Any, + ) -> None: + if not self._ws or not self._connected: + raise ConnectionError("Socket is not connected") + msg = json.dumps([join_ref, ref, topic, event, payload]) + await self._ws.send(msg) + + # ─── Receive loop ───────────────────────────────────────── + + async def _receive_loop(self) -> None: + try: + assert self._ws is not None + async for raw in self._ws: + if isinstance(raw, bytes): + raw = raw.decode("utf-8") + try: + msg = json.loads(raw) + except json.JSONDecodeError: + logger.warning("Non-JSON message: %s", raw[:100]) + continue + + if not isinstance(msg, list) or len(msg) != 5: + logger.warning("Malformed message: %s", raw[:100]) + continue + + join_ref, ref, topic, event, payload = msg + self._dispatch(join_ref, ref, topic, event, payload) + + except websockets.ConnectionClosed as exc: + logger.info("Connection closed: code=%s reason=%s", exc.code, exc.reason) + self._connected = False + for cb in self._on_close_callbacks: + cb(exc.code, exc.reason) + if self._auto_reconnect and not self._closing: + await self._schedule_reconnect() + except asyncio.CancelledError: + pass + except Exception as exc: + logger.error("Receive error: %s", exc) + self._connected = False + for cb in self._on_error_callbacks: + cb(exc) + if self._auto_reconnect and not self._closing: + await self._schedule_reconnect() + + def _dispatch( + self, + join_ref: str | None, + ref: str | None, + topic: str, + event: str, + payload: Any, + ) -> None: + # Heartbeat reply + if ref and ref == self._pending_heartbeat_ref: + self._pending_heartbeat_ref = None + return + + # Route to channel + channel = self._channels.get(topic) + if channel: + channel._on_message(join_ref, ref, event, payload) + + # ─── Heartbeat ──────────────────────────────────────────── + + async def _heartbeat_loop(self) -> None: + try: + while self._connected: + await asyncio.sleep(self._heartbeat_interval) + if not self._connected: + break + if self._pending_heartbeat_ref is not None: + # Previous heartbeat not acknowledged — connection is dead + logger.warning("Heartbeat timeout — closing connection") + self._pending_heartbeat_ref = None + if self._ws: + await self._ws.close(1000, "heartbeat timeout") + break + ref = self._make_ref() + self._pending_heartbeat_ref = ref + try: + await self._send(None, ref, "phoenix", "heartbeat", {}) + except Exception: + break + except asyncio.CancelledError: + pass + + # ─── Event callbacks ────────────────────────────────────── + + def on_open(self, callback: Callable[[], Any]) -> None: + self._on_open_callbacks.append(callback) + + def on_close(self, callback: Callable[[int, str], Any]) -> None: + self._on_close_callbacks.append(callback) + + def on_error(self, callback: Callable[[Exception], Any]) -> None: + self._on_error_callbacks.append(callback) diff --git a/src/phx_channel/tests/test_unit.py b/src/phx_channel/tests/test_unit.py new file mode 100644 index 0000000..ee80a08 --- /dev/null +++ b/src/phx_channel/tests/test_unit.py @@ -0,0 +1,428 @@ +""" +Unit tests for the Phoenix Channel client — no server required. +Tests protocol logic, state machine, message formatting, ref tracking, etc. +""" + +import asyncio + +import pytest + +from phx_channel.channel import Channel, ChannelError +from phx_channel.socket import Socket + + +class MockSocket: + """Minimal mock socket that records sent messages.""" + + def __init__(self): + self.sent: list[tuple] = [] + self._ref = 0 + self._channels: dict = {} + + def _make_ref(self) -> str: + self._ref += 1 + return str(self._ref) + + async def _send(self, join_ref, ref, topic, event, payload): + self.sent.append((join_ref, ref, topic, event, payload)) + + def _remove_channel(self, topic): + self._channels.pop(topic, None) + + +async def _joined_channel(): + """Helper: create a channel and join it.""" + socket = MockSocket() + ch = Channel(socket, "test:topic", {}) + join_task = asyncio.create_task(ch.join(timeout=1)) + await asyncio.sleep(0.01) + join_ref, ref, _, _, _ = socket.sent[0] + ch._on_message(join_ref, ref, "phx_reply", {"status": "ok", "response": {}}) + await join_task + socket.sent.clear() + return socket, ch + + +# ─── Socket unit tests ────────────────────────────────────────── + + +def test_socket_starts_disconnected(): + s = Socket("ws://localhost:4000/socket/websocket") + assert not s.is_connected + + +def test_socket_generates_incrementing_refs(): + s = Socket("ws://localhost:4000/socket/websocket") + assert s._make_ref() == "1" + assert s._make_ref() == "2" + assert s._make_ref() == "3" + + +def test_socket_creates_channels_by_topic(): + s = Socket("ws://localhost:4000/socket/websocket") + ch1 = s.channel("room:lobby") + ch2 = s.channel("room:lobby") + ch3 = s.channel("room:other") + assert ch1 is ch2 + assert ch1 is not ch3 + + +def test_socket_removes_channels(): + s = Socket("ws://localhost:4000/socket/websocket") + ch = s.channel("room:lobby") + s._remove_channel("room:lobby") + ch2 = s.channel("room:lobby") + assert ch2 is not ch + + +def test_socket_stores_config(): + s = Socket( + "ws://localhost:4000/socket/websocket", + heartbeat_interval=5, + timeout=2, + auto_reconnect=False, + params={"token": "abc"}, + ) + # Verify params are stored (used in URL construction) + assert s._params == {"token": "abc"} + + +def test_socket_on_callbacks_registrable(): + s = Socket("ws://localhost:4000/socket/websocket") + called = [] + s.on_open(lambda: called.append("open")) + s.on_close(lambda code, reason: called.append(("close", code, reason))) + s.on_error(lambda exc: called.append(("error", exc))) + # Just verify registration doesn't crash — actual invocation tested in integration + + +# ─── Channel state machine ────────────────────────────────────── + + +def test_initial_state(): + ch = Channel(MockSocket(), "test:topic", {}) + assert ch.state == "closed" + assert not ch.is_joined + + +async def test_join_sends_phx_join(): + socket = MockSocket() + ch = Channel(socket, "test:topic", {"key": "val"}) + join_task = asyncio.create_task(ch.join(timeout=1)) + await asyncio.sleep(0.01) + + assert len(socket.sent) == 1 + join_ref, ref, topic, event, payload = socket.sent[0] + assert topic == "test:topic" + assert event == "phx_join" + assert payload == {"key": "val"} + assert join_ref == ref + + ch._on_message(join_ref, ref, "phx_reply", {"status": "ok", "response": {"welcome": True}}) + response = await join_task + assert response == {"welcome": True} + assert ch.state == "joined" + assert ch.is_joined + + +async def test_join_already_joined_raises(): + # Previously returned {} — but silent success dropped the real join + # response, so a double-call (e.g. two generated-SDK `join_*` invocations + # reusing the cached channel) masked a bug. Now the second call raises + # so callers explicitly `leave()` before re-joining. + _, ch = await _joined_channel() + with pytest.raises(ChannelError, match="already joined"): + await ch.join(timeout=1) + + +async def test_join_rejected(): + socket = MockSocket() + ch = Channel(socket, "test:topic", {}) + join_task = asyncio.create_task(ch.join(timeout=1)) + await asyncio.sleep(0.01) + + join_ref, ref, _, _, _ = socket.sent[0] + ch._on_message( + join_ref, ref, "phx_reply", {"status": "error", "response": {"reason": "unauthorized"}} + ) + + with pytest.raises(ChannelError, match="unauthorized"): + await join_task + assert ch.state == "errored" + + +async def test_join_timeout(): + socket = MockSocket() + ch = Channel(socket, "test:topic", {}) + with pytest.raises(TimeoutError, match="timed out"): + await ch.join(timeout=0.05) + + +async def test_phx_close_transitions_to_closed(): + _, ch = await _joined_channel() + ch._on_message(None, None, "phx_close", {}) + assert ch.state == "closed" + + +async def test_phx_error_transitions_to_errored(): + _, ch = await _joined_channel() + ch._on_message(None, None, "phx_error", {"reason": "crash"}) + assert ch.state == "errored" + + +# ─── Push / Reply ──────────────────────────────────────────────── + + +async def test_push_sends_correct_format(): + socket, ch = await _joined_channel() + push_task = asyncio.create_task(ch.push("my_event", {"data": 123}, timeout=1)) + await asyncio.sleep(0.01) + + assert len(socket.sent) == 1 + join_ref, ref, topic, event, payload = socket.sent[0] + assert topic == "test:topic" + assert event == "my_event" + assert payload == {"data": 123} + + ch._on_message(join_ref, ref, "phx_reply", {"status": "ok", "response": {"id": "abc"}}) + result = await push_task + assert result == {"status": "ok", "response": {"id": "abc"}} + + +async def test_push_timeout(): + _, ch = await _joined_channel() + with pytest.raises(TimeoutError, match="timed out"): + await ch.push("slow", {}, timeout=0.05) + + +async def test_push_default_payload(): + socket, ch = await _joined_channel() + push_task = asyncio.create_task(ch.push("evt", timeout=1)) + await asyncio.sleep(0.01) + + _, _, _, _, payload = socket.sent[0] + assert payload == {} + + join_ref, ref, _, _, _ = socket.sent[0] + ch._on_message(join_ref, ref, "phx_reply", {"status": "ok", "response": {}}) + await push_task + + +async def test_multiple_concurrent_pushes(): + socket, ch = await _joined_channel() + + p1 = asyncio.create_task(ch.push("evt_a", {"n": 1}, timeout=1)) + p2 = asyncio.create_task(ch.push("evt_b", {"n": 2}, timeout=1)) + await asyncio.sleep(0.01) + + assert len(socket.sent) == 2 + jr1, ref1, _, _, _ = socket.sent[0] + jr2, ref2, _, _, _ = socket.sent[1] + + # Reply to second first + ch._on_message(jr2, ref2, "phx_reply", {"status": "ok", "response": {"from": "b"}}) + ch._on_message(jr1, ref1, "phx_reply", {"status": "ok", "response": {"from": "a"}}) + + r1 = await p1 + r2 = await p2 + assert r1["response"]["from"] == "a" + assert r2["response"]["from"] == "b" + + +async def test_each_push_gets_unique_ref(): + socket, ch = await _joined_channel() + for _ in range(3): + asyncio.create_task(ch.push("evt", {}, timeout=1)) + await asyncio.sleep(0.01) + + refs = [m[1] for m in socket.sent] + assert len(set(refs)) == 3 + + +# ─── Event handlers ────────────────────────────────────────────── + + +async def test_event_dispatch(): + _, ch = await _joined_channel() + received = [] + ch.on("my_event", lambda p: received.append(p)) + ch._on_message(None, None, "my_event", {"data": "hello"}) + assert received == [{"data": "hello"}] + + +async def test_multiple_handlers(): + _, ch = await _joined_channel() + a, b = [], [] + ch.on("evt", lambda p: a.append(p)) + ch.on("evt", lambda p: b.append(p)) + ch._on_message(None, None, "evt", {"n": 1}) + assert len(a) == 1 + assert len(b) == 1 + + +async def test_unsubscribe(): + _, ch = await _joined_channel() + a, b = [], [] + unsub = ch.on("evt", lambda p: a.append(p)) + ch.on("evt", lambda p: b.append(p)) + unsub() + ch._on_message(None, None, "evt", {"n": 1}) + assert len(a) == 0 + assert len(b) == 1 + + +async def test_stale_join_ref_ignored(): + _, ch = await _joined_channel() + received = [] + ch.on("evt", lambda p: received.append(p)) + ch._on_message("wrong_ref", None, "evt", {"data": "stale"}) + assert len(received) == 0 + + +async def test_null_join_ref_accepted(): + _, ch = await _joined_channel() + received = [] + ch.on("evt", lambda p: received.append(p)) + ch._on_message(None, None, "evt", {"data": "broadcast"}) + assert received == [{"data": "broadcast"}] + + +async def test_handler_error_does_not_crash_dispatch(): + _, ch = await _joined_channel() + received = [] + ch.on("evt", lambda p: (_ for _ in ()).throw(RuntimeError("boom"))) + ch.on("evt", lambda p: received.append(p)) + # Should not raise + ch._on_message(None, None, "evt", {"data": "test"}) + assert received == [{"data": "test"}] + + +async def test_phx_close_fires_handlers(): + _, ch = await _joined_channel() + events = [] + ch.on("phx_close", lambda p: events.append(p)) + ch._on_message(None, None, "phx_close", {}) + assert len(events) == 1 + + +async def test_phx_error_fires_handlers(): + _, ch = await _joined_channel() + events = [] + ch.on("phx_error", lambda p: events.append(p)) + ch._on_message(None, None, "phx_error", {"reason": "crash"}) + assert events == [{"reason": "crash"}] + + +# ─── Push buffering ────────────────────────────────────────────── + + +async def test_buffers_pushes_before_join(): + socket = MockSocket() + ch = Channel(socket, "test:topic", {}) + + push_task = asyncio.create_task(ch.push("buffered", {"n": 1}, timeout=5)) + await asyncio.sleep(0.05) + + # Reply to buffered push shortly after join succeeds + async def reply_to_buffered(): + # Wait for the buffered push to actually be sent + for _ in range(50): + push_msgs = [m for m in socket.sent if m[3] == "buffered"] + if push_msgs: + msg = push_msgs[0] + ch._on_message( + msg[0], msg[1], "phx_reply", {"status": "ok", "response": {"buffered": True}} + ) + return + await asyncio.sleep(0.05) + + join_task = asyncio.create_task(ch.join(timeout=5)) + reply_task = asyncio.create_task(reply_to_buffered()) + await asyncio.sleep(0.05) + + # Complete the join + join_ref, ref, _, _, _ = socket.sent[0] + ch._on_message(join_ref, ref, "phx_reply", {"status": "ok", "response": {}}) + await join_task + + # Wait for both the reply and the push to resolve + await reply_task + result = await push_task + assert result == {"status": "ok", "response": {"buffered": True}} + + +# ─── Leave ─────────────────────────────────────────────────────── + + +async def test_leave(): + socket, ch = await _joined_channel() + leave_task = asyncio.create_task(ch.leave(timeout=1)) + await asyncio.sleep(0.01) + + assert len(socket.sent) == 1 + _, _, _, event, _ = socket.sent[0] + assert event == "phx_leave" + + join_ref, ref, _, _, _ = socket.sent[0] + ch._on_message(join_ref, ref, "phx_reply", {"status": "ok", "response": {}}) + await leave_task + assert ch.state == "closed" + + +async def test_leave_on_closed_is_noop(): + socket = MockSocket() + ch = Channel(socket, "test:topic", {}) + await ch.leave(timeout=1) + assert len(socket.sent) == 0 + + +async def test_leave_timeout_graceful(): + _, ch = await _joined_channel() + await ch.leave(timeout=0.05) + assert ch.state == "closed" + + +# ─── Rejoin ────────────────────────────────────────────────────── + + +async def test_rejoin_resets_and_joins(): + socket, ch = await _joined_channel() + assert ch.is_joined + + rejoin_task = asyncio.create_task(ch._rejoin()) + await asyncio.sleep(0.01) + + join_msg = [m for m in socket.sent if m[3] == "phx_join"] + assert len(join_msg) == 1 + + ch._on_message( + join_msg[0][0], + join_msg[0][1], + "phx_reply", + {"status": "ok", "response": {"rejoined": True}}, + ) + await rejoin_task + assert ch.is_joined + + +async def test_rejoin_on_closed_is_noop(): + socket = MockSocket() + ch = Channel(socket, "test:topic", {}) + await ch._rejoin() + assert len(socket.sent) == 0 + + +async def test_rejoin_sets_errored_on_failure(): + socket, ch = await _joined_channel() + rejoin_task = asyncio.create_task(ch._rejoin()) + await asyncio.sleep(0.01) + + join_msg = [m for m in socket.sent if m[3] == "phx_join"] + ch._on_message( + join_msg[0][0], + join_msg[0][1], + "phx_reply", + {"status": "error", "response": {"reason": "gone"}}, + ) + await rejoin_task + assert ch.state == "errored" diff --git a/tests/contract/channels/test_api_activity_feed_channel.py b/tests/contract/channels/test_api_activity_feed_channel.py new file mode 100644 index 0000000..6498cfd --- /dev/null +++ b/tests/contract/channels/test_api_activity_feed_channel.py @@ -0,0 +1,145 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 321ffd7740ac + +""" +Contract tests for ApiActivityFeedChannel — generated from the channel spec. + +Drives the generated channel class through a real WebSocket against the +harness-service subprocess (spawned by conftest.py). Scenarios are +registered over HTTP — there is no in-process closure shortcut, so the +same service Python, TypeScript, and any future language can target. +""" + +import asyncio + +import pytest +import pytest_asyncio + +from archastro.platform.channels.api_activity_feed_channel import ApiActivityFeedChannel +from phx_channel import HarnessServiceClient +from phx_channel.channel import ChannelError + +# Mark every coroutine in this file as async. Generated tests are imported +# into arbitrary SDK projects whose pytest config may or may not set +# asyncio_mode=auto — pytestmark keeps them runnable either way. +pytestmark = pytest.mark.asyncio + + +@pytest_asyncio.fixture +async def rig(harness_service): + client = HarnessServiceClient( + ws_url=harness_service["wsUrl"], + control_url=harness_service["controlUrl"], + ) + await client.reset() + socket = await client.open_socket() + try: + yield (client, socket) + finally: + await client.close() + + +async def test_api_activity_feed_channel_join_agent_joins_and_receives_contract_valid_reply(rig): + _, socket = rig + channel = await ApiActivityFeedChannel.join_agent(socket, "test-id") + assert isinstance(channel, ApiActivityFeedChannel) + assert channel.join_response is not None + + +async def test_api_activity_feed_channel_join_agent_surfaces_server_error_reply_as_channel_error( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:activity_feed:agent:test-id", + "onJoin": [{"type": "replyError", "payload": {"reason": "test_error"}}], + } + ) + with pytest.raises(ChannelError): + await ApiActivityFeedChannel.join_agent(socket, "test-id") + + +async def test_api_activity_feed_channel_join_org_joins_and_receives_contract_valid_reply(rig): + _, socket = rig + channel = await ApiActivityFeedChannel.join_org(socket, "test-id") + assert isinstance(channel, ApiActivityFeedChannel) + assert channel.join_response is not None + + +async def test_api_activity_feed_channel_join_org_surfaces_server_error_reply_as_channel_error(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:activity_feed:org:test-id", + "onJoin": [{"type": "replyError", "payload": {"reason": "test_error"}}], + } + ) + with pytest.raises(ChannelError): + await ApiActivityFeedChannel.join_org(socket, "test-id") + + +async def test_api_activity_feed_channel_list_entries_sends_valid_push_and_receives_contract_valid_reply( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:activity_feed:agent:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "list_entries": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiActivityFeedChannel.join_agent(socket, "test-id") + reply = await channel.list_entries( + { + "after_cursor": "test-value", + "before_cursor": "test-value", + "kind": "test", + "level": "test-value", + "limit": 1, + } + ) + assert reply["status"] == "ok" + + observed = await client.observations("api:activity_feed:agent:test-id", "list_entries") + assert len(observed) == 1 + assert observed[0]["params"] is not None + + +async def test_api_activity_feed_channel_on_new_entry_delivers_contract_valid_payloads(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:activity_feed:agent:test-id", + "onJoin": [ + {"type": "autoReply"}, + {"type": "autoPush", "event": "new_entry"}, + ], + } + ) + channel = await ApiActivityFeedChannel.join_agent(socket, "test-id") + future: asyncio.Future = asyncio.get_event_loop().create_future() + + def handler(payload): + if not future.done(): + future.set_result(payload) + + channel.on_new_entry(handler) + payload = await asyncio.wait_for(future, timeout=1.0) + assert payload is not None + + +async def test_api_activity_feed_channel_leave_leaves_cleanly_through_generated_leave(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:activity_feed:agent:test-id", + "onJoin": [{"type": "autoReply"}], + } + ) + channel = await ApiActivityFeedChannel.join_agent(socket, "test-id") + await channel.leave() diff --git a/tests/contract/channels/test_api_chat_channel.py b/tests/contract/channels/test_api_chat_channel.py new file mode 100644 index 0000000..567571c --- /dev/null +++ b/tests/contract/channels/test_api_chat_channel.py @@ -0,0 +1,894 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 9dfd1ebdb695 + +""" +Contract tests for ApiChatChannel — generated from the channel spec. + +Drives the generated channel class through a real WebSocket against the +harness-service subprocess (spawned by conftest.py). Scenarios are +registered over HTTP — there is no in-process closure shortcut, so the +same service Python, TypeScript, and any future language can target. +""" + +import asyncio + +import pytest +import pytest_asyncio + +from archastro.platform.channels.api_chat_channel import ApiChatChannel +from phx_channel import HarnessServiceClient +from phx_channel.channel import ChannelError + +# Mark every coroutine in this file as async. Generated tests are imported +# into arbitrary SDK projects whose pytest config may or may not set +# asyncio_mode=auto — pytestmark keeps them runnable either way. +pytestmark = pytest.mark.asyncio + + +@pytest_asyncio.fixture +async def rig(harness_service): + client = HarnessServiceClient( + ws_url=harness_service["wsUrl"], + control_url=harness_service["controlUrl"], + ) + await client.reset() + socket = await client.open_socket() + try: + yield (client, socket) + finally: + await client.close() + + +async def test_api_chat_channel_join_team_thread_joins_and_receives_contract_valid_reply(rig): + _, socket = rig + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + assert isinstance(channel, ApiChatChannel) + assert channel.join_response is not None + + +async def test_api_chat_channel_join_team_thread_surfaces_server_error_reply_as_channel_error(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "replyError", "payload": {"reason": "test_error"}}], + } + ) + with pytest.raises(ChannelError): + await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + + +async def test_api_chat_channel_join_team_keyed_joins_and_receives_contract_valid_reply(rig): + _, socket = rig + channel = await ApiChatChannel.join_team_keyed( + socket, + "test-id", + "test-key", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + assert isinstance(channel, ApiChatChannel) + assert channel.join_response is not None + + +async def test_api_chat_channel_join_team_keyed_surfaces_server_error_reply_as_channel_error(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:key:test-key", + "onJoin": [{"type": "replyError", "payload": {"reason": "test_error"}}], + } + ) + with pytest.raises(ChannelError): + await ApiChatChannel.join_team_keyed( + socket, + "test-id", + "test-key", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + + +async def test_api_chat_channel_join_team_transient_joins_and_receives_contract_valid_reply(rig): + _, socket = rig + channel = await ApiChatChannel.join_team_transient( + socket, + "test-id", + "test-key", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + assert isinstance(channel, ApiChatChannel) + assert channel.join_response is not None + + +async def test_api_chat_channel_join_team_transient_surfaces_server_error_reply_as_channel_error( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:transient:test-key", + "onJoin": [{"type": "replyError", "payload": {"reason": "test_error"}}], + } + ) + with pytest.raises(ChannelError): + await ApiChatChannel.join_team_transient( + socket, + "test-id", + "test-key", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + + +async def test_api_chat_channel_join_user_thread_joins_and_receives_contract_valid_reply(rig): + _, socket = rig + channel = await ApiChatChannel.join_user_thread( + socket, + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + assert isinstance(channel, ApiChatChannel) + assert channel.join_response is not None + + +async def test_api_chat_channel_join_user_thread_surfaces_server_error_reply_as_channel_error(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:user:thread:test-id", + "onJoin": [{"type": "replyError", "payload": {"reason": "test_error"}}], + } + ) + with pytest.raises(ChannelError): + await ApiChatChannel.join_user_thread( + socket, + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + + +async def test_api_chat_channel_join_user_keyed_joins_and_receives_contract_valid_reply(rig): + _, socket = rig + channel = await ApiChatChannel.join_user_keyed( + socket, + "test-key", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + assert isinstance(channel, ApiChatChannel) + assert channel.join_response is not None + + +async def test_api_chat_channel_join_user_keyed_surfaces_server_error_reply_as_channel_error(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:user:key:test-key", + "onJoin": [{"type": "replyError", "payload": {"reason": "test_error"}}], + } + ) + with pytest.raises(ChannelError): + await ApiChatChannel.join_user_keyed( + socket, + "test-key", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + + +async def test_api_chat_channel_join_user_transient_joins_and_receives_contract_valid_reply(rig): + _, socket = rig + channel = await ApiChatChannel.join_user_transient( + socket, + "test-key", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + assert isinstance(channel, ApiChatChannel) + assert channel.join_response is not None + + +async def test_api_chat_channel_join_user_transient_surfaces_server_error_reply_as_channel_error( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:user:transient:test-key", + "onJoin": [{"type": "replyError", "payload": {"reason": "test_error"}}], + } + ) + with pytest.raises(ChannelError): + await ApiChatChannel.join_user_transient( + socket, + "test-key", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + + +async def test_api_chat_channel_api_chat_fork_thread_sends_valid_push_and_receives_contract_valid_reply( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "api:chat:fork_thread": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_fork_thread({"message_id": "test-id", "title": "test-value"}) + assert reply["status"] == "ok" + + observed = await client.observations( + "api:chat:team:test-id:thread:test-id", "api:chat:fork_thread" + ) + assert len(observed) == 1 + assert observed[0]["params"]["message_id"] == "test-id" + + +async def test_api_chat_channel_api_chat_fork_thread_returns_error_envelope_when_required_missing( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_fork_thread({}) + assert reply["status"] == "error" + + +async def test_api_chat_channel_api_chat_mark_thread_read_sends_valid_push_and_receives_contract_valid_reply( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "api:chat:mark_thread_read": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_mark_thread_read({"message_id": "test-id"}) + assert reply["status"] == "ok" + + observed = await client.observations( + "api:chat:team:test-id:thread:test-id", "api:chat:mark_thread_read" + ) + assert len(observed) == 1 + assert observed[0]["params"]["message_id"] == "test-id" + + +async def test_api_chat_channel_api_chat_mark_thread_read_returns_error_envelope_when_required_missing( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_mark_thread_read({}) + assert reply["status"] == "error" + + +async def test_api_chat_channel_api_chat_list_messages_sends_valid_push_and_receives_contract_valid_reply( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "api:chat:list_messages": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_list_messages({}) + assert reply["status"] == "ok" + + observed = await client.observations( + "api:chat:team:test-id:thread:test-id", "api:chat:list_messages" + ) + assert len(observed) == 1 + assert observed[0]["params"] is not None + + +async def test_api_chat_channel_api_chat_load_more_messages_sends_valid_push_and_receives_contract_valid_reply( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "api:chat:load_more_messages": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_load_more_messages( + { + "after_cursor": "test-value", + "before_cursor": "test-value", + "include_metadata": True, + "limit": 1, + } + ) + assert reply["status"] == "ok" + + observed = await client.observations( + "api:chat:team:test-id:thread:test-id", "api:chat:load_more_messages" + ) + assert len(observed) == 1 + assert observed[0]["params"] is not None + + +async def test_api_chat_channel_api_chat_post_message_sends_valid_push_and_receives_contract_valid_reply( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "api:chat:post_message": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_post_message( + { + "content": "test content", + "idempotency_key": "test-key", + "reply_to": "test-value", + "uploads": [{}], + } + ) + assert reply["status"] == "ok" + + observed = await client.observations( + "api:chat:team:test-id:thread:test-id", "api:chat:post_message" + ) + assert len(observed) == 1 + assert observed[0]["params"]["content"] == "test content" + + +async def test_api_chat_channel_api_chat_post_message_returns_error_envelope_when_required_missing( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_post_message({}) + assert reply["status"] == "error" + + +async def test_api_chat_channel_api_chat_post_simple_message_sends_valid_push_and_receives_contract_valid_reply( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "api:chat:post_simple_message": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_post_simple_message( + {"content": "test content", "idempotency_key": "test-key", "reply_to": "test-value"} + ) + assert reply["status"] == "ok" + + observed = await client.observations( + "api:chat:team:test-id:thread:test-id", "api:chat:post_simple_message" + ) + assert len(observed) == 1 + assert observed[0]["params"] is not None + + +async def test_api_chat_channel_api_chat_edit_message_sends_valid_push_and_receives_contract_valid_reply( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "api:chat:edit_message": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_edit_message( + {"content": "test content", "message_id": "test-id"} + ) + assert reply["status"] == "ok" + + observed = await client.observations( + "api:chat:team:test-id:thread:test-id", "api:chat:edit_message" + ) + assert len(observed) == 1 + assert observed[0]["params"]["content"] == "test content" + assert observed[0]["params"]["message_id"] == "test-id" + + +async def test_api_chat_channel_api_chat_edit_message_returns_error_envelope_when_required_missing( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_edit_message({}) + assert reply["status"] == "error" + + +async def test_api_chat_channel_api_chat_delete_message_sends_valid_push_and_receives_contract_valid_reply( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "api:chat:delete_message": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_delete_message({"message_id": "test-id"}) + assert reply["status"] == "ok" + + observed = await client.observations( + "api:chat:team:test-id:thread:test-id", "api:chat:delete_message" + ) + assert len(observed) == 1 + assert observed[0]["params"]["message_id"] == "test-id" + + +async def test_api_chat_channel_api_chat_delete_message_returns_error_envelope_when_required_missing( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_delete_message({}) + assert reply["status"] == "error" + + +async def test_api_chat_channel_api_chat_add_reaction_sends_valid_push_and_receives_contract_valid_reply( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "api:chat:add_reaction": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_add_reaction({"emoji": "test-value", "message_id": "test-id"}) + assert reply["status"] == "ok" + + observed = await client.observations( + "api:chat:team:test-id:thread:test-id", "api:chat:add_reaction" + ) + assert len(observed) == 1 + assert observed[0]["params"]["emoji"] == "test-value" + assert observed[0]["params"]["message_id"] == "test-id" + + +async def test_api_chat_channel_api_chat_add_reaction_returns_error_envelope_when_required_missing( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_add_reaction({}) + assert reply["status"] == "error" + + +async def test_api_chat_channel_api_chat_remove_reaction_sends_valid_push_and_receives_contract_valid_reply( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "api:chat:remove_reaction": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_remove_reaction({"emoji": "test-value", "message_id": "test-id"}) + assert reply["status"] == "ok" + + observed = await client.observations( + "api:chat:team:test-id:thread:test-id", "api:chat:remove_reaction" + ) + assert len(observed) == 1 + assert observed[0]["params"]["emoji"] == "test-value" + assert observed[0]["params"]["message_id"] == "test-id" + + +async def test_api_chat_channel_api_chat_remove_reaction_returns_error_envelope_when_required_missing( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_remove_reaction({}) + assert reply["status"] == "error" + + +async def test_api_chat_channel_on_message_added_delivers_contract_valid_payloads(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [ + {"type": "autoReply"}, + {"type": "autoPush", "event": "message_added"}, + ], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + future: asyncio.Future = asyncio.get_event_loop().create_future() + + def handler(payload): + if not future.done(): + future.set_result(payload) + + channel.on_message_added(handler) + payload = await asyncio.wait_for(future, timeout=1.0) + assert payload is not None + + +async def test_api_chat_channel_on_message_updated_delivers_contract_valid_payloads(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [ + {"type": "autoReply"}, + {"type": "autoPush", "event": "message_updated"}, + ], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + future: asyncio.Future = asyncio.get_event_loop().create_future() + + def handler(payload): + if not future.done(): + future.set_result(payload) + + channel.on_message_updated(handler) + payload = await asyncio.wait_for(future, timeout=1.0) + assert payload is not None + + +async def test_api_chat_channel_on_thread_event_delivers_contract_valid_payloads(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [ + {"type": "autoReply"}, + {"type": "autoPush", "event": "thread_event"}, + ], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + future: asyncio.Future = asyncio.get_event_loop().create_future() + + def handler(payload): + if not future.done(): + future.set_result(payload) + + channel.on_thread_event(handler) + payload = await asyncio.wait_for(future, timeout=1.0) + assert payload is not None + + +async def test_api_chat_channel_on_system_event_delivers_contract_valid_payloads(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [ + {"type": "autoReply"}, + {"type": "autoPush", "event": "system_event"}, + ], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + future: asyncio.Future = asyncio.get_event_loop().create_future() + + def handler(payload): + if not future.done(): + future.set_result(payload) + + channel.on_system_event(handler) + payload = await asyncio.wait_for(future, timeout=1.0) + assert payload is not None + + +async def test_api_chat_channel_leave_leaves_cleanly_through_generated_leave(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + await channel.leave() diff --git a/tests/contract/channels/test_api_object_channel.py b/tests/contract/channels/test_api_object_channel.py new file mode 100644 index 0000000..791bfef --- /dev/null +++ b/tests/contract/channels/test_api_object_channel.py @@ -0,0 +1,191 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: f8787a7eb96f + +""" +Contract tests for ApiObjectChannel — generated from the channel spec. + +Drives the generated channel class through a real WebSocket against the +harness-service subprocess (spawned by conftest.py). Scenarios are +registered over HTTP — there is no in-process closure shortcut, so the +same service Python, TypeScript, and any future language can target. +""" + +import asyncio + +import pytest +import pytest_asyncio + +from archastro.platform.channels.api_object_channel import ApiObjectChannel +from phx_channel import HarnessServiceClient +from phx_channel.channel import ChannelError + +# Mark every coroutine in this file as async. Generated tests are imported +# into arbitrary SDK projects whose pytest config may or may not set +# asyncio_mode=auto — pytestmark keeps them runnable either way. +pytestmark = pytest.mark.asyncio + + +@pytest_asyncio.fixture +async def rig(harness_service): + client = HarnessServiceClient( + ws_url=harness_service["wsUrl"], + control_url=harness_service["controlUrl"], + ) + await client.reset() + socket = await client.open_socket() + try: + yield (client, socket) + finally: + await client.close() + + +async def test_api_object_channel_join_by_id_joins_and_receives_contract_valid_reply(rig): + _, socket = rig + channel = await ApiObjectChannel.join_by_id(socket, "test-id") + assert isinstance(channel, ApiObjectChannel) + assert channel.join_response is not None + + +async def test_api_object_channel_join_by_id_surfaces_server_error_reply_as_channel_error(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:object:test-id", + "onJoin": [{"type": "replyError", "payload": {"reason": "test_error"}}], + } + ) + with pytest.raises(ChannelError): + await ApiObjectChannel.join_by_id(socket, "test-id") + + +async def test_api_object_channel_join_by_row_key_joins_and_receives_contract_valid_reply(rig): + _, socket = rig + channel = await ApiObjectChannel.join_by_row_key(socket, "test-value", "test-key") + assert isinstance(channel, ApiObjectChannel) + assert channel.join_response is not None + + +async def test_api_object_channel_join_by_row_key_surfaces_server_error_reply_as_channel_error(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:object:test-value:test-key", + "onJoin": [{"type": "replyError", "payload": {"reason": "test_error"}}], + } + ) + with pytest.raises(ChannelError): + await ApiObjectChannel.join_by_row_key(socket, "test-value", "test-key") + + +async def test_api_object_channel_update_fields_sends_valid_push_and_receives_contract_valid_reply( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:object:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "update_fields": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiObjectChannel.join_by_id(socket, "test-id") + reply = await channel.update_fields({"fields": {}}) + assert reply["status"] == "ok" + + observed = await client.observations("api:object:test-id", "update_fields") + assert len(observed) == 1 + assert observed[0]["params"]["fields"] == {} + + +async def test_api_object_channel_update_fields_returns_error_envelope_when_required_missing(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:object:test-id", + "onJoin": [{"type": "autoReply"}], + } + ) + channel = await ApiObjectChannel.join_by_id(socket, "test-id") + reply = await channel.update_fields({}) + assert reply["status"] == "error" + + +async def test_api_object_channel_save_sends_valid_push_and_receives_contract_valid_reply(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:object:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "save": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiObjectChannel.join_by_id(socket, "test-id") + reply = await channel.save({}) + assert reply["status"] == "ok" + + observed = await client.observations("api:object:test-id", "save") + assert len(observed) == 1 + assert observed[0]["params"] is not None + + +async def test_api_object_channel_on_object_updated_delivers_contract_valid_payloads(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:object:test-id", + "onJoin": [ + {"type": "autoReply"}, + {"type": "autoPush", "event": "object_updated"}, + ], + } + ) + channel = await ApiObjectChannel.join_by_id(socket, "test-id") + future: asyncio.Future = asyncio.get_event_loop().create_future() + + def handler(payload): + if not future.done(): + future.set_result(payload) + + channel.on_object_updated(handler) + payload = await asyncio.wait_for(future, timeout=1.0) + assert payload is not None + + +async def test_api_object_channel_on_object_created_delivers_contract_valid_payloads(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:object:test-id", + "onJoin": [ + {"type": "autoReply"}, + {"type": "autoPush", "event": "object_created"}, + ], + } + ) + channel = await ApiObjectChannel.join_by_id(socket, "test-id") + future: asyncio.Future = asyncio.get_event_loop().create_future() + + def handler(payload): + if not future.done(): + future.set_result(payload) + + channel.on_object_created(handler) + payload = await asyncio.wait_for(future, timeout=1.0) + assert payload is not None + + +async def test_api_object_channel_leave_leaves_cleanly_through_generated_leave(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:object:test-id", + "onJoin": [{"type": "autoReply"}], + } + ) + channel = await ApiObjectChannel.join_by_id(socket, "test-id") + await channel.leave() diff --git a/tests/contract/conftest.py b/tests/contract/conftest.py new file mode 100644 index 0000000..0c3160c --- /dev/null +++ b/tests/contract/conftest.py @@ -0,0 +1,163 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 77cf6ffac1d2 + +import json +import os +import selectors +import signal +import subprocess +import time + +import httpx +import pytest + +PRISM_PORT = os.environ.get("PRISM_PORT", "4040") +PRISM_URL = f"http://127.0.0.1:{PRISM_PORT}" +SPEC_PATH = os.environ.get( + "OPENAPI_SPEC_PATH", + os.path.join(os.path.dirname(__file__), "../../specs/platform-openapi.json"), +) + +_prism_process = None + +HARNESS_BIN = os.environ.get( + "ARCHASTRO_HARNESS_BIN", + os.path.join( + os.path.dirname(__file__), + "../../node_modules/@archastro/channel-harness/dist/bin.js", + ), +) +_harness_process = None +_harness_urls: dict[str, str] | None = None + + +def _channel_tests_enabled() -> bool: + """Channel contract tests are opt-in — set ARCHASTRO_RUN_CHANNEL_CONTRACT_TESTS=1 to run.""" + return os.environ.get("ARCHASTRO_RUN_CHANNEL_CONTRACT_TESTS", "") in ("1", "true", "yes") + + +# Skip the channel test tree entirely at collection time when the env var +# is not set, so CI runs REST tests without pulling in the harness service. +collect_ignore_glob = [] if _channel_tests_enabled() else ["channels/*"] + + +def pytest_configure(config): + global _prism_process + _prism_process = subprocess.Popen( + [ + "npx", + "@stoplight/prism-cli", + "mock", + SPEC_PATH, + "--port", + PRISM_PORT, + "--host", + "127.0.0.1", + "--dynamic", + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + _wait_for_prism() + if _channel_tests_enabled(): + _start_harness_service() + + +def pytest_unconfigure(config): + if _prism_process: + _prism_process.send_signal(signal.SIGTERM) + try: + _prism_process.wait(timeout=10) + except subprocess.TimeoutExpired: + _prism_process.kill() + if _channel_tests_enabled(): + _stop_harness_service() + + +def _wait_for_prism(timeout=30): + deadline = time.time() + timeout + while time.time() < deadline: + # Fast-fail if Prism process exited (bad spec path, missing npx, etc.) + if _prism_process.poll() is not None: + stderr = _prism_process.stderr.read().decode() if _prism_process.stderr else "" + raise RuntimeError(f"Prism exited with code {_prism_process.returncode}: {stderr}") + try: + httpx.get(f"{PRISM_URL}/") + return + except httpx.ConnectError: + time.sleep(0.3) + raise RuntimeError(f"Prism did not start on port {PRISM_PORT} within {timeout}s") + + +def _start_harness_service(timeout: float = 15.0) -> None: + global _harness_process, _harness_urls + if not os.path.exists(HARNESS_BIN): + raise RuntimeError( + f"channel-harness bin not found at {HARNESS_BIN}. Set ARCHASTRO_HARNESS_BIN " + f"or run 'npm install @archastro/channel-harness' (or 'npm run build' in the archastro-openapi workspace)." + ) + _harness_process = subprocess.Popen( + ["node", HARNESS_BIN, SPEC_PATH], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + # Use selectors to bound each wait so the deadline actually fires even + # if the subprocess starts but stalls before printing a line — plain + # readline() on a blocking pipe would hang pytest_configure forever. + assert _harness_process.stdout is not None + _selector = selectors.DefaultSelector() + _selector.register(_harness_process.stdout, selectors.EVENT_READ) + deadline = time.time() + timeout + buf = "" + try: + while True: + remaining = deadline - time.time() + if remaining <= 0: + raise RuntimeError(f"harness service did not report URLs within {timeout}s") + if _harness_process.poll() is not None: + err = _harness_process.stderr.read() if _harness_process.stderr else "" + raise RuntimeError( + f"harness service exited with code {_harness_process.returncode} before reporting URLs\n{err}" + ) + if not _selector.select(timeout=min(remaining, 0.25)): + continue + chunk = _harness_process.stdout.readline() + if not chunk: + time.sleep(0.05) + continue + buf += chunk + if "\n" not in buf: + continue + line, _, buf = buf.partition("\n") + parsed = json.loads(line.strip()) + if "wsUrl" in parsed and "controlUrl" in parsed: + _harness_urls = parsed + os.environ["ARCHASTRO_HARNESS_WS_URL"] = parsed["wsUrl"] + os.environ["ARCHASTRO_HARNESS_CONTROL_URL"] = parsed["controlUrl"] + return + finally: + _selector.close() + + +def _stop_harness_service() -> None: + global _harness_process + if _harness_process is None: + return + try: + _harness_process.send_signal(signal.SIGTERM) + _harness_process.wait(timeout=5) + except subprocess.TimeoutExpired: + _harness_process.kill() + _harness_process.wait(timeout=2) + finally: + _harness_process = None + + +@pytest.fixture(scope="session") +def harness_service() -> dict[str, str]: + """Resolved wsUrl + controlUrl for the running harness service.""" + if _harness_urls is None: + raise RuntimeError("harness service was not started by pytest_configure") + return _harness_urls diff --git a/tests/contract/v1/test_agent_computers.py b/tests/contract/v1/test_agent_computers.py new file mode 100644 index 0000000..6b02b31 --- /dev/null +++ b/tests/contract/v1/test_agent_computers.py @@ -0,0 +1,151 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: a5481f7b6264 + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_agent_computers_delete_success(): + client = _client() + result = await client.v1.agent_computers.delete("test-value") + assert result is None + + +async def test_agent_computers_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_computers.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_computers_delete_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_computers.delete("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_computers_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_computers.delete("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_computers_get_success(): + client = _client() + result = await client.v1.agent_computers.get("test-value") + assert result is not None + + +async def test_agent_computers_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_computers.get("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_computers_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_computers.get("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_computers_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_computers.get("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_computers_exec_success(): + client = _client() + result = await client.v1.agent_computers.exec( + "test-value", {"command": "test-value", "computer": "test-value"} + ) + assert result is not None + + +async def test_agent_computers_exec_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_computers.exec( + "test-value", {"command": "test-value", "computer": "test-value"} + ) + assert exc_info.value.status == 401 + + +async def test_agent_computers_exec_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_computers.exec( + "test-value", {"command": "test-value", "computer": "test-value"} + ) + assert exc_info.value.status == 403 + + +async def test_agent_computers_exec_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_computers.exec( + "test-value", {"command": "test-value", "computer": "test-value"} + ) + assert exc_info.value.status == 404 + + +async def test_agent_computers_exec_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_computers.exec( + "test-value", {"command": "test-value", "computer": "test-value"} + ) + assert exc_info.value.status == 422 + + +async def test_agent_computers_refresh_success(): + client = _client() + result = await client.v1.agent_computers.refresh("test-value", {"computer": "test-value"}) + assert result is not None + + +async def test_agent_computers_refresh_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_computers.refresh("test-value", {"computer": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_computers_refresh_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_computers.refresh("test-value", {"computer": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_computers_refresh_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_computers.refresh("test-value", {"computer": "test-value"}) + assert exc_info.value.status == 404 diff --git a/tests/contract/v1/test_agent_installations.py b/tests/contract/v1/test_agent_installations.py new file mode 100644 index 0000000..8839354 --- /dev/null +++ b/tests/contract/v1/test_agent_installations.py @@ -0,0 +1,277 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 3ec14aeaa53e + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_agent_installations_list_success(): + client = _client() + result = await client.v1.agent_installations.list() + assert result is not None + + +async def test_agent_installations_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.list() + assert exc_info.value.status == 401 + + +async def test_agent_installations_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.list() + assert exc_info.value.status == 403 + + +async def test_agent_installations_delete_success(): + client = _client() + result = await client.v1.agent_installations.delete("test-value") + assert result is None + + +async def test_agent_installations_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_installations_delete_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.delete("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_installations_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.delete("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_installations_get_success(): + client = _client() + result = await client.v1.agent_installations.get("test-value") + assert result is not None + + +async def test_agent_installations_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.get("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_installations_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.get("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_installations_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.get("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_installations_activate_success(): + client = _client() + result = await client.v1.agent_installations.activate( + "test-value", {"installation": "test-value"} + ) + assert result is not None + + +async def test_agent_installations_activate_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.activate("test-value", {"installation": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_installations_activate_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.activate("test-value", {"installation": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_installations_activate_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.activate("test-value", {"installation": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agent_installations_activate_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.activate("test-value", {"installation": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_agent_installations_pause_success(): + client = _client() + result = await client.v1.agent_installations.pause("test-value", {"installation": "test-value"}) + assert result is not None + + +async def test_agent_installations_pause_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.pause("test-value", {"installation": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_installations_pause_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.pause("test-value", {"installation": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_installations_pause_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.pause("test-value", {"installation": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agent_installations_pause_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.pause("test-value", {"installation": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_agent_installations_suspend_success(): + client = _client() + result = await client.v1.agent_installations.suspend( + "test-value", {"installation": "test-value"} + ) + assert result is not None + + +async def test_agent_installations_suspend_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.suspend("test-value", {"installation": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_installations_suspend_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.suspend("test-value", {"installation": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_installations_suspend_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.suspend("test-value", {"installation": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agent_installations_suspend_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.suspend("test-value", {"installation": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_agent_installations_installation_sources_list_success(): + client = _client() + result = await client.v1.agent_installations.installation_sources.list("test-value") + assert result is not None + + +async def test_agent_installations_installation_sources_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.installation_sources.list("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_installations_installation_sources_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.installation_sources.list("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_installations_installation_sources_list_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.installation_sources.list("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_installations_installation_sources_create_success(): + client = _client() + result = await client.v1.agent_installations.installation_sources.create( + "test-value", {"installation": "test-value", "payload": {}, "type": "test"} + ) + assert result is not None + + +async def test_agent_installations_installation_sources_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.installation_sources.create( + "test-value", {"installation": "test-value", "payload": {}, "type": "test"} + ) + assert exc_info.value.status == 401 + + +async def test_agent_installations_installation_sources_create_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.installation_sources.create( + "test-value", {"installation": "test-value", "payload": {}, "type": "test"} + ) + assert exc_info.value.status == 403 + + +async def test_agent_installations_installation_sources_create_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.installation_sources.create( + "test-value", {"installation": "test-value", "payload": {}, "type": "test"} + ) + assert exc_info.value.status == 404 + + +async def test_agent_installations_installation_sources_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_installations.installation_sources.create( + "test-value", {"installation": "test-value", "payload": {}, "type": "test"} + ) + assert exc_info.value.status == 422 diff --git a/tests/contract/v1/test_agent_routines.py b/tests/contract/v1/test_agent_routines.py new file mode 100644 index 0000000..1ca96ee --- /dev/null +++ b/tests/contract/v1/test_agent_routines.py @@ -0,0 +1,320 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 1c3246e1c210 + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_agent_routines_list_success(): + client = _client() + result = await client.v1.agent_routines.list() + assert result is not None + + +async def test_agent_routines_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.list() + assert exc_info.value.status == 401 + + +async def test_agent_routines_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.list() + assert exc_info.value.status == 403 + + +async def test_agent_routines_presets_success(): + client = _client() + result = await client.v1.agent_routines.presets() + assert result is not None + + +async def test_agent_routines_presets_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.presets() + assert exc_info.value.status == 401 + + +async def test_agent_routines_presets_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.presets() + assert exc_info.value.status == 403 + + +async def test_agent_routines_delete_success(): + client = _client() + result = await client.v1.agent_routines.delete("test-value") + assert result is None + + +async def test_agent_routines_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_routines_delete_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.delete("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_routines_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.delete("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_routines_get_success(): + client = _client() + result = await client.v1.agent_routines.get("test-value") + assert result is not None + + +async def test_agent_routines_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.get("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_routines_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.get("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_routines_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.get("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_routines_update_success(): + client = _client() + result = await client.v1.agent_routines.update("test-value", {"routine": "test-value"}) + assert result is not None + + +async def test_agent_routines_update_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.update("test-value", {"routine": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_routines_update_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.update("test-value", {"routine": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_routines_update_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.update("test-value", {"routine": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agent_routines_update_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.update("test-value", {"routine": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_agent_routines_activate_success(): + client = _client() + result = await client.v1.agent_routines.activate("test-value", {"routine": "test-value"}) + assert result is not None + + +async def test_agent_routines_activate_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.activate("test-value", {"routine": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_routines_activate_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.activate("test-value", {"routine": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_routines_activate_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.activate("test-value", {"routine": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agent_routines_activate_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.activate("test-value", {"routine": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_agent_routines_invoke_success(): + client = _client() + result = await client.v1.agent_routines.invoke( + "test-value", {"message": "test-value", "routine": "test-value"} + ) + assert result is not None + + +async def test_agent_routines_invoke_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.invoke( + "test-value", {"message": "test-value", "routine": "test-value"} + ) + assert exc_info.value.status == 401 + + +async def test_agent_routines_invoke_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.invoke( + "test-value", {"message": "test-value", "routine": "test-value"} + ) + assert exc_info.value.status == 403 + + +async def test_agent_routines_invoke_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.invoke( + "test-value", {"message": "test-value", "routine": "test-value"} + ) + assert exc_info.value.status == 404 + + +async def test_agent_routines_invoke_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.invoke( + "test-value", {"message": "test-value", "routine": "test-value"} + ) + assert exc_info.value.status == 422 + + +async def test_agent_routines_pause_success(): + client = _client() + result = await client.v1.agent_routines.pause("test-value", {"routine": "test-value"}) + assert result is not None + + +async def test_agent_routines_pause_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.pause("test-value", {"routine": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_routines_pause_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.pause("test-value", {"routine": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_routines_pause_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.pause("test-value", {"routine": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agent_routines_runs_success(): + client = _client() + result = await client.v1.agent_routines.runs("test-value") + assert result is not None + + +async def test_agent_routines_runs_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.runs("test-value") + assert exc_info.value.status == 400 + + +async def test_agent_routines_runs_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.runs("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_routines_runs_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.runs("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_routines_runs_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.runs("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_routines_agent_routine_runs_get_success(): + client = _client() + result = await client.v1.agent_routines.agent_routine_runs.get("test-value") + assert result is not None + + +async def test_agent_routines_agent_routine_runs_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.agent_routine_runs.get("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_routines_agent_routine_runs_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.agent_routine_runs.get("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_routines_agent_routine_runs_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_routines.agent_routine_runs.get("test-value") + assert exc_info.value.status == 404 diff --git a/tests/contract/v1/test_agent_sessions.py b/tests/contract/v1/test_agent_sessions.py new file mode 100644 index 0000000..d2d7345 --- /dev/null +++ b/tests/contract/v1/test_agent_sessions.py @@ -0,0 +1,241 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 50c616fa1fbc + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_agent_sessions_list_success(): + client = _client() + result = await client.v1.agent_sessions.list() + assert result is not None + + +async def test_agent_sessions_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.list() + assert exc_info.value.status == 401 + + +async def test_agent_sessions_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.list() + assert exc_info.value.status == 403 + + +async def test_agent_sessions_list_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.list() + assert exc_info.value.status == 422 + + +async def test_agent_sessions_create_success(): + client = _client() + result = await client.v1.agent_sessions.create( + {"agent": "test-value", "instructions": "test-value"} + ) + assert result is not None + + +async def test_agent_sessions_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.create({"agent": "test-value", "instructions": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_sessions_create_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.create({"agent": "test-value", "instructions": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_sessions_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.create({"agent": "test-value", "instructions": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_agent_sessions_delete_success(): + client = _client() + result = await client.v1.agent_sessions.delete("test-value") + assert result is None + + +async def test_agent_sessions_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_sessions_delete_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.delete("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_sessions_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.delete("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_sessions_get_success(): + client = _client() + result = await client.v1.agent_sessions.get("test-value") + assert result is not None + + +async def test_agent_sessions_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.get("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_sessions_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.get("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_sessions_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.get("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_sessions_update_success(): + client = _client() + result = await client.v1.agent_sessions.update("test-value", {"agent_session": "test-value"}) + assert result is not None + + +async def test_agent_sessions_update_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.update("test-value", {"agent_session": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_sessions_update_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.update("test-value", {"agent_session": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_sessions_update_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.update("test-value", {"agent_session": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agent_sessions_update_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.update("test-value", {"agent_session": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_agent_sessions_cancel_success(): + client = _client() + result = await client.v1.agent_sessions.cancel("test-value", {"agent_session": "test-value"}) + assert result is not None + + +async def test_agent_sessions_cancel_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.cancel("test-value", {"agent_session": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_sessions_cancel_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.cancel("test-value", {"agent_session": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_sessions_cancel_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.cancel("test-value", {"agent_session": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agent_sessions_message_success(): + client = _client() + result = await client.v1.agent_sessions.message( + "test-value", {"agent_session": "test-value", "content": "test content"} + ) + assert result is not None + + +async def test_agent_sessions_message_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.message( + "test-value", {"agent_session": "test-value", "content": "test content"} + ) + assert exc_info.value.status == 401 + + +async def test_agent_sessions_message_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.message( + "test-value", {"agent_session": "test-value", "content": "test content"} + ) + assert exc_info.value.status == 403 + + +async def test_agent_sessions_message_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.message( + "test-value", {"agent_session": "test-value", "content": "test content"} + ) + assert exc_info.value.status == 404 + + +async def test_agent_sessions_message_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_sessions.message( + "test-value", {"agent_session": "test-value", "content": "test content"} + ) + assert exc_info.value.status == 422 diff --git a/tests/contract/v1/test_agent_skills.py b/tests/contract/v1/test_agent_skills.py new file mode 100644 index 0000000..6cb3db3 --- /dev/null +++ b/tests/contract/v1/test_agent_skills.py @@ -0,0 +1,222 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 04324cac9db4 + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_agent_skills_list_success(): + client = _client() + result = await client.v1.agent_skills.list() + assert result is not None + + +async def test_agent_skills_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.list() + assert exc_info.value.status == 401 + + +async def test_agent_skills_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.list() + assert exc_info.value.status == 403 + + +async def test_agent_skills_create_success(): + client = _client() + result = await client.v1.agent_skills.create({"agent": "test-value", "config": "test-value"}) + assert result is not None + + +async def test_agent_skills_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.create({"agent": "test-value", "config": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_skills_create_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.create({"agent": "test-value", "config": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_skills_create_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.create({"agent": "test-value", "config": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agent_skills_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.create({"agent": "test-value", "config": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_agent_skills_delete_success(): + client = _client() + result = await client.v1.agent_skills.delete("test-value") + assert result is None + + +async def test_agent_skills_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_skills_delete_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.delete("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_skills_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.delete("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_skills_get_success(): + client = _client() + result = await client.v1.agent_skills.get("test-value") + assert result is not None + + +async def test_agent_skills_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.get("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_skills_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.get("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_skills_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.get("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_skills_update_success(): + client = _client() + result = await client.v1.agent_skills.update("test-value", {"agent_skill": "test-value"}) + assert result is not None + + +async def test_agent_skills_update_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.update("test-value", {"agent_skill": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_skills_update_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.update("test-value", {"agent_skill": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_skills_update_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.update("test-value", {"agent_skill": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agent_skills_update_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.update("test-value", {"agent_skill": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_agent_skills_activate_success(): + client = _client() + result = await client.v1.agent_skills.activate("test-value", {"agent_skill": "test-value"}) + assert result is not None + + +async def test_agent_skills_activate_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.activate("test-value", {"agent_skill": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_skills_activate_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.activate("test-value", {"agent_skill": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_skills_activate_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.activate("test-value", {"agent_skill": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agent_skills_deactivate_success(): + client = _client() + result = await client.v1.agent_skills.deactivate("test-value", {"agent_skill": "test-value"}) + assert result is not None + + +async def test_agent_skills_deactivate_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.deactivate("test-value", {"agent_skill": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_skills_deactivate_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.deactivate("test-value", {"agent_skill": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_skills_deactivate_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_skills.deactivate("test-value", {"agent_skill": "test-value"}) + assert exc_info.value.status == 404 diff --git a/tests/contract/v1/test_agent_tools.py b/tests/contract/v1/test_agent_tools.py new file mode 100644 index 0000000..a790b81 --- /dev/null +++ b/tests/contract/v1/test_agent_tools.py @@ -0,0 +1,215 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: a10f73f80349 + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_agent_tools_list_success(): + client = _client() + result = await client.v1.agent_tools.list() + assert result is not None + + +async def test_agent_tools_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.list() + assert exc_info.value.status == 401 + + +async def test_agent_tools_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.list() + assert exc_info.value.status == 403 + + +async def test_agent_tools_catalog_success(): + client = _client() + result = await client.v1.agent_tools.catalog() + assert result is not None + + +async def test_agent_tools_catalog_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.catalog() + assert exc_info.value.status == 401 + + +async def test_agent_tools_catalog_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.catalog() + assert exc_info.value.status == 403 + + +async def test_agent_tools_delete_success(): + client = _client() + result = await client.v1.agent_tools.delete("test-value") + assert result is None + + +async def test_agent_tools_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_tools_delete_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.delete("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_tools_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.delete("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_tools_get_success(): + client = _client() + result = await client.v1.agent_tools.get("test-value") + assert result is not None + + +async def test_agent_tools_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.get("test-value") + assert exc_info.value.status == 401 + + +async def test_agent_tools_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.get("test-value") + assert exc_info.value.status == 403 + + +async def test_agent_tools_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.get("test-value") + assert exc_info.value.status == 404 + + +async def test_agent_tools_update_success(): + client = _client() + result = await client.v1.agent_tools.update("test-value", {"tool": "test-value"}) + assert result is not None + + +async def test_agent_tools_update_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.update("test-value", {"tool": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_tools_update_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.update("test-value", {"tool": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_tools_update_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.update("test-value", {"tool": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agent_tools_update_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.update("test-value", {"tool": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_agent_tools_activate_success(): + client = _client() + result = await client.v1.agent_tools.activate("test-value", {"tool": "test-value"}) + assert result is not None + + +async def test_agent_tools_activate_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.activate("test-value", {"tool": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_tools_activate_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.activate("test-value", {"tool": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_tools_activate_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.activate("test-value", {"tool": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agent_tools_activate_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.activate("test-value", {"tool": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_agent_tools_deactivate_success(): + client = _client() + result = await client.v1.agent_tools.deactivate("test-value", {"tool": "test-value"}) + assert result is not None + + +async def test_agent_tools_deactivate_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.deactivate("test-value", {"tool": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agent_tools_deactivate_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.deactivate("test-value", {"tool": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agent_tools_deactivate_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agent_tools.deactivate("test-value", {"tool": "test-value"}) + assert exc_info.value.status == 404 diff --git a/tests/contract/v1/test_agents.py b/tests/contract/v1/test_agents.py new file mode 100644 index 0000000..dc83f8f --- /dev/null +++ b/tests/contract/v1/test_agents.py @@ -0,0 +1,610 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 4326b01f839d + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_agents_list_success(): + client = _client() + result = await client.v1.agents.list() + assert result is not None + + +async def test_agents_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.list() + assert exc_info.value.status == 401 + + +async def test_agents_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.list() + assert exc_info.value.status == 403 + + +async def test_agents_create_success(): + client = _client() + result = await client.v1.agents.create({}) + assert result is not None + + +async def test_agents_create_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.create({}) + assert exc_info.value.status == 400 + + +async def test_agents_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.create({}) + assert exc_info.value.status == 401 + + +async def test_agents_create_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.create({}) + assert exc_info.value.status == 403 + + +async def test_agents_create_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.create({}) + assert exc_info.value.status == 404 + + +async def test_agents_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.create({}) + assert exc_info.value.status == 422 + + +async def test_agents_delete_success(): + client = _client() + result = await client.v1.agents.delete("test-value") + assert result is None + + +async def test_agents_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_agents_delete_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.delete("test-value") + assert exc_info.value.status == 403 + + +async def test_agents_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.delete("test-value") + assert exc_info.value.status == 404 + + +async def test_agents_get_success(): + client = _client() + result = await client.v1.agents.get("test-value") + assert result is not None + + +async def test_agents_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.get("test-value") + assert exc_info.value.status == 401 + + +async def test_agents_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.get("test-value") + assert exc_info.value.status == 403 + + +async def test_agents_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.get("test-value") + assert exc_info.value.status == 404 + + +async def test_agents_update_success(): + client = _client() + result = await client.v1.agents.update("test-value", {"agent": "test-value"}) + assert result is not None + + +async def test_agents_update_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.update("test-value", {"agent": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agents_update_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.update("test-value", {"agent": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_agents_update_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.update("test-value", {"agent": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agents_update_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.update("test-value", {"agent": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_agents_agent_routines_success(): + client = _client() + result = await client.v1.agents.agent_routines( + "test-value", {"agent": "test-value", "handler_type": "test-value", "name": "test-name"} + ) + assert result is not None + + +async def test_agents_agent_routines_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_routines( + "test-value", {"agent": "test-value", "handler_type": "test-value", "name": "test-name"} + ) + assert exc_info.value.status == 401 + + +async def test_agents_agent_routines_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_routines( + "test-value", {"agent": "test-value", "handler_type": "test-value", "name": "test-name"} + ) + assert exc_info.value.status == 403 + + +async def test_agents_agent_routines_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_routines( + "test-value", {"agent": "test-value", "handler_type": "test-value", "name": "test-name"} + ) + assert exc_info.value.status == 404 + + +async def test_agents_agent_routines_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_routines( + "test-value", {"agent": "test-value", "handler_type": "test-value", "name": "test-name"} + ) + assert exc_info.value.status == 422 + + +async def test_agents_agent_working_memory_success(): + client = _client() + result = await client.v1.agents.agent_working_memory("test-value") + assert result is not None + + +async def test_agents_agent_working_memory_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_working_memory("test-value") + assert exc_info.value.status == 401 + + +async def test_agents_agent_working_memory_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_working_memory("test-value") + assert exc_info.value.status == 403 + + +async def test_agents_agent_working_memory_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_working_memory("test-value") + assert exc_info.value.status == 404 + + +async def test_agents_export_success(): + client = _client() + result = await client.v1.agents.export("test-value") + assert result is not None + + +async def test_agents_export_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.export("test-value") + assert exc_info.value.status == 401 + + +async def test_agents_export_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.export("test-value") + assert exc_info.value.status == 403 + + +async def test_agents_export_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.export("test-value") + assert exc_info.value.status == 404 + + +async def test_agents_search_success(): + client = _client() + result = await client.v1.agents.search( + "test-value", {"agent": "test-value", "query": "test-value"} + ) + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_agents_search_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.search("test-value", {"agent": "test-value", "query": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_agents_search_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.search("test-value", {"agent": "test-value", "query": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_agents_search_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.search("test-value", {"agent": "test-value", "query": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_agents_threads_success(): + client = _client() + result = await client.v1.agents.threads("test-value", {"agent": "test-value", "thread": {}}) + assert result is not None + + +async def test_agents_threads_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.threads("test-value", {"agent": "test-value", "thread": {}}) + assert exc_info.value.status == 401 + + +async def test_agents_threads_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.threads("test-value", {"agent": "test-value", "thread": {}}) + assert exc_info.value.status == 403 + + +async def test_agents_threads_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.threads("test-value", {"agent": "test-value", "thread": {}}) + assert exc_info.value.status == 404 + + +async def test_agents_threads_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.threads("test-value", {"agent": "test-value", "thread": {}}) + assert exc_info.value.status == 422 + + +async def test_agents_agent_computers_list_success(): + client = _client() + result = await client.v1.agents.agent_computers.list("test-value") + assert result is not None + + +async def test_agents_agent_computers_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_computers.list("test-value") + assert exc_info.value.status == 401 + + +async def test_agents_agent_computers_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_computers.list("test-value") + assert exc_info.value.status == 403 + + +async def test_agents_agent_computers_list_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_computers.list("test-value") + assert exc_info.value.status == 404 + + +async def test_agents_agent_computers_create_success(): + client = _client() + result = await client.v1.agents.agent_computers.create( + "test-value", {"agent": "test-value", "name": "test-name"} + ) + assert result is not None + + +async def test_agents_agent_computers_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_computers.create( + "test-value", {"agent": "test-value", "name": "test-name"} + ) + assert exc_info.value.status == 401 + + +async def test_agents_agent_computers_create_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_computers.create( + "test-value", {"agent": "test-value", "name": "test-name"} + ) + assert exc_info.value.status == 403 + + +async def test_agents_agent_computers_create_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_computers.create( + "test-value", {"agent": "test-value", "name": "test-name"} + ) + assert exc_info.value.status == 404 + + +async def test_agents_agent_computers_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_computers.create( + "test-value", {"agent": "test-value", "name": "test-name"} + ) + assert exc_info.value.status == 422 + + +async def test_agents_agent_installations_list_success(): + client = _client() + result = await client.v1.agents.agent_installations.list("test-value") + assert result is not None + + +async def test_agents_agent_installations_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_installations.list("test-value") + assert exc_info.value.status == 401 + + +async def test_agents_agent_installations_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_installations.list("test-value") + assert exc_info.value.status == 403 + + +async def test_agents_agent_installations_list_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_installations.list("test-value") + assert exc_info.value.status == 404 + + +async def test_agents_agent_installations_create_success(): + client = _client() + result = await client.v1.agents.agent_installations.create( + "test-value", {"agent": "test-value", "kind": "test"} + ) + assert result is not None + + +async def test_agents_agent_installations_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_installations.create( + "test-value", {"agent": "test-value", "kind": "test"} + ) + assert exc_info.value.status == 401 + + +async def test_agents_agent_installations_create_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_installations.create( + "test-value", {"agent": "test-value", "kind": "test"} + ) + assert exc_info.value.status == 403 + + +async def test_agents_agent_installations_create_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_installations.create( + "test-value", {"agent": "test-value", "kind": "test"} + ) + assert exc_info.value.status == 404 + + +async def test_agents_agent_installations_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_installations.create( + "test-value", {"agent": "test-value", "kind": "test"} + ) + assert exc_info.value.status == 422 + + +async def test_agents_agent_installations_kinds_success(): + client = _client() + result = await client.v1.agents.agent_installations.kinds("test-value") + assert result is not None + + +async def test_agents_agent_installations_kinds_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_installations.kinds("test-value") + assert exc_info.value.status == 401 + + +async def test_agents_agent_installations_kinds_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_installations.kinds("test-value") + assert exc_info.value.status == 403 + + +async def test_agents_agent_tools_list_success(): + client = _client() + result = await client.v1.agents.agent_tools.list("test-value") + assert result is not None + + +async def test_agents_agent_tools_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_tools.list("test-value") + assert exc_info.value.status == 401 + + +async def test_agents_agent_tools_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_tools.list("test-value") + assert exc_info.value.status == 403 + + +async def test_agents_agent_tools_create_success(): + client = _client() + result = await client.v1.agents.agent_tools.create( + "test-value", {"agent": "test-value", "kind": "test"} + ) + assert result is not None + + +async def test_agents_agent_tools_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_tools.create("test-value", {"agent": "test-value", "kind": "test"}) + assert exc_info.value.status == 401 + + +async def test_agents_agent_tools_create_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_tools.create("test-value", {"agent": "test-value", "kind": "test"}) + assert exc_info.value.status == 403 + + +async def test_agents_agent_tools_create_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_tools.create("test-value", {"agent": "test-value", "kind": "test"}) + assert exc_info.value.status == 404 + + +async def test_agents_agent_tools_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.agent_tools.create("test-value", {"agent": "test-value", "kind": "test"}) + assert exc_info.value.status == 422 + + +async def test_agents_schedules_list_success(): + client = _client() + result = await client.v1.agents.schedules.list("test-value") + assert result is not None + + +async def test_agents_schedules_list_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.schedules.list("test-value") + assert exc_info.value.status == 400 + + +async def test_agents_schedules_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.schedules.list("test-value") + assert exc_info.value.status == 401 + + +async def test_agents_schedules_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.schedules.list("test-value") + assert exc_info.value.status == 403 + + +async def test_agents_schedules_get_success(): + client = _client() + result = await client.v1.agents.schedules.get("test-value", "test-value") + assert result is not None + + +async def test_agents_schedules_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.schedules.get("test-value", "test-value") + assert exc_info.value.status == 401 + + +async def test_agents_schedules_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.schedules.get("test-value", "test-value") + assert exc_info.value.status == 403 + + +async def test_agents_schedules_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.agents.schedules.get("test-value", "test-value") + assert exc_info.value.status == 404 diff --git a/tests/contract/v1/test_ai.py b/tests/contract/v1/test_ai.py new file mode 100644 index 0000000..dcb355f --- /dev/null +++ b/tests/contract/v1/test_ai.py @@ -0,0 +1,165 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 9bc1145890cd + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_ai_chat_completions_success(): + client = _client() + result = await client.v1.ai.chat.completions( + {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} + ) + assert result is not None + + +async def test_ai_chat_completions_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.ai.chat.completions( + {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} + ) + assert exc_info.value.status == 400 + + +async def test_ai_chat_completions_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.ai.chat.completions( + {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} + ) + assert exc_info.value.status == 401 + + +async def test_ai_chat_completions_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.ai.chat.completions( + {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} + ) + assert exc_info.value.status == 422 + + +async def test_ai_chat_models_success(): + client = _client() + result = await client.v1.ai.chat.models() + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_ai_chat_models_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.ai.chat.models() + assert exc_info.value.status == 401 + + +async def test_ai_image_edits_success(): + client = _client() + result = await client.v1.ai.image.edits( + { + "images": [{"image_data": "test-value", "image_type": "test-value"}], + "prompt": "test-value", + } + ) + assert result is not None + + +async def test_ai_image_edits_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.ai.image.edits( + { + "images": [{"image_data": "test-value", "image_type": "test-value"}], + "prompt": "test-value", + } + ) + assert exc_info.value.status == 400 + + +async def test_ai_image_edits_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.ai.image.edits( + { + "images": [{"image_data": "test-value", "image_type": "test-value"}], + "prompt": "test-value", + } + ) + assert exc_info.value.status == 401 + + +async def test_ai_image_edits_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.ai.image.edits( + { + "images": [{"image_data": "test-value", "image_type": "test-value"}], + "prompt": "test-value", + } + ) + assert exc_info.value.status == 422 + + +async def test_ai_image_generations_success(): + client = _client() + result = await client.v1.ai.image.generations({"prompt": "test-value"}) + assert result is not None + + +async def test_ai_image_generations_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.ai.image.generations({"prompt": "test-value"}) + assert exc_info.value.status == 400 + + +async def test_ai_image_generations_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.ai.image.generations({"prompt": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_ai_image_generations_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.ai.image.generations({"prompt": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_ai_image_models_success(): + client = _client() + result = await client.v1.ai.image.models() + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_ai_image_models_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.ai.image.models() + assert exc_info.value.status == 401 diff --git a/tests/contract/v1/test_artifacts.py b/tests/contract/v1/test_artifacts.py new file mode 100644 index 0000000..370164a --- /dev/null +++ b/tests/contract/v1/test_artifacts.py @@ -0,0 +1,185 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: cd8f35e16277 + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_artifacts_delete_success(): + client = _client() + result = await client.v1.artifacts.delete("test-value") + assert result is None + + +async def test_artifacts_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_artifacts_delete_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.delete("test-value") + assert exc_info.value.status == 403 + + +async def test_artifacts_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.delete("test-value") + assert exc_info.value.status == 404 + + +async def test_artifacts_get_success(): + client = _client() + result = await client.v1.artifacts.get("test-value") + assert result is not None + + +async def test_artifacts_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.get("test-value") + assert exc_info.value.status == 401 + + +async def test_artifacts_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.get("test-value") + assert exc_info.value.status == 403 + + +async def test_artifacts_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.get("test-value") + assert exc_info.value.status == 404 + + +async def test_artifacts_replace_success(): + client = _client() + result = await client.v1.artifacts.replace( + "test-value", {"artifact": "test-value", "from_version": 1} + ) + assert result is not None + + +async def test_artifacts_replace_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.replace("test-value", {"artifact": "test-value", "from_version": 1}) + assert exc_info.value.status == 401 + + +async def test_artifacts_replace_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.replace("test-value", {"artifact": "test-value", "from_version": 1}) + assert exc_info.value.status == 403 + + +async def test_artifacts_replace_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.replace("test-value", {"artifact": "test-value", "from_version": 1}) + assert exc_info.value.status == 404 + + +async def test_artifacts_replace_error_409(): + ec = _error_client(409) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.replace("test-value", {"artifact": "test-value", "from_version": 1}) + assert exc_info.value.status == 409 + + +async def test_artifacts_replace_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.replace("test-value", {"artifact": "test-value", "from_version": 1}) + assert exc_info.value.status == 422 + + +async def test_artifacts_archive_success(): + client = _client() + result = await client.v1.artifacts.archive("test-value", {"artifact": "test-value"}) + assert result is None + + +async def test_artifacts_archive_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.archive("test-value", {"artifact": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_artifacts_archive_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.archive("test-value", {"artifact": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_artifacts_archive_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.archive("test-value", {"artifact": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_artifacts_content_success(): + client = _client() + result = await client.v1.artifacts.content("test-value") + assert result["content"] is not None + assert result["mime_type"] + + +async def test_artifacts_content_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.content("test-value") + assert exc_info.value.status == 401 + + +async def test_artifacts_content_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.content("test-value") + assert exc_info.value.status == 403 + + +async def test_artifacts_content_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.content("test-value") + assert exc_info.value.status == 404 + + +async def test_artifacts_content_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.artifacts.content("test-value") + assert exc_info.value.status == 422 diff --git a/tests/contract/v1/test_automation_runs.py b/tests/contract/v1/test_automation_runs.py new file mode 100644 index 0000000..a5a43c3 --- /dev/null +++ b/tests/contract/v1/test_automation_runs.py @@ -0,0 +1,53 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: aee4c433c044 + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_automation_runs_get_success(): + client = _client() + result = await client.v1.automation_runs.get("test-value") + assert result is not None + + +async def test_automation_runs_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.automation_runs.get("test-value") + assert exc_info.value.status == 401 + + +async def test_automation_runs_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.automation_runs.get("test-value") + assert exc_info.value.status == 403 + + +async def test_automation_runs_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.automation_runs.get("test-value") + assert exc_info.value.status == 404 diff --git a/tests/contract/v1/test_automations.py b/tests/contract/v1/test_automations.py new file mode 100644 index 0000000..91cae1c --- /dev/null +++ b/tests/contract/v1/test_automations.py @@ -0,0 +1,60 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 7586dd4e50cc + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_automations_invoke_success(): + client = _client() + result = await client.v1.automations.invoke("test-value", {"automation": "test-value"}) + assert result is not None + + +async def test_automations_invoke_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.automations.invoke("test-value", {"automation": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_automations_invoke_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.automations.invoke("test-value", {"automation": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_automations_invoke_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.automations.invoke("test-value", {"automation": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_automations_invoke_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.automations.invoke("test-value", {"automation": "test-value"}) + assert exc_info.value.status == 422 diff --git a/tests/contract/v1/test_config.py b/tests/contract/v1/test_config.py new file mode 100644 index 0000000..462cdeb --- /dev/null +++ b/tests/contract/v1/test_config.py @@ -0,0 +1,461 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: f97b2d7bdc4a + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_config_list_success(): + client = _client() + result = await client.v1.config.list() + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_config_list_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.list() + assert exc_info.value.status == 400 + + +async def test_config_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.list() + assert exc_info.value.status == 401 + + +async def test_config_create_success(): + client = _client() + result = await client.v1.config.create( + {"kind": "test", "mime_type": "application/json", "raw_content": "test content"} + ) + assert result is not None + + +async def test_config_create_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.create( + {"kind": "test", "mime_type": "application/json", "raw_content": "test content"} + ) + assert exc_info.value.status == 400 + + +async def test_config_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.create( + {"kind": "test", "mime_type": "application/json", "raw_content": "test content"} + ) + assert exc_info.value.status == 401 + + +async def test_config_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.create( + {"kind": "test", "mime_type": "application/json", "raw_content": "test content"} + ) + assert exc_info.value.status == 422 + + +async def test_config_encrypt_secret_success(): + client = _client() + result = await client.v1.config.encrypt_secret({"plaintext": "test-value"}) + assert result is not None + + +async def test_config_encrypt_secret_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.encrypt_secret({"plaintext": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_config_encrypt_secret_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.encrypt_secret({"plaintext": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_config_validate_success(): + client = _client() + result = await client.v1.config.validate( + {"kind": "test", "mime_type": "application/json", "raw_content": "test content"} + ) + assert result is not None + + +async def test_config_validate_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.validate( + {"kind": "test", "mime_type": "application/json", "raw_content": "test content"} + ) + assert exc_info.value.status == 400 + + +async def test_config_validate_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.validate( + {"kind": "test", "mime_type": "application/json", "raw_content": "test content"} + ) + assert exc_info.value.status == 401 + + +async def test_config_delete_success(): + client = _client() + result = await client.v1.config.delete("test-value") + assert result is None + + +async def test_config_delete_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.delete("test-value") + assert exc_info.value.status == 400 + + +async def test_config_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_config_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.delete("test-value") + assert exc_info.value.status == 404 + + +async def test_config_get_success(): + client = _client() + result = await client.v1.config.get("test-value") + assert result is not None + + +async def test_config_get_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.get("test-value") + assert exc_info.value.status == 400 + + +async def test_config_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.get("test-value") + assert exc_info.value.status == 401 + + +async def test_config_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.get("test-value") + assert exc_info.value.status == 404 + + +async def test_config_replace_success(): + client = _client() + result = await client.v1.config.replace( + "test-value", + {"config": "test-value", "mime_type": "application/json", "raw_content": "test content"}, + ) + assert result is not None + + +async def test_config_replace_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.replace( + "test-value", + { + "config": "test-value", + "mime_type": "application/json", + "raw_content": "test content", + }, + ) + assert exc_info.value.status == 400 + + +async def test_config_replace_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.replace( + "test-value", + { + "config": "test-value", + "mime_type": "application/json", + "raw_content": "test content", + }, + ) + assert exc_info.value.status == 401 + + +async def test_config_replace_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.replace( + "test-value", + { + "config": "test-value", + "mime_type": "application/json", + "raw_content": "test content", + }, + ) + assert exc_info.value.status == 404 + + +async def test_config_replace_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.replace( + "test-value", + { + "config": "test-value", + "mime_type": "application/json", + "raw_content": "test content", + }, + ) + assert exc_info.value.status == 422 + + +async def test_config_archive_success(): + client = _client() + result = await client.v1.config.archive("test-value", {"config": "test-value"}) + assert result is not None + + +async def test_config_archive_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.archive("test-value", {"config": "test-value"}) + assert exc_info.value.status == 400 + + +async def test_config_archive_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.archive("test-value", {"config": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_config_archive_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.archive("test-value", {"config": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_config_content_success(): + client = _client() + result = await client.v1.config.content("test-value") + assert result["content"] is not None + assert result["mime_type"] + + +async def test_config_content_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.content("test-value") + assert exc_info.value.status == 400 + + +async def test_config_content_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.content("test-value") + assert exc_info.value.status == 401 + + +async def test_config_content_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.content("test-value") + assert exc_info.value.status == 404 + + +async def test_config_unarchive_success(): + client = _client() + result = await client.v1.config.unarchive("test-value", {"config": "test-value"}) + assert result is not None + + +async def test_config_unarchive_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.unarchive("test-value", {"config": "test-value"}) + assert exc_info.value.status == 400 + + +async def test_config_unarchive_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.unarchive("test-value", {"config": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_config_unarchive_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.unarchive("test-value", {"config": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_config_versions_success(): + client = _client() + result = await client.v1.config.versions("test-value") + assert result is not None + + +async def test_config_versions_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.versions("test-value") + assert exc_info.value.status == 400 + + +async def test_config_versions_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.versions("test-value") + assert exc_info.value.status == 401 + + +async def test_config_versions_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.versions("test-value") + assert exc_info.value.status == 404 + + +async def test_config_kinds_list_success(): + client = _client() + result = await client.v1.config.kinds.list() + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_config_kinds_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.kinds.list() + assert exc_info.value.status == 401 + + +async def test_config_kinds_schema_success(): + client = _client() + result = await client.v1.config.kinds.schema("test") + assert result is not None + + +async def test_config_kinds_schema_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.kinds.schema("test") + assert exc_info.value.status == 401 + + +async def test_config_kinds_schema_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.kinds.schema("test") + assert exc_info.value.status == 404 + + +async def test_config_system_list_success(): + client = _client() + result = await client.v1.config.system.list() + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_config_system_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.system.list() + assert exc_info.value.status == 401 + + +async def test_config_system_get_success(): + client = _client() + result = await client.v1.config.system.get("test-value") + assert result is not None + + +async def test_config_system_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.system.get("test-value") + assert exc_info.value.status == 401 + + +async def test_config_system_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.system.get("test-value") + assert exc_info.value.status == 404 + + +async def test_config_system_clone_success(): + client = _client() + result = await client.v1.config.system.clone("test-value", {"system": "test-value"}) + assert result is not None + + +async def test_config_system_clone_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.system.clone("test-value", {"system": "test-value"}) + assert exc_info.value.status == 400 + + +async def test_config_system_clone_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.system.clone("test-value", {"system": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_config_system_clone_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.system.clone("test-value", {"system": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_config_system_clone_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.config.system.clone("test-value", {"system": "test-value"}) + assert exc_info.value.status == 422 diff --git a/tests/contract/v1/test_custom_objects.py b/tests/contract/v1/test_custom_objects.py new file mode 100644 index 0000000..c827dea --- /dev/null +++ b/tests/contract/v1/test_custom_objects.py @@ -0,0 +1,95 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: a245f9200fe5 + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_custom_objects_delete_success(): + client = _client() + result = await client.v1.custom_objects.delete("test-value") + assert result is None + + +async def test_custom_objects_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.custom_objects.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_custom_objects_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.custom_objects.delete("test-value") + assert exc_info.value.status == 404 + + +async def test_custom_objects_get_success(): + client = _client() + result = await client.v1.custom_objects.get("test-value") + assert result is not None + + +async def test_custom_objects_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.custom_objects.get("test-value") + assert exc_info.value.status == 401 + + +async def test_custom_objects_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.custom_objects.get("test-value") + assert exc_info.value.status == 404 + + +async def test_custom_objects_replace_success(): + client = _client() + result = await client.v1.custom_objects.replace( + "test-value", {"fields": {}, "object": "test-value"} + ) + assert result is not None + + +async def test_custom_objects_replace_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.custom_objects.replace("test-value", {"fields": {}, "object": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_custom_objects_replace_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.custom_objects.replace("test-value", {"fields": {}, "object": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_custom_objects_replace_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.custom_objects.replace("test-value", {"fields": {}, "object": "test-value"}) + assert exc_info.value.status == 422 diff --git a/tests/contract/v1/test_installation_sources.py b/tests/contract/v1/test_installation_sources.py new file mode 100644 index 0000000..606815b --- /dev/null +++ b/tests/contract/v1/test_installation_sources.py @@ -0,0 +1,53 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 3f92a95ee470 + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_installation_sources_delete_success(): + client = _client() + result = await client.v1.installation_sources.delete("test-value") + assert result is None + + +async def test_installation_sources_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.installation_sources.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_installation_sources_delete_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.installation_sources.delete("test-value") + assert exc_info.value.status == 403 + + +async def test_installation_sources_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.installation_sources.delete("test-value") + assert exc_info.value.status == 404 diff --git a/tests/contract/v1/test_kv.py b/tests/contract/v1/test_kv.py new file mode 100644 index 0000000..27a785b --- /dev/null +++ b/tests/contract/v1/test_kv.py @@ -0,0 +1,147 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: dbb888c6a01c + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_kv_list_success(): + client = _client() + result = await client.v1.kv.list() + assert result is not None + + +async def test_kv_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.kv.list() + assert exc_info.value.status == 401 + + +async def test_kv_create_success(): + client = _client() + result = await client.v1.kv.create({"key": "test-key", "value": "test-value"}) + assert result is not None + + +async def test_kv_create_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.kv.create({"key": "test-key", "value": "test-value"}) + assert exc_info.value.status == 400 + + +async def test_kv_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.kv.create({"key": "test-key", "value": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_kv_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.kv.create({"key": "test-key", "value": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_kv_delete_success(): + client = _client() + result = await client.v1.kv.delete("test-key") + assert result is None + + +async def test_kv_delete_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.kv.delete("test-key") + assert exc_info.value.status == 400 + + +async def test_kv_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.kv.delete("test-key") + assert exc_info.value.status == 401 + + +async def test_kv_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.kv.delete("test-key") + assert exc_info.value.status == 404 + + +async def test_kv_get_success(): + client = _client() + result = await client.v1.kv.get("test-key") + assert result is not None + + +async def test_kv_get_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.kv.get("test-key") + assert exc_info.value.status == 400 + + +async def test_kv_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.kv.get("test-key") + assert exc_info.value.status == 401 + + +async def test_kv_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.kv.get("test-key") + assert exc_info.value.status == 404 + + +async def test_kv_upsert_success(): + client = _client() + result = await client.v1.kv.upsert("test-key", {"value": "test-value"}) + assert result is not None + + +async def test_kv_upsert_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.kv.upsert("test-key", {"value": "test-value"}) + assert exc_info.value.status == 400 + + +async def test_kv_upsert_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.kv.upsert("test-key", {"value": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_kv_upsert_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.kv.upsert("test-key", {"value": "test-value"}) + assert exc_info.value.status == 422 diff --git a/tests/contract/v1/test_orgs.py b/tests/contract/v1/test_orgs.py new file mode 100644 index 0000000..7dbe3c8 --- /dev/null +++ b/tests/contract/v1/test_orgs.py @@ -0,0 +1,48 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 33049a9d58cc + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_orgs_list_success(): + client = _client() + result = await client.v1.orgs.list() + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_orgs_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.orgs.list() + assert exc_info.value.status == 401 + + +async def test_orgs_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.orgs.list() + assert exc_info.value.status == 403 diff --git a/tests/contract/v1/test_team_memberships.py b/tests/contract/v1/test_team_memberships.py new file mode 100644 index 0000000..93e94c6 --- /dev/null +++ b/tests/contract/v1/test_team_memberships.py @@ -0,0 +1,66 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 8394a4e65595 + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_team_memberships_list_success(): + client = _client() + result = await client.v1.team_memberships.list() + assert result is not None + + +async def test_team_memberships_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.team_memberships.list() + assert exc_info.value.status == 401 + + +async def test_team_memberships_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.team_memberships.list() + assert exc_info.value.status == 403 + + +async def test_team_memberships_delete_success(): + client = _client() + result = await client.v1.team_memberships.delete("test-value") + assert result is None + + +async def test_team_memberships_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.team_memberships.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_team_memberships_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.team_memberships.delete("test-value") + assert exc_info.value.status == 404 diff --git a/tests/contract/v1/test_teams.py b/tests/contract/v1/test_teams.py new file mode 100644 index 0000000..66aad1e --- /dev/null +++ b/tests/contract/v1/test_teams.py @@ -0,0 +1,518 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 2ca47dd776e0 + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_teams_list_success(): + client = _client() + result = await client.v1.teams.list() + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_teams_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.list() + assert exc_info.value.status == 401 + + +async def test_teams_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.list() + assert exc_info.value.status == 403 + + +async def test_teams_create_success(): + client = _client() + result = await client.v1.teams.create({"name": "test-name"}) + assert result is not None + + +async def test_teams_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.create({"name": "test-name"}) + assert exc_info.value.status == 401 + + +async def test_teams_create_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.create({"name": "test-name"}) + assert exc_info.value.status == 403 + + +async def test_teams_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.create({"name": "test-name"}) + assert exc_info.value.status == 422 + + +async def test_teams_join_by_code_success(): + client = _client() + result = await client.v1.teams.join_by_code({}) + assert result is not None + + +async def test_teams_join_by_code_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.join_by_code({}) + assert exc_info.value.status == 400 + + +async def test_teams_join_by_code_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.join_by_code({}) + assert exc_info.value.status == 401 + + +async def test_teams_join_by_code_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.join_by_code({}) + assert exc_info.value.status == 404 + + +async def test_teams_join_by_code_error_429(): + ec = _error_client(429) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.join_by_code({}) + assert exc_info.value.status == 429 + + +async def test_teams_delete_success(): + client = _client() + result = await client.v1.teams.delete("test-value") + assert result is None + + +async def test_teams_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_teams_delete_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.delete("test-value") + assert exc_info.value.status == 403 + + +async def test_teams_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.delete("test-value") + assert exc_info.value.status == 404 + + +async def test_teams_get_success(): + client = _client() + result = await client.v1.teams.get("test-value") + assert result is not None + + +async def test_teams_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.get("test-value") + assert exc_info.value.status == 401 + + +async def test_teams_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.get("test-value") + assert exc_info.value.status == 403 + + +async def test_teams_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.get("test-value") + assert exc_info.value.status == 404 + + +async def test_teams_update_success(): + client = _client() + result = await client.v1.teams.update("test-value", {"team": "test-value"}) + assert result is not None + + +async def test_teams_update_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.update("test-value", {"team": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_teams_update_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.update("test-value", {"team": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_teams_update_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.update("test-value", {"team": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_teams_update_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.update("test-value", {"team": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_teams_invite_success(): + client = _client() + result = await client.v1.teams.invite("test-value", {"team": "test-value"}) + assert result is not None + + +async def test_teams_invite_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.invite("test-value", {"team": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_teams_invite_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.invite("test-value", {"team": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_teams_invite_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.invite("test-value", {"team": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_teams_invites_success(): + client = _client() + result = await client.v1.teams.invites("test-value", {"team": "test-value"}) + assert result is not None + + +async def test_teams_invites_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.invites("test-value", {"team": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_teams_invites_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.invites("test-value", {"team": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_teams_join_success(): + client = _client() + result = await client.v1.teams.join("test-value", {"team": "test-value"}) + assert result is None + + +async def test_teams_join_error_400(): + ec = _error_client(400) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.join("test-value", {"team": "test-value"}) + assert exc_info.value.status == 400 + + +async def test_teams_join_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.join("test-value", {"team": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_teams_join_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.join("test-value", {"team": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_teams_join_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.join("test-value", {"team": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_teams_leave_success(): + client = _client() + result = await client.v1.teams.leave("test-value") + assert result is None + + +async def test_teams_leave_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.leave("test-value") + assert exc_info.value.status == 401 + + +async def test_teams_leave_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.leave("test-value") + assert exc_info.value.status == 403 + + +async def test_teams_leave_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.leave("test-value") + assert exc_info.value.status == 404 + + +async def test_teams_leave_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.leave("test-value") + assert exc_info.value.status == 422 + + +async def test_teams_artifacts_list_success(): + client = _client() + result = await client.v1.teams.artifacts.list("test-value") + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_teams_artifacts_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.artifacts.list("test-value") + assert exc_info.value.status == 401 + + +async def test_teams_artifacts_list_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.artifacts.list("test-value") + assert exc_info.value.status == 404 + + +async def test_teams_artifacts_create_success(): + client = _client() + result = await client.v1.teams.artifacts.create("test-value", {"artifact": {}}) + assert result is not None + + +async def test_teams_artifacts_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.artifacts.create("test-value", {"artifact": {}}) + assert exc_info.value.status == 401 + + +async def test_teams_artifacts_create_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.artifacts.create("test-value", {"artifact": {}}) + assert exc_info.value.status == 403 + + +async def test_teams_artifacts_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.artifacts.create("test-value", {"artifact": {}}) + assert exc_info.value.status == 422 + + +async def test_teams_custom_objects_list_success(): + client = _client() + result = await client.v1.teams.custom_objects.list("test-value", type="test") + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_teams_custom_objects_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.custom_objects.list("test-value", type="test") + assert exc_info.value.status == 401 + + +async def test_teams_custom_objects_list_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.custom_objects.list("test-value", type="test") + assert exc_info.value.status == 404 + + +async def test_teams_custom_objects_create_success(): + client = _client() + result = await client.v1.teams.custom_objects.create( + "test-value", {"fields": {}, "team": "test-value", "type": "test"} + ) + assert result is not None + + +async def test_teams_custom_objects_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.custom_objects.create( + "test-value", {"fields": {}, "team": "test-value", "type": "test"} + ) + assert exc_info.value.status == 401 + + +async def test_teams_custom_objects_create_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.custom_objects.create( + "test-value", {"fields": {}, "team": "test-value", "type": "test"} + ) + assert exc_info.value.status == 404 + + +async def test_teams_custom_objects_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.custom_objects.create( + "test-value", {"fields": {}, "team": "test-value", "type": "test"} + ) + assert exc_info.value.status == 422 + + +async def test_teams_members_list_success(): + client = _client() + result = await client.v1.teams.members.list("test-value") + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_teams_members_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.members.list("test-value") + assert exc_info.value.status == 401 + + +async def test_teams_members_list_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.members.list("test-value") + assert exc_info.value.status == 404 + + +async def test_teams_members_create_success(): + client = _client() + result = await client.v1.teams.members.create("test-value", {"team": "test-value"}) + assert result is not None + + +async def test_teams_members_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.members.create("test-value", {"team": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_teams_members_create_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.members.create("test-value", {"team": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_teams_members_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.members.create("test-value", {"team": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_teams_threads_list_success(): + client = _client() + result = await client.v1.teams.threads.list("test-value") + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_teams_threads_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.threads.list("test-value") + assert exc_info.value.status == 401 + + +async def test_teams_threads_list_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.threads.list("test-value") + assert exc_info.value.status == 404 + + +async def test_teams_threads_create_success(): + client = _client() + result = await client.v1.teams.threads.create( + "test-value", {"team": "test-value", "thread": {}} + ) + assert result is not None + + +async def test_teams_threads_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.threads.create("test-value", {"team": "test-value", "thread": {}}) + assert exc_info.value.status == 401 + + +async def test_teams_threads_create_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.threads.create("test-value", {"team": "test-value", "thread": {}}) + assert exc_info.value.status == 404 + + +async def test_teams_threads_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.teams.threads.create("test-value", {"team": "test-value", "thread": {}}) + assert exc_info.value.status == 422 diff --git a/tests/contract/v1/test_thread_messages.py b/tests/contract/v1/test_thread_messages.py new file mode 100644 index 0000000..db5a9e2 --- /dev/null +++ b/tests/contract/v1/test_thread_messages.py @@ -0,0 +1,206 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 2f46f00d5937 + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_thread_messages_delete_success(): + client = _client() + result = await client.v1.thread_messages.delete("test-value") + assert result is None + + +async def test_thread_messages_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_thread_messages_delete_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.delete("test-value") + assert exc_info.value.status == 403 + + +async def test_thread_messages_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.delete("test-value") + assert exc_info.value.status == 404 + + +async def test_thread_messages_delete_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.delete("test-value") + assert exc_info.value.status == 422 + + +async def test_thread_messages_replace_success(): + client = _client() + result = await client.v1.thread_messages.replace("test-value", {"message": "test-value"}) + assert result is not None + + +async def test_thread_messages_replace_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.replace("test-value", {"message": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_thread_messages_replace_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.replace("test-value", {"message": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_thread_messages_replace_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.replace("test-value", {"message": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_thread_messages_replace_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.replace("test-value", {"message": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_thread_messages_replies_success(): + client = _client() + result = await client.v1.thread_messages.replies("test-value") + assert result is not None + + +async def test_thread_messages_replies_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.replies("test-value") + assert exc_info.value.status == 401 + + +async def test_thread_messages_replies_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.replies("test-value") + assert exc_info.value.status == 403 + + +async def test_thread_messages_replies_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.replies("test-value") + assert exc_info.value.status == 404 + + +async def test_thread_messages_replies_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.replies("test-value") + assert exc_info.value.status == 422 + + +async def test_thread_messages_reactions_remove_success(): + client = _client() + result = await client.v1.thread_messages.reactions.remove("test-value") + assert result is None + + +async def test_thread_messages_reactions_remove_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.reactions.remove("test-value") + assert exc_info.value.status == 401 + + +async def test_thread_messages_reactions_remove_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.reactions.remove("test-value") + assert exc_info.value.status == 403 + + +async def test_thread_messages_reactions_remove_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.reactions.remove("test-value") + assert exc_info.value.status == 404 + + +async def test_thread_messages_reactions_remove_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.reactions.remove("test-value") + assert exc_info.value.status == 422 + + +async def test_thread_messages_reactions_create_success(): + client = _client() + result = await client.v1.thread_messages.reactions.create( + "test-value", {"emoji": "test-value", "message": "test-value"} + ) + assert result is not None + + +async def test_thread_messages_reactions_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.reactions.create( + "test-value", {"emoji": "test-value", "message": "test-value"} + ) + assert exc_info.value.status == 401 + + +async def test_thread_messages_reactions_create_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.reactions.create( + "test-value", {"emoji": "test-value", "message": "test-value"} + ) + assert exc_info.value.status == 403 + + +async def test_thread_messages_reactions_create_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.reactions.create( + "test-value", {"emoji": "test-value", "message": "test-value"} + ) + assert exc_info.value.status == 404 + + +async def test_thread_messages_reactions_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.thread_messages.reactions.create( + "test-value", {"emoji": "test-value", "message": "test-value"} + ) + assert exc_info.value.status == 422 diff --git a/tests/contract/v1/test_threads.py b/tests/contract/v1/test_threads.py new file mode 100644 index 0000000..402f736 --- /dev/null +++ b/tests/contract/v1/test_threads.py @@ -0,0 +1,514 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 497234290bcd + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_threads_delete_success(): + client = _client() + result = await client.v1.threads.delete("test-value") + assert result is None + + +async def test_threads_delete_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.delete("test-value") + assert exc_info.value.status == 401 + + +async def test_threads_delete_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.delete("test-value") + assert exc_info.value.status == 403 + + +async def test_threads_delete_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.delete("test-value") + assert exc_info.value.status == 404 + + +async def test_threads_get_success(): + client = _client() + result = await client.v1.threads.get("test-value") + assert result is not None + + +async def test_threads_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.get("test-value") + assert exc_info.value.status == 401 + + +async def test_threads_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.get("test-value") + assert exc_info.value.status == 403 + + +async def test_threads_get_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.get("test-value") + assert exc_info.value.status == 404 + + +async def test_threads_replace_success(): + client = _client() + result = await client.v1.threads.replace("test-value", {"thread": "test-value"}) + assert result is not None + + +async def test_threads_replace_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.replace("test-value", {"thread": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_threads_replace_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.replace("test-value", {"thread": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_threads_replace_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.replace("test-value", {"thread": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_threads_replace_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.replace("test-value", {"thread": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_threads_agents_success(): + client = _client() + result = await client.v1.threads.agents("test-value") + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_threads_agents_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.agents("test-value") + assert exc_info.value.status == 401 + + +async def test_threads_agents_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.agents("test-value") + assert exc_info.value.status == 403 + + +async def test_threads_agents_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.agents("test-value") + assert exc_info.value.status == 404 + + +async def test_threads_artifacts_success(): + client = _client() + result = await client.v1.threads.artifacts("test-value") + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_threads_artifacts_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.artifacts("test-value") + assert exc_info.value.status == 401 + + +async def test_threads_artifacts_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.artifacts("test-value") + assert exc_info.value.status == 403 + + +async def test_threads_artifacts_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.artifacts("test-value") + assert exc_info.value.status == 404 + + +async def test_threads_mark_read_success(): + client = _client() + result = await client.v1.threads.mark_read("test-value", {"thread": "test-value"}) + assert result is None + + +async def test_threads_mark_read_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.mark_read("test-value", {"thread": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_threads_mark_read_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.mark_read("test-value", {"thread": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_threads_mark_read_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.mark_read("test-value", {"thread": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_threads_messages_success(): + client = _client() + result = await client.v1.threads.messages("test-value") + assert result is not None + + +async def test_threads_messages_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.messages("test-value") + assert exc_info.value.status == 401 + + +async def test_threads_messages_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.messages("test-value") + assert exc_info.value.status == 403 + + +async def test_threads_messages_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.messages("test-value") + assert exc_info.value.status == 404 + + +async def test_threads_picture_success(): + client = _client() + result = await client.v1.threads.picture( + "test-value", + { + "picture": { + "data": "test-value", + "filename": "test-value", + "mime_type": "application/json", + }, + "thread": "test-value", + }, + ) + assert result is not None + + +async def test_threads_picture_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.picture( + "test-value", + { + "picture": { + "data": "test-value", + "filename": "test-value", + "mime_type": "application/json", + }, + "thread": "test-value", + }, + ) + assert exc_info.value.status == 401 + + +async def test_threads_picture_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.picture( + "test-value", + { + "picture": { + "data": "test-value", + "filename": "test-value", + "mime_type": "application/json", + }, + "thread": "test-value", + }, + ) + assert exc_info.value.status == 403 + + +async def test_threads_picture_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.picture( + "test-value", + { + "picture": { + "data": "test-value", + "filename": "test-value", + "mime_type": "application/json", + }, + "thread": "test-value", + }, + ) + assert exc_info.value.status == 404 + + +async def test_threads_picture_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.picture( + "test-value", + { + "picture": { + "data": "test-value", + "filename": "test-value", + "mime_type": "application/json", + }, + "thread": "test-value", + }, + ) + assert exc_info.value.status == 422 + + +async def test_threads_read_status_success(): + client = _client() + result = await client.v1.threads.read_status("test-value") + assert result is not None + + +async def test_threads_read_status_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.read_status("test-value") + assert exc_info.value.status == 401 + + +async def test_threads_read_status_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.read_status("test-value") + assert exc_info.value.status == 403 + + +async def test_threads_read_status_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.read_status("test-value") + assert exc_info.value.status == 404 + + +async def test_threads_search_success(): + client = _client() + result = await client.v1.threads.search("test-value", q="test-value") + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_threads_search_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.search("test-value", q="test-value") + assert exc_info.value.status == 401 + + +async def test_threads_search_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.search("test-value", q="test-value") + assert exc_info.value.status == 403 + + +async def test_threads_search_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.search("test-value", q="test-value") + assert exc_info.value.status == 404 + + +async def test_threads_members_remove_success(): + client = _client() + result = await client.v1.threads.members.remove("test-value") + assert result is None + + +async def test_threads_members_remove_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.members.remove("test-value") + assert exc_info.value.status == 401 + + +async def test_threads_members_remove_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.members.remove("test-value") + assert exc_info.value.status == 404 + + +async def test_threads_members_remove_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.members.remove("test-value") + assert exc_info.value.status == 422 + + +async def test_threads_members_list_success(): + client = _client() + result = await client.v1.threads.members.list("test-value") + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_threads_members_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.members.list("test-value") + assert exc_info.value.status == 401 + + +async def test_threads_members_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.members.list("test-value") + assert exc_info.value.status == 403 + + +async def test_threads_members_list_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.members.list("test-value") + assert exc_info.value.status == 404 + + +async def test_threads_members_create_success(): + client = _client() + result = await client.v1.threads.members.create( + "test-value", {"thread": "test-value", "type": "test"} + ) + assert result is not None + + +async def test_threads_members_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.members.create("test-value", {"thread": "test-value", "type": "test"}) + assert exc_info.value.status == 401 + + +async def test_threads_members_create_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.members.create("test-value", {"thread": "test-value", "type": "test"}) + assert exc_info.value.status == 404 + + +async def test_threads_members_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.members.create("test-value", {"thread": "test-value", "type": "test"}) + assert exc_info.value.status == 422 + + +async def test_threads_settings_list_success(): + client = _client() + result = await client.v1.threads.settings.list("test-value") + assert result is not None + + +async def test_threads_settings_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.settings.list("test-value") + assert exc_info.value.status == 401 + + +async def test_threads_settings_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.settings.list("test-value") + assert exc_info.value.status == 403 + + +async def test_threads_settings_list_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.settings.list("test-value") + assert exc_info.value.status == 404 + + +async def test_threads_settings_replace_success(): + client = _client() + result = await client.v1.threads.settings.replace( + "test-value", {"settings": {}, "thread": "test-value"} + ) + assert result is not None + + +async def test_threads_settings_replace_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.settings.replace("test-value", {"settings": {}, "thread": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_threads_settings_replace_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.settings.replace("test-value", {"settings": {}, "thread": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_threads_settings_replace_error_404(): + ec = _error_client(404) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.settings.replace("test-value", {"settings": {}, "thread": "test-value"}) + assert exc_info.value.status == 404 + + +async def test_threads_settings_replace_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.threads.settings.replace("test-value", {"settings": {}, "thread": "test-value"}) + assert exc_info.value.status == 422 diff --git a/tests/contract/v1/test_users.py b/tests/contract/v1/test_users.py new file mode 100644 index 0000000..fd79bbb --- /dev/null +++ b/tests/contract/v1/test_users.py @@ -0,0 +1,194 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 20b2d53d4c4f + +import pytest + +from archastro.platform import PlatformClient +from archastro.platform.runtime.http_client import ApiError + +PRISM_URL = "http://127.0.0.1:4040" + + +def _client() -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + + +def _error_client(code: int) -> PlatformClient: + return PlatformClient( + base_url=PRISM_URL, + default_headers={"x-archastro-api-key": "pk_test-key", "Prefer": f"code={code}"}, + access_token="test-token", + ) + + +async def test_users_me_success(): + client = _client() + result = await client.v1.users.me() + assert result is not None + + +async def test_users_me_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.me() + assert exc_info.value.status == 401 + + +async def test_users_get_success(): + client = _client() + result = await client.v1.users.get("test-value") + assert result is not None + + +async def test_users_get_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.get("test-value") + assert exc_info.value.status == 401 + + +async def test_users_get_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.get("test-value") + assert exc_info.value.status == 403 + + +async def test_users_orgs_success(): + client = _client() + result = await client.v1.users.orgs("test-value") + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_users_orgs_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.orgs("test-value") + assert exc_info.value.status == 401 + + +async def test_users_profile_success(): + client = _client() + result = await client.v1.users.profile("test-value", {"user": "test-value"}) + assert result is not None + + +async def test_users_profile_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.profile("test-value", {"user": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_users_profile_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.profile("test-value", {"user": "test-value"}) + assert exc_info.value.status == 422 + + +async def test_users_artifacts_list_success(): + client = _client() + result = await client.v1.users.artifacts.list("test-value") + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_users_artifacts_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.artifacts.list("test-value") + assert exc_info.value.status == 401 + + +async def test_users_artifacts_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.artifacts.list("test-value") + assert exc_info.value.status == 403 + + +async def test_users_artifacts_create_success(): + client = _client() + result = await client.v1.users.artifacts.create("test-value", {"artifact": {}}) + assert result is not None + + +async def test_users_artifacts_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.artifacts.create("test-value", {"artifact": {}}) + assert exc_info.value.status == 401 + + +async def test_users_artifacts_create_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.artifacts.create("test-value", {"artifact": {}}) + assert exc_info.value.status == 403 + + +async def test_users_artifacts_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.artifacts.create("test-value", {"artifact": {}}) + assert exc_info.value.status == 422 + + +async def test_users_threads_list_success(): + client = _client() + result = await client.v1.users.threads.list("test-value") + assert result is not None + assert "data" in result + assert isinstance(result["data"], list) + + +async def test_users_threads_list_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.threads.list("test-value") + assert exc_info.value.status == 401 + + +async def test_users_threads_list_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.threads.list("test-value") + assert exc_info.value.status == 403 + + +async def test_users_threads_create_success(): + client = _client() + result = await client.v1.users.threads.create( + "test-value", {"thread": {}, "user": "test-value"} + ) + assert result is not None + + +async def test_users_threads_create_error_401(): + ec = _error_client(401) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.threads.create("test-value", {"thread": {}, "user": "test-value"}) + assert exc_info.value.status == 401 + + +async def test_users_threads_create_error_403(): + ec = _error_client(403) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.threads.create("test-value", {"thread": {}, "user": "test-value"}) + assert exc_info.value.status == 403 + + +async def test_users_threads_create_error_422(): + ec = _error_client(422) + with pytest.raises(ApiError) as exc_info: + await ec.v1.users.threads.create("test-value", {"thread": {}, "user": "test-value"}) + assert exc_info.value.status == 422 diff --git a/tests/harness/conftest.py b/tests/harness/conftest.py new file mode 100644 index 0000000..f0b246f --- /dev/null +++ b/tests/harness/conftest.py @@ -0,0 +1,137 @@ +""" +Spawn the @archastro/channel-harness subprocess for the Python side. + +The subprocess is the same service TS tests spawn — a compiled Node CLI +from the @archastro/channel-harness npm package — so both languages +drive the same `ContractServer` and exercise the same wire contract. + +This conftest: + + 1. Locates the channel-harness bin under node_modules (installed by + `npm ci` at the repo root). + 2. Spawns it pointed at the LiveDoc fixture spec. + 3. Parses the first JSON line on stdout to discover the ephemeral URLs. + 4. Exposes those URLs via a session fixture + environment variables. + 5. Kills the subprocess on session teardown. + +Keeping the lifecycle at session scope matches the TS `globalSetup` story — +one service instance, scenarios/observations reset between tests through +``HarnessServiceClient.reset()``. +""" + +from __future__ import annotations + +import json +import os +import selectors +import signal +import subprocess +import time +from collections.abc import Iterator +from pathlib import Path + +import pytest + + +def _repo_root() -> Path: + p = Path(__file__).resolve().parent + while p != p.parent: + if (p / ".git").exists(): + return p + p = p.parent + raise RuntimeError("Could not find repo root from tests/harness/conftest.py") + + +REPO_ROOT = _repo_root() +HARNESS_BIN = Path( + os.environ.get( + "ARCHASTRO_HARNESS_BIN", + REPO_ROOT / "node_modules" / "@archastro" / "channel-harness" / "dist" / "bin.js", + ) +) +SPEC_PATH = Path(__file__).resolve().parent / "fixtures" / "channel-harness-spec.json" + + +@pytest.fixture(scope="session") +def harness_service() -> Iterator[dict[str, str]]: + """Session-scoped: the running service's ``wsUrl`` and ``controlUrl``.""" + if not HARNESS_BIN.exists(): + raise RuntimeError( + f"channel-harness bin not found at {HARNESS_BIN}. " + f"Run `npm ci` at the repo root to install @archastro/channel-harness, " + f"or set ARCHASTRO_HARNESS_BIN to point at an alternate checkout." + ) + + proc = subprocess.Popen( + ["node", str(HARNESS_BIN), str(SPEC_PATH)], + cwd=str(REPO_ROOT), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + + try: + urls = _read_first_json_line(proc, timeout=15.0) + except Exception: + _terminate(proc) + raise + + os.environ["ARCHASTRO_HARNESS_WS_URL"] = urls["wsUrl"] + os.environ["ARCHASTRO_HARNESS_CONTROL_URL"] = urls["controlUrl"] + + try: + yield urls + finally: + _terminate(proc) + os.environ.pop("ARCHASTRO_HARNESS_WS_URL", None) + os.environ.pop("ARCHASTRO_HARNESS_CONTROL_URL", None) + + +def _read_first_json_line(proc: subprocess.Popen[str], *, timeout: float) -> dict[str, str]: + assert proc.stdout is not None + # Use `selectors` to bound each wait to a fraction of the deadline so a + # subprocess that starts but stalls before printing can't hang pytest. + selector = selectors.DefaultSelector() + selector.register(proc.stdout, selectors.EVENT_READ) + deadline = time.monotonic() + timeout + buf = "" + try: + while True: + remaining = deadline - time.monotonic() + if remaining <= 0: + raise RuntimeError( + f"timed out after {timeout}s waiting for harness service to emit URLs" + ) + if proc.poll() is not None: + err = proc.stderr.read() if proc.stderr else "" + raise RuntimeError( + f"harness service exited with code {proc.returncode} before " + f"reporting URLs\nstderr: {err}" + ) + events = selector.select(timeout=min(remaining, 0.25)) + if not events: + continue + chunk = proc.stdout.readline() + if not chunk: + time.sleep(0.05) + continue + buf += chunk + if "\n" not in buf: + continue + line, _, buf = buf.partition("\n") + parsed = json.loads(line.strip()) + if "wsUrl" in parsed and "controlUrl" in parsed: + return parsed # type: ignore[return-value] + finally: + selector.close() + + +def _terminate(proc: subprocess.Popen[str]) -> None: + if proc.poll() is not None: + return + try: + proc.send_signal(signal.SIGTERM) + proc.wait(timeout=2) + except subprocess.TimeoutExpired: + proc.kill() + proc.wait(timeout=2) diff --git a/tests/harness/fixtures/channel-harness-spec.json b/tests/harness/fixtures/channel-harness-spec.json new file mode 100644 index 0000000..98c59c4 --- /dev/null +++ b/tests/harness/fixtures/channel-harness-spec.json @@ -0,0 +1,131 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "LiveDoc Channel API", + "version": "0.1.0", + "description": "Fake real-time collaborative-document API used to exercise the channel harness end-to-end." + }, + "paths": {}, + "components": { + "schemas": { + "User": { + "type": "object", + "required": ["id", "name"], + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" } + } + }, + "Document": { + "type": "object", + "required": ["id", "content", "version"], + "properties": { + "id": { "type": "string" }, + "content": { "type": "string" }, + "version": { "type": "integer" } + } + }, + "EditApplied": { + "type": "object", + "required": ["user_id", "position", "text", "version"], + "properties": { + "user_id": { "type": "string" }, + "position": { "type": "integer" }, + "text": { "type": "string" }, + "version": { "type": "integer" } + } + } + } + }, + "x-channels": [ + { + "name": "LiveDoc", + "description": "Collaborative editing channel for a single document.", + "joins": [ + { + "pattern": "doc:{doc_id}", + "name": "join_document", + "description": "Subscribe to a document and receive its current state.", + "params": { + "type": "object", + "required": ["userId"], + "properties": { + "userId": { "type": "string" }, + "cursor": { "type": "integer" } + } + }, + "returns": { + "type": "object", + "required": ["document", "collaborators"], + "properties": { + "document": { "$ref": "#/components/schemas/Document" }, + "collaborators": { + "type": "array", + "items": { "$ref": "#/components/schemas/User" } + } + } + } + } + ], + "messages": [ + { + "event": "edit", + "description": "Apply an edit at a position. Server assigns the next version.", + "params": { + "type": "object", + "required": ["position", "text"], + "properties": { + "position": { "type": "integer" }, + "text": { "type": "string" } + } + }, + "returns": { + "type": "object", + "required": ["version"], + "properties": { + "version": { "type": "integer" } + } + } + }, + { + "event": "move_cursor", + "description": "Report the caller's cursor position.", + "params": { + "type": "object", + "required": ["position"], + "properties": { + "position": { "type": "integer" } + } + }, + "returns": { + "type": "object", + "properties": {} + } + } + ], + "pushes": [ + { + "event": "user_joined", + "description": "Another user joined the document.", + "payload": { "$ref": "#/components/schemas/User" } + }, + { + "event": "user_left", + "description": "A user left the document.", + "payload": { + "type": "object", + "required": ["user_id"], + "properties": { + "user_id": { "type": "string" } + } + } + }, + { + "event": "edit_applied", + "description": "An edit from another user was applied to the document.", + "payload": { "$ref": "#/components/schemas/EditApplied" } + } + ] + } + ] +} diff --git a/tests/harness/test_harness.py b/tests/harness/test_harness.py new file mode 100644 index 0000000..c744bcb --- /dev/null +++ b/tests/harness/test_harness.py @@ -0,0 +1,152 @@ +""" +Smoke tests that drive the channel-harness service from the Python side. + +Uses raw ``phx_channel.Socket`` + ``HarnessServiceClient`` — no generated +channel class involved. If these pass, the service's wire contract and the +Python runtime's reply/push handling are both sound, which is the prerequisite +for the emitted per-channel tests (added in a separate file by the generator). + +The fixture spec is ``channel-harness-spec.json`` — the same one the TS +tests use, so any asymmetry between languages shows up as a failure here. +""" + +from __future__ import annotations + +import asyncio +from typing import Any + +import pytest + +from phx_channel import HarnessServiceClient, Socket +from phx_channel.channel import ChannelError + + +@pytest.fixture +async def client(harness_service): + c = HarnessServiceClient( + ws_url=harness_service["wsUrl"], + control_url=harness_service["controlUrl"], + ) + await c.reset() + try: + yield c + finally: + await c.close() + + +async def _join(socket: Socket, topic: str, payload: dict[str, Any]) -> dict[str, Any]: + channel = socket.channel(topic) + return await channel.join(payload) + + +async def test_service_synthesizes_a_contract_valid_join_reply(client): + socket = await client.open_socket() + resp = await _join(socket, "doc:doc_42", {"userId": "user_1"}) + + assert set(resp.keys()) == {"document", "collaborators"} + document = resp["document"] + assert isinstance(document["id"], str) + assert isinstance(document["content"], str) + assert isinstance(document["version"], int) + assert isinstance(resp["collaborators"], list) + + +async def test_register_scenario_reply_error_surfaces_as_channel_error(client): + await client.register_scenario( + { + "topic": "doc:doc_42", + "onJoin": [ + {"type": "replyError", "payload": {"reason": "locked"}}, + ], + } + ) + socket = await client.open_socket() + with pytest.raises(ChannelError, match="locked"): + await _join(socket, "doc:doc_42", {"userId": "user_1"}) + + +async def test_observations_capture_inbound_message_params(client): + await client.register_scenario( + { + "topic": "doc:doc_42", + "onJoin": [{"type": "autoReply"}], + "onMessage": {"edit": [{"type": "autoReply"}]}, + } + ) + socket = await client.open_socket() + channel = socket.channel("doc:doc_42") + await channel.join({"userId": "user_1"}) + + reply = await channel.push("edit", {"position": 4, "text": "yo"}) + assert reply["status"] == "ok" + + observed = await client.observations("doc:doc_42", "edit") + assert len(observed) == 1 + assert observed[0]["params"] == {"position": 4, "text": "yo"} + + +async def test_autopush_reaches_the_python_handler(client): + await client.register_scenario( + { + "topic": "doc:doc_42", + "onJoin": [ + {"type": "autoReply"}, + {"type": "autoPush", "event": "user_joined"}, + ], + } + ) + socket = await client.open_socket() + channel = socket.channel("doc:doc_42") + received: list[Any] = [] + + future: asyncio.Future[Any] = asyncio.get_event_loop().create_future() + + def handler(payload): + received.append(payload) + if not future.done(): + future.set_result(payload) + + channel.on("user_joined", handler) + await channel.join({"userId": "user_1"}) + + payload = await asyncio.wait_for(future, timeout=1.0) + assert set(payload.keys()) == {"id", "name"} + assert isinstance(payload["id"], str) + assert isinstance(payload["name"], str) + assert received == [payload] + + +async def test_schema_invalid_push_params_reply_with_error_envelope(client): + await client.register_scenario( + { + "topic": "doc:doc_42", + "onJoin": [{"type": "autoReply"}], + } + ) + socket = await client.open_socket() + channel = socket.channel("doc:doc_42") + await channel.join({"userId": "user_1"}) + + reply = await channel.push("edit", {}) + assert reply["status"] == "error" + assert reply["response"]["reason"] == "invalid_params" + + +async def test_reset_clears_scenarios_and_observations(client): + await client.register_scenario( + { + "topic": "doc:doc_42", + "onJoin": [{"type": "autoReply"}], + } + ) + socket = await client.open_socket() + await _join(socket, "doc:doc_42", {"userId": "user_1"}) + assert len(await client.observations()) > 0 + + await client.reset() + assert await client.observations() == [] + + # After reset the topic has no scenario — default path synthesizes a reply. + socket2 = await client.open_socket() + resp = await _join(socket2, "doc:doc_42", {"userId": "user_1"}) + assert "document" in resp diff --git a/tests/test_http_client.py b/tests/test_http_client.py new file mode 100644 index 0000000..53d2e79 --- /dev/null +++ b/tests/test_http_client.py @@ -0,0 +1,255 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +"""Unit tests for HttpClient 401 auto-refresh — mirrors the TS test suite.""" + +from unittest.mock import AsyncMock, patch + +import httpx +import pytest + +from archastro.platform.runtime.http_client import ApiError, HttpClient + + +def _mock_response(status: int, body: dict | None = None) -> httpx.Response: + """Build a fake httpx.Response with the given status and JSON body.""" + resp = httpx.Response( + status_code=status, + json=body or {}, + request=httpx.Request("GET", "https://api.test"), + ) + return resp + + +async def test_retries_with_new_token_after_401(): + client = HttpClient( + base_url="https://api.test", + access_token="expired-token", + on_refresh_token=AsyncMock(return_value="fresh-token"), + ) + responses = [ + _mock_response(401, {"error": "unauthenticated", "message": "expired"}), + _mock_response(200, {"id": "123"}), + ] + call_count = 0 + + async def mock_request(*args, **kwargs): + nonlocal call_count + resp = responses[call_count] + call_count += 1 + return resp + + with patch.object(client._client, "request", side_effect=mock_request): + result = await client.request("/api/v1/things") + + assert result == {"id": "123"} + assert call_count == 2 + # Second call should use the new token + client._on_refresh_token.assert_called_once() + + +async def test_throws_401_when_no_refresh_handler(): + client = HttpClient(base_url="https://api.test", access_token="expired-token") + + with patch.object( + client._client, + "request", + return_value=_mock_response(401, {"error": "unauthenticated"}), + ): + with pytest.raises(ApiError) as exc_info: + await client.request("/api/v1/things") + assert exc_info.value.status == 401 + + +async def test_throws_401_when_refresh_handler_fails(): + async def failing_handler(): + raise RuntimeError("refresh token expired") + + client = HttpClient( + base_url="https://api.test", + access_token="expired-token", + on_refresh_token=failing_handler, + ) + + with patch.object( + client._client, + "request", + return_value=_mock_response(401, {"error": "unauthenticated"}), + ): + with pytest.raises(ApiError) as exc_info: + await client.request("/api/v1/things") + assert exc_info.value.status == 401 + + +async def test_does_not_retry_on_non_401_errors(): + handler = AsyncMock(return_value="fresh-token") + client = HttpClient( + base_url="https://api.test", + access_token="some-token", + on_refresh_token=handler, + ) + + with patch.object( + client._client, + "request", + return_value=_mock_response(403, {"error": "forbidden"}), + ): + with pytest.raises(ApiError) as exc_info: + await client.request("/api/v1/things") + assert exc_info.value.status == 403 + handler.assert_not_called() + + +async def test_does_not_retry_auth_paths(): + handler = AsyncMock(return_value="fresh-token") + client = HttpClient( + base_url="https://api.test", + access_token="expired-token", + on_refresh_token=handler, + ) + + with patch.object( + client._client, + "request", + return_value=_mock_response(401, {"error": "unauthenticated"}), + ): + with pytest.raises(ApiError): + await client.request("/api/v1/auth/refresh", method="POST") + handler.assert_not_called() + + +async def test_refresh_only_client_throws_on_non_auth_paths(): + client = HttpClient(base_url="https://api.test", refresh_only=True) + + with pytest.raises(RuntimeError, match="Refresh-only HTTP client"): + await client.request("/api/v1/agents") + + # Auth paths should work + with patch.object( + client._client, + "request", + return_value=_mock_response(200, {"token": "t"}), + ): + result = await client.request("/api/v1/auth/refresh", method="POST") + assert result == {"token": "t"} + + +async def test_concurrent_401s_piggyback_on_same_refresh(): + refresh_call_count = 0 + + async def refresh_handler(): + nonlocal refresh_call_count + refresh_call_count += 1 + return "fresh-token" + + client = HttpClient( + base_url="https://api.test", + access_token="expired-token", + on_refresh_token=refresh_handler, + ) + + call_count = 0 + + async def mock_request(*args, **kwargs): + nonlocal call_count + call_count += 1 + if call_count <= 2: + return _mock_response(401, {"error": "unauthenticated"}) + return _mock_response(200, {"id": f"item-{call_count}"}) + + with patch.object(client._client, "request", side_effect=mock_request): + import asyncio + + a, b = await asyncio.gather( + client.request("/api/v1/things"), + client.request("/api/v1/stuff"), + ) + + assert a["id"] + assert b["id"] + assert refresh_call_count == 1 + assert call_count == 4 # 2 original (401) + 2 retries (200) + + +async def test_set_refresh_handler_wires_handler_post_construction(): + client = HttpClient(base_url="https://api.test", access_token="expired-token") + client.set_refresh_handler(AsyncMock(return_value="refreshed-token")) + + responses = [ + _mock_response(401, {"error": "unauthenticated"}), + _mock_response(200, {"ok": True}), + ] + call_count = 0 + + async def mock_request(*args, **kwargs): + nonlocal call_count + resp = responses[call_count] + call_count += 1 + return resp + + with patch.object(client._client, "request", side_effect=mock_request): + result = await client.request("/api/v1/things") + + assert result == {"ok": True} + assert call_count == 2 + + +async def test_propagates_retry_error_when_refresh_succeeds_but_retry_fails(): + client = HttpClient( + base_url="https://api.test", + access_token="expired-token", + on_refresh_token=AsyncMock(return_value="fresh-token"), + ) + + responses = [ + _mock_response(401, {"error": "unauthenticated"}), + _mock_response(403, {"error": "forbidden", "message": "no access"}), + ] + call_count = 0 + + async def mock_request(*args, **kwargs): + nonlocal call_count + resp = responses[call_count] + call_count += 1 + return resp + + with patch.object(client._client, "request", side_effect=mock_request): + with pytest.raises(ApiError) as exc_info: + await client.request("/api/v1/things") + assert exc_info.value.status == 403 + assert call_count == 2 + + +async def test_clears_refresh_task_after_failure_so_future_refreshes_work(): + call_count = 0 + + async def mock_request(*args, **kwargs): + nonlocal call_count + call_count += 1 + if call_count <= 2: + return _mock_response(401, {"error": "unauthenticated"}) + return _mock_response(200, {"id": "ok"}) + + attempt = 0 + + async def refresh_handler(): + nonlocal attempt + attempt += 1 + if attempt == 1: + raise RuntimeError("refresh failed") + return "fresh-token" + + client = HttpClient( + base_url="https://api.test", + access_token="expired-token", + on_refresh_token=refresh_handler, + ) + + with patch.object(client._client, "request", side_effect=mock_request): + # First call: refresh fails → throws 401 + with pytest.raises(ApiError): + await client.request("/api/v1/things") + + # Second call: refresh succeeds → retries → 200 + result = await client.request("/api/v1/things") + + assert result == {"id": "ok"} + assert attempt == 2 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..0b260d6 --- /dev/null +++ b/uv.lock @@ -0,0 +1,407 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "archastro-platform-sdk" +version = "0.77.0" +source = { editable = "." } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, + { name = "websockets" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx", specifier = ">=0.27" }, + { name = "pydantic", specifier = ">=2.0" }, + { name = "websockets", specifier = ">=13.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.0" }, + { name = "pytest-asyncio", specifier = ">=0.24" }, + { name = "ruff", specifier = ">=0.11" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "packaging" +version = "26.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/e5/06d23afac9973109d1e3c8ad38e1547a12e860610e327c05ee686827dc37/pydantic-2.13.2.tar.gz", hash = "sha256:b418196607e61081c3226dcd4f0672f2a194828abb9109e9cfb84026564df2d1", size = 843836, upload-time = "2026-04-17T09:31:59.636Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/ca/b45c378e6e8d0b90577288b533e04e95b7afd61bb1d51b6c263176435489/pydantic-2.13.2-py3-none-any.whl", hash = "sha256:a525087f4c03d7e7456a3de89b64cd693d2229933bb1068b9af6befd5563694e", size = 471947, upload-time = "2026-04-17T09:31:57.541Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/bb/4742f05b739b2478459bb16fa8470549518c802e06ddcf3f106c5081315e/pydantic_core-2.46.2.tar.gz", hash = "sha256:37bb079f9ee3f1a519392b73fda2a96379b31f2013c6b467fe693e7f2987f596", size = 471269, upload-time = "2026-04-17T09:10:07.017Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/91/089f517a725f29084364169437833ab0ae4da4d7a6ed9d4474db7f1412e6/pydantic_core-2.46.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8060f42db3cd204871db0afd51fef54a13fa544c4dd48cdcae2e174ef40c8ba", size = 2106218, upload-time = "2026-04-17T09:10:48.023Z" }, + { url = "https://files.pythonhosted.org/packages/a0/92/23858ed1b58f2a134e50c2fdd0e34ea72721ccb257e1e9346514e1ccb5b9/pydantic_core-2.46.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:73a9d2809bd8d4a7cda4d336dc996a565eb4feaaa39932f9d85a65fa18382f28", size = 1948087, upload-time = "2026-04-17T09:11:58.639Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ac/e2240fccb4794e965817593d5a46cf5ea22f2001b73fe360b7578925b7d8/pydantic_core-2.46.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b0a2dee92dfaabcfb93629188c3e9cf74fdfc0f22e7c369cb444a98814a1e50", size = 1972931, upload-time = "2026-04-17T09:13:13.304Z" }, + { url = "https://files.pythonhosted.org/packages/1a/da/3b11dab2aa15c5c8ed20a01eb7aa432a78b8e3a4713659f7e58490a020a5/pydantic_core-2.46.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3098446ba8cf774f61cb8d4008c1dba14a30426a15169cd95ac3392a461193b1", size = 2040454, upload-time = "2026-04-17T09:13:47.895Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/c4cf5e1f1c6c34c53c0902039c95d81dc15cdd1f03634bd1a93f33e70a72/pydantic_core-2.46.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57c584af6c375ea3f826d8131a94cb212b3d9926eaff67117e3711bbff3a83a5", size = 2221320, upload-time = "2026-04-17T09:13:08.568Z" }, + { url = "https://files.pythonhosted.org/packages/c7/46/891035bc9e93538e754c3188424d24b5a69ec3ae5210fa01d483e99b3302/pydantic_core-2.46.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:547381cca999be88b4715a0ed7afa11f07fc7e53cb1883687b190d25a92c56cf", size = 2274559, upload-time = "2026-04-17T09:11:10.257Z" }, + { url = "https://files.pythonhosted.org/packages/ab/d0/7af0b905b3148152c159c9caf203e7ecd9b90b76389f0862e6ab0cf1b2a3/pydantic_core-2.46.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caeed15dcb1233a5a94bc6ff37ef5393cf5b33a45e4bdfb2d6042f3d24e1cb27", size = 2089239, upload-time = "2026-04-17T09:13:06.326Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bc/566afe02ba2de37712eece74ac7bfba322abd7916410bf90504f1b17ddad/pydantic_core-2.46.2-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:c05f53362568c75476b5c96659377a5dfd982cfbe5a5c07de5106d08a04efc4f", size = 2116182, upload-time = "2026-04-17T09:11:33.738Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5b/3fcb3a229bbfa23b0e3c65014057af0f9d51ec7a2d9f7adb282f41ff5ac8/pydantic_core-2.46.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2643ac7eae296200dbd48762a1c852cf2cad5f5e3eba34e652053cebf03becf8", size = 2172346, upload-time = "2026-04-17T09:10:46.472Z" }, + { url = "https://files.pythonhosted.org/packages/43/9a/baa9e3aa70ea7bbcb9db0f87162a371649ac80c03e43eb54af193390cf17/pydantic_core-2.46.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dc4620a47c6fe6a39f89392c00833a82fc050ce90169798f78a25a8d4df03b6e", size = 2179540, upload-time = "2026-04-17T09:11:21.881Z" }, + { url = "https://files.pythonhosted.org/packages/bd/46/912047a5427f949c909495704b3c8b9ead9d1c66f87e96606011beab1fcb/pydantic_core-2.46.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:78cb0d2453b50bf2035f85fd0d9cfabdb98c47f9c53ddb7c23873cd83da9560b", size = 2327423, upload-time = "2026-04-17T09:13:40.291Z" }, + { url = "https://files.pythonhosted.org/packages/e9/bf/c5e661451dc9411c2ab88a244c1ba57644950c971486040dc200f77b69f4/pydantic_core-2.46.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f0c1cbb7d6112932cc188c6be007a5e2867005a069e47f42fe67bf5f122b0908", size = 2348652, upload-time = "2026-04-17T09:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/77/b3/3219e7c522af54b010cf7422dcb11cc6616a4414d1ccd628b0d3f61c6af6/pydantic_core-2.46.2-cp311-cp311-win32.whl", hash = "sha256:c1ce5b2366f85cfdbf7f0907755043707f86d09a5b1b1acebbb7bf1600d75c64", size = 1974410, upload-time = "2026-04-17T09:13:27.392Z" }, + { url = "https://files.pythonhosted.org/packages/e5/29/e5cfac8a74c59873dfd47d3a1477c39ad9247639a7120d3e251a9ff12417/pydantic_core-2.46.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1a6197eadff5bd0bb932f12bb038d403cb75db5b0b391e70e816a647745ddaf", size = 2071158, upload-time = "2026-04-17T09:09:57.69Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/b7b19b717cdb3675cb109de143f62d4dc62f5d4a0b9879b6f1ace62c6654/pydantic_core-2.46.2-cp311-cp311-win_arm64.whl", hash = "sha256:15e42885b283f87846ee79e161002c5c496ef747a73f6e47054f45a13d9035bc", size = 2043507, upload-time = "2026-04-17T09:09:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/97/ec/2fafa4c86f5d2a69372c7cddef30925fd0e370b1efaf556609c1a0196d8a/pydantic_core-2.46.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ea1ad8c89da31512fe2d249cf0638fb666925bda341901541bc5f3311c6fcc9e", size = 2101729, upload-time = "2026-04-17T09:12:30.042Z" }, + { url = "https://files.pythonhosted.org/packages/cf/55/be5386c2c4b49af346e8a26b748194ff25757bbb6cf544130854e997af7a/pydantic_core-2.46.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b308da17b92481e0587244631c5529e5d91d04cb2b08194825627b1eca28e21e", size = 1951546, upload-time = "2026-04-17T09:10:10.585Z" }, + { url = "https://files.pythonhosted.org/packages/29/92/89e273a055ce440e6636c756379af35ad86da9d336a560049c3ba5e41c80/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d333a50bdd814a917d8d6a7ee35ba2395d53ddaa882613bc24e54a9d8b129095", size = 1976178, upload-time = "2026-04-17T09:11:49.619Z" }, + { url = "https://files.pythonhosted.org/packages/91/b3/e4664469cf70c0cb0f7b2f5719d64e5968bb6f38217042c2afa3d3c4ba17/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d00b99590c5bd1fabbc5d28b170923e32c1b1071b1f1de1851a4d14d89eb192", size = 2051697, upload-time = "2026-04-17T09:12:04.917Z" }, + { url = "https://files.pythonhosted.org/packages/98/58/dbf68213ee06ce51cdd6d8c95f97980e646858c45bd96bd2dfb40433be73/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f0e686960ffe9e65066395af856ac2d52c159043144433602c50c221d81c1ba", size = 2233160, upload-time = "2026-04-17T09:12:00.956Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d3/68092aa0ee6c60ff4de4740eb82db3d4ce338ec89b3cecb978c532472f12/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d1128da41c9cb474e0a4701f9c363ec645c9d1a02229904c76bf4e0a194fde2", size = 2298398, upload-time = "2026-04-17T09:10:29.694Z" }, + { url = "https://files.pythonhosted.org/packages/e4/51/5d6155eb737db55b0ad354ca5f333ef009f75feb67df2d79a84bace45af6/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48649cf2d8c358d79586e9fb2f8235902fcaa2d969ec1c5301f2d1873b2f8321", size = 2094058, upload-time = "2026-04-17T09:12:10.995Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f3/eb4a986197d71319430464ff181226c95adc8f06d932189b158bae5a82f5/pydantic_core-2.46.2-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:b902f0fc7c2cf503865a05718b68147c6cd5d0a3867af38c527be574a9fa6e9d", size = 2130388, upload-time = "2026-04-17T09:12:41.159Z" }, + { url = "https://files.pythonhosted.org/packages/56/00/44a9c4fe6d0f64b5786d6a8c649d6f0e34ba6c89b3663add1066e54451a2/pydantic_core-2.46.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e80011f808b03d1d87a8f1e76ae3da19a18eb706c823e17981dcf1fae43744fc", size = 2184245, upload-time = "2026-04-17T09:12:36.532Z" }, + { url = "https://files.pythonhosted.org/packages/78/6b/685b98a834d5e3d1c34a1bde1627525559dd223b75075bc7490cdb24eb33/pydantic_core-2.46.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b839d5c802e31348b949b6473f8190cddbf7d47475856d8ac995a373ee16ec59", size = 2186842, upload-time = "2026-04-17T09:13:04.054Z" }, + { url = "https://files.pythonhosted.org/packages/22/64/caa2f5a2ac8b6113adaa410ccdf31ba7f54897a6e54cd0d726fc7e780c88/pydantic_core-2.46.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:c6b1064f3f9cf9072e1d59dd2936f9f3b668bec1c37039708c9222db703c0d5b", size = 2336066, upload-time = "2026-04-17T09:12:13.006Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f9/7d2701bf82945b5b9e7df8347be97ef6a36da2846bfe5b4afec299ffe27b/pydantic_core-2.46.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a68e6f2ac95578ce3c0564802404b27b24988649616e556c07e77111ed3f1d", size = 2363691, upload-time = "2026-04-17T09:13:42.972Z" }, + { url = "https://files.pythonhosted.org/packages/3b/65/0dab11574101522941055109419db3cc09db871643dc3fc74e2413215e5b/pydantic_core-2.46.2-cp312-cp312-win32.whl", hash = "sha256:d9ffa75a7ef4b97d6e5e205fabd4304ef01fec09e6f1bdde04b9ad1b07d20289", size = 1958801, upload-time = "2026-04-17T09:11:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/13/2b/df84baa609c676f6450b8ecad44ea59146c805e3371b7b52443c0899f989/pydantic_core-2.46.2-cp312-cp312-win_amd64.whl", hash = "sha256:0551f2d2ddb68af5a00e26497f8025c538f73ef3cb698f8e5a487042cd2792a8", size = 2072634, upload-time = "2026-04-17T09:11:02.407Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4e/e1ce8029fc438086a946739bf9d596f70ff470aad4a8345555920618cabe/pydantic_core-2.46.2-cp312-cp312-win_arm64.whl", hash = "sha256:83aef30f106edcc21a6a4cc44b82d3169a1dbe255508db788e778f3c804d3583", size = 2026188, upload-time = "2026-04-17T09:13:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/07/2b/662e48254479a2d3450ba24b1e25061108b64339794232f503990c519144/pydantic_core-2.46.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d26e9eea3715008a09a74585fe9becd0c67fbb145dc4df9756d597d7230a652c", size = 2101762, upload-time = "2026-04-17T09:10:13.87Z" }, + { url = "https://files.pythonhosted.org/packages/73/ab/bafd7c7503757ccc8ec4d1911e106fe474c629443648c51a88f08b0fe91a/pydantic_core-2.46.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48b36e3235140510dc7861f0cd58b714b1cdd3d48f75e10ce52e69866b746f10", size = 1951814, upload-time = "2026-04-17T09:12:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/92/cc/7549c2d57ba2e9a42caa5861a2d398dbe31c02c6aca783253ace59ce84f8/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36b1f99dc451f1a3981f236151465bcf995bbe712d0727c9f7b236fe228a8133", size = 1977329, upload-time = "2026-04-17T09:13:37.605Z" }, + { url = "https://files.pythonhosted.org/packages/18/50/7ed4a8a0d478a4dca8f0134a5efa7193f03cc8520dd4c9509339fb2e5002/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8641c8d535c2d95b45c2e19b646ecd23ebba35d461e0ae48a3498277006250ab", size = 2051832, upload-time = "2026-04-17T09:12:49.771Z" }, + { url = "https://files.pythonhosted.org/packages/dc/16/bb35b193741c0298ddc5f5e4234269efdc0c65e2bcd198aa0de9b68845e4/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20fb194788a0a50993e87013e693494ba183a2af5b44e99cf060bbae10912b11", size = 2233127, upload-time = "2026-04-17T09:11:04.449Z" }, + { url = "https://files.pythonhosted.org/packages/91/a5/98f4b637149185addea19e1785ea20c373cca31b202f589111d8209d9873/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9262d11d0cd11ee3303a95156939402bed6cedfe5ed0e331b95a283a4da6eb8b", size = 2297418, upload-time = "2026-04-17T09:11:25.929Z" }, + { url = "https://files.pythonhosted.org/packages/36/90/93a5d21990b152da7b7507b7fddb0b935f6a0984d57ac3ec45a6e17777a2/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac204542736aa295fa25f713b7fad6fc50b46ab7764d16087575c85f085174f3", size = 2093735, upload-time = "2026-04-17T09:12:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/14/22/b8b1ffdddf08b4e84380bcb67f41dbbf4c171377c1d36fc6290794bb2094/pydantic_core-2.46.2-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9a7c43a0584742dface3ca0daf6f719d46c1ac2f87cf080050f9ae052c75e1b2", size = 2127570, upload-time = "2026-04-17T09:11:53.906Z" }, + { url = "https://files.pythonhosted.org/packages/c6/26/e60d72b4e2d0ce1fa811044a974412ac1c567fe067d97b3e6b290530786e/pydantic_core-2.46.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fd05e1edb6a90ad446fa268ab09e59202766b837597b714b2492db11ee87fab9", size = 2183524, upload-time = "2026-04-17T09:11:30.092Z" }, + { url = "https://files.pythonhosted.org/packages/35/32/36bec7584a1eefb17dec4dfa1c946d3fe4440f466c5705b8adfda69c9a9f/pydantic_core-2.46.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:91155b110788b5501abc7ea954f1d08606219e4e28e3c73a94124307c06efb80", size = 2185408, upload-time = "2026-04-17T09:10:57.228Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d6/1a5689d873620efd67d6b163db0c444c056adb0849b5bc33e2b9f09665a6/pydantic_core-2.46.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e4e2c72a529fa03ff228be1d2b76944013f428220b764e03cc50ada67e17a42c", size = 2335171, upload-time = "2026-04-17T09:11:43.369Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/675104802abe8ef502b072050ee5f2e915251aa1a3af87e1015ce31ec42d/pydantic_core-2.46.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:56291ec1a11c3499890c99a8fd9053b47e60fe837a77ec72c0671b1b8b3dce24", size = 2362743, upload-time = "2026-04-17T09:10:18.333Z" }, + { url = "https://files.pythonhosted.org/packages/8d/bc/86c5dde4fa6e24467680eef5047da3c1a19be0a527d0d8e14aa76b39307c/pydantic_core-2.46.2-cp313-cp313-win32.whl", hash = "sha256:b50f9c5f826ddca1246f055148df939f5f3f2d0d96db73de28e2233f22210d4c", size = 1958074, upload-time = "2026-04-17T09:12:38.622Z" }, + { url = "https://files.pythonhosted.org/packages/2a/97/2537e8c1282b2c4eb062580c0d7a4339e10b072b803d1ee0b7f1f0a5c22c/pydantic_core-2.46.2-cp313-cp313-win_amd64.whl", hash = "sha256:251a57788823230ca8cbc99e6245d1a2ed6e180ec4864f251c94182c580c7f2e", size = 2071741, upload-time = "2026-04-17T09:13:32.405Z" }, + { url = "https://files.pythonhosted.org/packages/da/aa/2ee75798706f9dbc4e76dbe59e41a396c5c311e3d6223b9cf6a5fa7780be/pydantic_core-2.46.2-cp313-cp313-win_arm64.whl", hash = "sha256:315d32d1a71494d6b4e1e14a9fa7a4329597b4c4340088ad7e1a9dafbeed92a9", size = 2025955, upload-time = "2026-04-17T09:10:15.567Z" }, + { url = "https://files.pythonhosted.org/packages/d0/96/a50ccb6b539ae780f73cea74905468777680e30c6c3bdf714b9d4c116ea0/pydantic_core-2.46.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:4f59b45f3ef8650c0c736a57f59031d47ed9df4c0a64e83796849d7d14863a2d", size = 2097111, upload-time = "2026-04-17T09:10:49.617Z" }, + { url = "https://files.pythonhosted.org/packages/34/5f/fdead7b3afa822ab6e5a18ee0ecffd54937de1877c01ed13a342e0fb3f07/pydantic_core-2.46.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a075a29ebef752784a91532a1a85be6b234ccffec0a9d7978a92696387c3da6", size = 1951904, upload-time = "2026-04-17T09:12:32.062Z" }, + { url = "https://files.pythonhosted.org/packages/95/e0/1c5d547e550cdab1bec737492aa08865337af6fe7fc9b96f7f45f17d9519/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d12d786e30c04a9d307c5d7080bf720d9bac7f1668191d8e37633a9562749e2", size = 1978667, upload-time = "2026-04-17T09:11:35.589Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cb/665ce629e218c8228302cb94beff4f6531082a2c87d3ecc3d5e63a26f392/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d5e6d6343b0b5dcacb3503b5de90022968da8ed0ab9ab39d3eda71c20cbf84e", size = 2046721, upload-time = "2026-04-17T09:11:47.725Z" }, + { url = "https://files.pythonhosted.org/packages/77/e9/6cb2cf60f54c1472bbdfce19d957553b43dbba79d1d7b2930a195c594785/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:233eebac0999b6b9ba76eb56f3ec8fce13164aa16b6d2225a36a79e0f95b5973", size = 2228483, upload-time = "2026-04-17T09:12:08.837Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2a/93e018dd5571f781ebaeda8c0cf65398489d5bee9b1f484df0b6149b43b9/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cc0eee720dd2f14f3b7c349469402b99ad81a174ab49d3533974529e9d93992", size = 2294663, upload-time = "2026-04-17T09:12:52.053Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4f/49e57ca55c770c93d9bb046666a54949b42e3c9099a0c5fe94557873fe30/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83ee76bf2c9910513dbc19e7d82367131fa7508dedd6186a462393071cc11059", size = 2098742, upload-time = "2026-04-17T09:13:45.472Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b0/6e46b5cd3332af665f794b8cdeea206618a8630bd9e7bcc36864518fce81/pydantic_core-2.46.2-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:d61db38eb4ee5192f0c261b7f2d38e420b554df8912245e3546aee5c45e2fd78", size = 2125922, upload-time = "2026-04-17T09:12:54.304Z" }, + { url = "https://files.pythonhosted.org/packages/06/d1/40850c81585be443a2abfdf7f795f8fae831baf8e2f9b2133c8246ac671c/pydantic_core-2.46.2-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f09a713d17bcd55da8ab02ebd9110c5246a49c44182af213b5212800af8bc83", size = 2183000, upload-time = "2026-04-17T09:10:59.027Z" }, + { url = "https://files.pythonhosted.org/packages/04/af/8493d7dfa03ebb7866909e577c6aa65ea0de7377b86023cc51d0c8e11db3/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:30cacc5fb696e64b8ef6fd31d9549d394dd7d52760db072eecb98e37e3af1677", size = 2180335, upload-time = "2026-04-17T09:12:57.01Z" }, + { url = "https://files.pythonhosted.org/packages/72/5b/1f6a344c4ffdf284da41c6067b82d5ebcbd11ce1b515ae4b662d4adb6f61/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:7ccfb105fcfe91a22bbb5563ad3dc124bc1aa75bfd2e53a780ab05f78cdf6108", size = 2330002, upload-time = "2026-04-17T09:12:02.958Z" }, + { url = "https://files.pythonhosted.org/packages/25/ff/9a694126c12d6d2f48a0cafa6f8eef88ef0d8825600e18d03ff2e896c3b2/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:13ffef637dc8370c249e5b26bd18e9a80a4fca3d809618c44e18ec834a7ca7a8", size = 2359920, upload-time = "2026-04-17T09:10:27.764Z" }, + { url = "https://files.pythonhosted.org/packages/51/c8/3a35c763d68a9cb2675eb10ef242cf66c5d4701b28ae12e688d67d2c180e/pydantic_core-2.46.2-cp314-cp314-win32.whl", hash = "sha256:1b0ab6d756ca2704a938e6c31b53f290c2f9c10d3914235410302a149de1a83e", size = 1953701, upload-time = "2026-04-17T09:13:30.021Z" }, + { url = "https://files.pythonhosted.org/packages/1a/6a/f2726a780365f7dfd89d62036f984f7acb99978c60c5e1fa7c0cb898ed11/pydantic_core-2.46.2-cp314-cp314-win_amd64.whl", hash = "sha256:99ebade8c9ada4df975372d8dd25883daa0e379a05f1cd0c99aa0c04368d01a6", size = 2071867, upload-time = "2026-04-17T09:10:39.205Z" }, + { url = "https://files.pythonhosted.org/packages/e1/79/76baacb9feba3d7c399b245ca1a29c74ea0db04ea693811374827eec2290/pydantic_core-2.46.2-cp314-cp314-win_arm64.whl", hash = "sha256:de87422197cf7f83db91d89c86a21660d749b3cd76cd8a45d115b8e675670f02", size = 2017252, upload-time = "2026-04-17T09:10:26.175Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3b/77c26938f817668d9ad9bab1a905cb23f11d9a3d4bf724d429b3e55a8eaf/pydantic_core-2.46.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:236f22b4a206b5b61db955396b7cf9e2e1ff77f372efe9570128ccfcd6a525eb", size = 2094545, upload-time = "2026-04-17T09:12:19.339Z" }, + { url = "https://files.pythonhosted.org/packages/fe/de/42c13f590e3c260966aa49bcdb1674774f975467c49abd51191e502bea28/pydantic_core-2.46.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c2012f64d2cd7cca50f49f22445aa5a88691ac2b4498ee0a9a977f8ca4f7289f", size = 1933953, upload-time = "2026-04-17T09:09:55.889Z" }, + { url = "https://files.pythonhosted.org/packages/4e/84/ebe3ebb3e2d8db656937cfa6f97f544cb7132f2307a4a7dfdcd0ea102a12/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d07d6c63106d3a9c9a333e2636f9c82c703b1a9e3b079299e58747964e4fdb72", size = 1974435, upload-time = "2026-04-17T09:10:12.371Z" }, + { url = "https://files.pythonhosted.org/packages/b9/15/0bf51ca6709477cd4ef86148b6d7844f3308f029eac361dd0383f1e17b1a/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c326a2b4b85e959d9a1fc3a11f32f84611b6ec07c053e1828a860edf8d068208", size = 2031113, upload-time = "2026-04-17T09:10:00.752Z" }, + { url = "https://files.pythonhosted.org/packages/02/ae/b7b5af9b79db036d9e61a44c481c17a213dc8fc4b8b71fe6875a72fc778b/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac8a65e798f2462552c00d2e013d532c94d646729dda98458beaf51f9ec7b120", size = 2236325, upload-time = "2026-04-17T09:10:33.227Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ae/ecef7477b5a03d4a499708f7e75d2836452ebb70b776c2d64612b334f57a/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a3c2bc1cc8164bedbc160b7bb1e8cc1e8b9c27f69ae4f9ae2b976cdae02b2dd", size = 2278135, upload-time = "2026-04-17T09:10:23.287Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/2f9d82faa47af6c39fc3f120145fd915971e1e0cb6b55b494fad9fdf8275/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69aa5e10b7e8b1bb4a6888650fd12fcbf11d396ca11d4a44de1450875702830", size = 2109071, upload-time = "2026-04-17T09:11:06.149Z" }, + { url = "https://files.pythonhosted.org/packages/f1/9c/677cf10873fbd0b116575ab7b97c90482b21564f8a8040beb18edef7a577/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4e6df5c3301e65fb42bc5338bf9a1027a02b0a31dc7f54c33775229af474daf0", size = 2106028, upload-time = "2026-04-17T09:10:51.525Z" }, + { url = "https://files.pythonhosted.org/packages/d6/53/6a06183544daba51c059123a2064a99039df25f115a06bdb26f2ea177038/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c2f6e32548ac8d559b47944effcf8ae4d81c161f6b6c885edc53bc08b8f192d", size = 2164816, upload-time = "2026-04-17T09:11:56.187Z" }, + { url = "https://files.pythonhosted.org/packages/57/6f/10fcdd9e3eca66fc828eef0f6f5850f2dd3bca2c59e6e041fb8bc3da39be/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:b089a81c58e6ea0485562bbbbbca4f65c0549521606d5ef27fba217aac9b665a", size = 2166130, upload-time = "2026-04-17T09:10:03.804Z" }, + { url = "https://files.pythonhosted.org/packages/29/83/92d3fd0e0156cad2e3cb5c26de73794af78ac9fa0c22ab666e566dd67061/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:7f700a6d6f64112ae9193709b84303bbab84424ad4b47d0253301aabce9dfc70", size = 2316605, upload-time = "2026-04-17T09:12:45.249Z" }, + { url = "https://files.pythonhosted.org/packages/97/f1/facffdb970981068219582e499b8d0871ed163ffcc6b347de5c412669e4c/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:67db6814beaa5fefe91101ec7eb9efda613795767be96f7cf58b1ca8c9ca9972", size = 2358385, upload-time = "2026-04-17T09:09:54.657Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a1/b8160b2f22b2199467bc68581a4ed380643c16b348a27d6165c6c242d694/pydantic_core-2.46.2-cp314-cp314t-win32.whl", hash = "sha256:32fbc7447be8e3be99bf7869f7066308f16be55b61f9882c2cefc7931f5c7664", size = 1942373, upload-time = "2026-04-17T09:12:59.594Z" }, + { url = "https://files.pythonhosted.org/packages/0d/90/db89acabe5b150e11d1b59fe3d947dda2ef6abbfef5c82f056ff63802f5d/pydantic_core-2.46.2-cp314-cp314t-win_amd64.whl", hash = "sha256:b317a2b97019c0b95ce99f4f901ae383f40132da6706cdf1731066a73394c25c", size = 2052078, upload-time = "2026-04-17T09:10:19.96Z" }, + { url = "https://files.pythonhosted.org/packages/97/32/e19b83ceb07a3f1bb21798407790bbc9a31740158fd132b94139cb84e16c/pydantic_core-2.46.2-cp314-cp314t-win_arm64.whl", hash = "sha256:7dcb9d40930dfad7ab6b20bcc6ca9d2b030b0f347a0cd9909b54bd53ead521b1", size = 2016941, upload-time = "2026-04-17T09:12:34.447Z" }, + { url = "https://files.pythonhosted.org/packages/25/ec/e91aa08df1c33d5e3c2b60c07a1eca9f21809728a824c7b467bb3bda68b5/pydantic_core-2.46.2-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:7c5a5b3dbb9e8918e223be6580da5ffcf861c0505bbc196ebed7176ce05b7b4e", size = 2105046, upload-time = "2026-04-17T09:10:55.614Z" }, + { url = "https://files.pythonhosted.org/packages/f0/73/27112400a0452e375290e7c40aef5cc9844ac0920fb1029238cfc68121fa/pydantic_core-2.46.2-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:bc1e8ce33d5a337f2ba862e0719b8201cd54aaed967406c748e009191d47efdd", size = 1940029, upload-time = "2026-04-17T09:12:21.5Z" }, + { url = "https://files.pythonhosted.org/packages/b1/44/3d39f782bc82ddd0b2d82bde83b408aa40a332cdf6f3018acb34e3d4dcfc/pydantic_core-2.46.2-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b737c0b280f41143266445de2689c0e49c79307e51c44ce3a77fef2bedad4994", size = 1987772, upload-time = "2026-04-17T09:10:02.357Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1a/0242e5b7b6cf51dbccc065029f0420107b6bf7e191fcb918f5cb71218acf/pydantic_core-2.46.2-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b877d597afb82b4898e35354bba55de6f7f048421ae0edadbb9886ec137b532", size = 2138468, upload-time = "2026-04-17T09:11:51.546Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/66c146f421178641bda880b0267c0d57dd84f5fec9ecc8e46be17b480742/pydantic_core-2.46.2-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e9fcabd1857492b5bf16f90258babde50f618f55d046b1309972da2396321ff9", size = 2091621, upload-time = "2026-04-17T09:12:47.501Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b2/c28419aa9fc8055f4ac8e801d1d11c6357351bfa4321ed9bafab3eb98087/pydantic_core-2.46.2-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:fb3ec2c7f54c07b30d89983ce78dc32c37dd06a972448b8716d609493802d628", size = 1937059, upload-time = "2026-04-17T09:10:53.554Z" }, + { url = "https://files.pythonhosted.org/packages/30/ce/cd0824a2db213dc17113291b7a09b9b0ccd9fbf97daa4b81548703341baf/pydantic_core-2.46.2-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130a6c837d819ef33e8c2bf702ed2c3429237ea69807f1140943d6f4bdaf52fa", size = 1997278, upload-time = "2026-04-17T09:12:23.784Z" }, + { url = "https://files.pythonhosted.org/packages/c9/69/47283fe3c0c967d3e9e9cd6c42b70907610c8a6f8d6e8381f1bb55f8006c/pydantic_core-2.46.2-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2e25417cec5cd9bddb151e33cb08c50160f317479ecc02b22a95ec18f8fe004", size = 2147096, upload-time = "2026-04-17T09:12:43.124Z" }, + { url = "https://files.pythonhosted.org/packages/16/d5/dec7c127fa722ff56e1ccf1e960ae1318a9f66742135e97bf9771447216f/pydantic_core-2.46.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3ad79ed32004d9de91cacd4b5faaff44d56051392fe1d5526feda596f01af25", size = 2107613, upload-time = "2026-04-17T09:10:36.269Z" }, + { url = "https://files.pythonhosted.org/packages/bc/35/975c109b337260a71c93198baf663982b6b39fe3e584e279548a0969e5d4/pydantic_core-2.46.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d157c48d28eebe5d46906de06a6a2f2c9e00b67d3e42de1f1b9c2d42b810f77c", size = 1947099, upload-time = "2026-04-17T09:12:15.304Z" }, + { url = "https://files.pythonhosted.org/packages/4e/11/52a971a0f9218631690274be533f05e5ddde5547f0823bb3e9dfd1be49f6/pydantic_core-2.46.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b42c6471288dedc979ac8400d9c9770f03967dd187db1f8d3405d4d182cc714", size = 2133866, upload-time = "2026-04-17T09:12:27.994Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7a/33d94d0698602b2d1712e78c703a33952eb2ca69e02e8e4b208e7f6602b5/pydantic_core-2.46.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4f27bc4801358dc070d6697b41237fce9923d8e69a1ce1e95606ac36c1552dc1", size = 2161721, upload-time = "2026-04-17T09:11:16.111Z" }, + { url = "https://files.pythonhosted.org/packages/b0/cb/0df7ee0a148e9ce0968a80787967ddca9f6b3f8a49152a881b88da262701/pydantic_core-2.46.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e094a8f85db41aa7f6a45c5dac2950afc9862e66832934231962252b5d284eed", size = 2180175, upload-time = "2026-04-17T09:11:41.577Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a8/258a32878140347532be4e44c6f3b1ace3b52b9c9ca7548a65ce18adf4b4/pydantic_core-2.46.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:807eeda5551f6884d3b4421578be37be50ddb7a58832348e99617a6714a73748", size = 2319882, upload-time = "2026-04-17T09:10:21.872Z" }, + { url = "https://files.pythonhosted.org/packages/13/b9/5071c298a0f91314a5402b8c56e0efbcebe77085327d0b4df7dc9cb0b674/pydantic_core-2.46.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fcaa1c3c846a7f6686b38fe493d1b2e8007380e293bfef6a9354563c026cbf36", size = 2348065, upload-time = "2026-04-17T09:11:08.263Z" }, + { url = "https://files.pythonhosted.org/packages/75/f3/0a7087e5f861d66ca64ce927230b397cc264c87b712156e6a93b26a459c8/pydantic_core-2.46.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:154dbfdfb11b8cbd8ff4d00d0b81e3d19f4cb4bedd5aa9f091060ba071474c6a", size = 2192159, upload-time = "2026-04-17T09:11:20.123Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264, upload-time = "2026-04-16T18:46:26.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943, upload-time = "2026-04-16T18:46:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592, upload-time = "2026-04-16T18:46:00.742Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501, upload-time = "2026-04-16T18:46:03.723Z" }, + { url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693, upload-time = "2026-04-16T18:46:41.941Z" }, + { url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177, upload-time = "2026-04-16T18:46:21.717Z" }, + { url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886, upload-time = "2026-04-16T18:46:15.086Z" }, + { url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183, upload-time = "2026-04-16T18:46:07.944Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575, upload-time = "2026-04-16T18:46:31.687Z" }, + { url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537, upload-time = "2026-04-16T18:46:36.988Z" }, + { url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813, upload-time = "2026-04-16T18:46:24.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136, upload-time = "2026-04-16T18:46:39.802Z" }, + { url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701, upload-time = "2026-04-16T18:46:10.381Z" }, + { url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887, upload-time = "2026-04-16T18:46:29.157Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316, upload-time = "2026-04-16T18:46:19.462Z" }, + { url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535, upload-time = "2026-04-16T18:46:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692, upload-time = "2026-04-16T18:46:17.268Z" }, + { url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614, upload-time = "2026-04-16T18:46:34.487Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, + { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, + { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, + { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, + { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +]