diff --git a/.github/scripts/stlc-fork-hint.sh b/.github/scripts/stlc-fork-hint.sh
new file mode 100755
index 000000000..ce6e7c778
--- /dev/null
+++ b/.github/scripts/stlc-fork-hint.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+# Explains a staging/production trunk fork and prints the heal command.
+# Expects origin/main = staging main and production/main = production main,
+# plus GITHUB_REPOSITORY (staging) and PRODUCTION_REPO (production).
+set -uo pipefail
+
+STAGING_REPO="$GITHUB_REPOSITORY"
+MERGE_MSG="chore: merge staging main into production to heal trunk fork"
+# A squash PR cannot heal the fork: only a real merge commit makes staging an
+# ancestor of production again, so this pushes past production's PR rule.
+HEAL="git clone -q https://github.com/${STAGING_REPO}.git heal-fork && cd heal-fork && git remote add production https://github.com/${PRODUCTION_REPO}.git && git fetch -q production main && git checkout -B heal-fork production/main && git merge --no-ff origin/main -m \"${MERGE_MSG}\" && git push production heal-fork:main"
+RESYNC="gh workflow run stlc-sync.yml -R ${STAGING_REPO}"
+
+if git merge-tree --write-tree origin/main production/main >/dev/null 2>&1; then
+ CLEAN=true
+else
+ CLEAN=false
+fi
+
+{
+ echo "### Staging and production main have forked"
+ echo
+ echo "Each trunk has commits the other lacks, so neither back-sync nor promote can fast-forward. Every run of both fails until the fork is healed."
+ echo
+ echo "**Only on staging** (\`${STAGING_REPO}\`):"
+ echo '```'
+ git log --format='%h %an %s' production/main..origin/main
+ echo '```'
+ echo "**Only on production** (\`${PRODUCTION_REPO}\`):"
+ echo '```'
+ git log --format='%h %an %s' origin/main..production/main
+ echo '```'
+ if [ "$CLEAN" = true ]; then
+ echo "They merge cleanly. Heal by pushing a merge commit to production main, with an identity that can bypass its pull-request rule (a squash-merged PR does not restore ancestry):"
+ echo '```'
+ echo "$HEAL"
+ echo '```'
+ echo "Then back-sync fast-forwards staging onto it:"
+ echo '```'
+ echo "$RESYNC"
+ echo '```'
+ else
+ echo "They **conflict**, so the heal needs a hand-resolved merge: run the command below without the final \`git push\`, resolve and commit, then push \`heal-fork\` to production main and run \`${RESYNC}\`."
+ echo '```'
+ echo "$HEAL"
+ echo '```'
+ fi
+} >> "${GITHUB_STEP_SUMMARY:-/dev/stdout}"
+
+if [ "$CLEAN" = true ]; then
+ echo "::error title=Trunks forked (clean merge)::staging and production main have diverged. Heal: push a merge commit to production main, then re-run back-sync. See the job summary for the commits involved.%0A%0A${HEAL}%0A%0A${RESYNC}"
+else
+ echo "::error title=Trunks forked (conflicting)::staging and production main have diverged and do not merge cleanly. Hand-resolve a merge of staging main into production main, push it, then run: ${RESYNC}. See the job summary."
+fi
diff --git a/.github/scripts/stlc-heal-fork.sh b/.github/scripts/stlc-heal-fork.sh
new file mode 100755
index 000000000..a42aeaa3f
--- /dev/null
+++ b/.github/scripts/stlc-heal-fork.sh
@@ -0,0 +1,115 @@
+#!/usr/bin/env bash
+# Heals a staging/production trunk fork with a merge commit, only when the
+# trunks merge cleanly and staging CI passes on that merge; otherwise explains
+# the fork and fails.
+# Expects origin/main = staging main and production/main = production main,
+# plus GITHUB_REPOSITORY (staging), PRODUCTION_REPO, PRODUCTION_REPO_TOKEN, and
+# GH_TOKEN able to read staging's Actions runs.
+set -euo pipefail
+
+HINT="$(dirname "$0")/stlc-fork-hint.sh"
+HEAL_BRANCH=stlc-fork-heal
+CI_WORKFLOW=.github/workflows/ci.yml
+CI_TIMEOUT=${STLC_HEAL_CI_TIMEOUT:-1800}
+POLL=${STLC_HEAL_POLL:-15}
+
+# One trunk ahead of the other is a fast-forward, not a fork.
+if git merge-base --is-ancestor origin/main production/main ||
+ git merge-base --is-ancestor production/main origin/main; then
+ echo "::error title=Fork heal refused::staging and production main have not forked."
+ exit 1
+fi
+
+if [ -z "${PRODUCTION_REPO_TOKEN:-}" ]; then
+ bash "$HINT"
+ echo "::error title=Fork heal blocked::PRODUCTION_REPO_TOKEN is required to heal the fork."
+ exit 1
+fi
+
+if ! TREE=$(git merge-tree --write-tree origin/main production/main 2>/dev/null); then
+ bash "$HINT"
+ exit 1
+fi
+
+STAGING_ONLY=$(git log --format='%h %an %s' production/main..origin/main)
+PRODUCTION_ONLY=$(git log --format='%h %an %s' origin/main..production/main)
+
+# Not stlc-bot: seal-dispatch must still re-seal the production custom code
+# this merge brings onto staging.
+MERGE=$(
+ GIT_AUTHOR_NAME="stlc-fork-heal" GIT_AUTHOR_EMAIL="41898282+github-actions[bot]@users.noreply.github.com" \
+ GIT_COMMITTER_NAME="stlc-fork-heal" GIT_COMMITTER_EMAIL="41898282+github-actions[bot]@users.noreply.github.com" \
+ git commit-tree "$TREE" -p production/main -p origin/main \
+ -m "chore: merge staging main into production to heal trunk fork"
+)
+
+git remote set-url production "https://x-access-token:${PRODUCTION_REPO_TOKEN}@github.com/${PRODUCTION_REPO}.git"
+git remote set-url origin "https://x-access-token:${PRODUCTION_REPO_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
+push() { git -c "http.https://github.com/.extraheader=" push "$@"; }
+
+# Nothing reaches main until staging CI passes on this exact merge: a clean text
+# merge can still fail to build. CI runs on any staging branch push; the branch
+# only triggers it.
+push --force origin "$MERGE:refs/heads/$HEAL_BRANCH"
+trap 'push -q origin --delete "$HEAL_BRANCH" || true' EXIT
+
+blocked() {
+ {
+ echo "### Staging and production main have forked; heal blocked by CI"
+ echo
+ echo "They merge cleanly as text, but $1, so nothing was pushed to main. Fix the combination on either trunk (or push a fixed merge by hand); the next back-sync poll retries the heal."
+ echo
+ echo "CI run: ${CI_URL:-none found}"
+ echo
+ echo "**Only on staging:**"
+ echo '```'
+ echo "$STAGING_ONLY"
+ echo '```'
+ echo "**Only on production:**"
+ echo '```'
+ echo "$PRODUCTION_ONLY"
+ echo '```'
+ } >> "${GITHUB_STEP_SUMMARY:-/dev/stdout}"
+ echo "::error title=Fork heal blocked::$1 on clean merge commit ${MERGE}; nothing was pushed to main. CI run: ${CI_URL:-none found}"
+ exit 1
+}
+
+deadline=$((SECONDS + CI_TIMEOUT))
+STATUS="" CONCLUSION="" CI_URL=""
+while :; do
+ # A failed lookup reads as "not finished yet" and is retried until the deadline.
+ read -r STATUS CONCLUSION CI_URL < <(
+ gh api "repos/${GITHUB_REPOSITORY}/actions/runs?head_sha=${MERGE}&event=push" \
+ --jq "[.workflow_runs[] | select(.path | startswith(\"${CI_WORKFLOW}\"))][0] | \"\(.status) \(.conclusion) \(.html_url)\""
+ ) || true
+ [ "$CI_URL" = null ] && CI_URL=""
+ [ "$STATUS" = completed ] && break
+ [ "$SECONDS" -ge "$deadline" ] && blocked "staging CI did not finish within ${CI_TIMEOUT}s"
+ sleep "$POLL"
+done
+
+[ "$CONCLUSION" = success ] || blocked "staging CI concluded ${CONCLUSION}"
+
+# Both pushes are fast-forwards (MERGE descends from each tip), so a trunk that
+# moved since the fetch rejects the push and the next scheduled run retries.
+# Production first: if staging then moves, the next run sees a fresh fork and heals it.
+push production "$MERGE:refs/heads/main"
+push origin "$MERGE:refs/heads/main"
+
+SHORT=$(git rev-parse --short "$MERGE")
+{
+ echo "### Healed a staging/production trunk fork"
+ echo
+ echo "The trunks had diverged but merged cleanly, so merge commit \`${SHORT}\` was pushed to both \`${PRODUCTION_REPO}\` main and \`${GITHUB_REPOSITORY}\` main by ${GITHUB_WORKFLOW:-stlc} run ${GITHUB_RUN_ID:-local}."
+ echo
+ echo "**Was only on staging:**"
+ echo '```'
+ echo "$STAGING_ONLY"
+ echo '```'
+ echo "**Was only on production:**"
+ echo '```'
+ echo "$PRODUCTION_ONLY"
+ echo '```'
+ echo "Staging CI passed on this exact commit before it was pushed: ${CI_URL}"
+} >> "${GITHUB_STEP_SUMMARY:-/dev/stdout}"
+echo "::warning title=Trunk fork healed::pushed clean merge commit ${SHORT} to staging and production main. See the job summary for the commits it joined."
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 87c5aabb0..2232e4311 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -28,6 +28,9 @@ jobs:
with:
version: '0.10.2'
+ - name: Check lockfile before installing dependencies
+ run: uv lock --check
+
- name: Install dependencies
run: uv sync --all-extras --group docs
diff --git a/.github/workflows/stlc-promote.yml b/.github/workflows/stlc-promote.yml
index 1e2d977ce..42f641444 100644
--- a/.github/workflows/stlc-promote.yml
+++ b/.github/workflows/stlc-promote.yml
@@ -2,13 +2,25 @@ name: Promote SDKs
# Promote staging to production by fast-forwarding production main up to staging,
# preserving SHAs so the trunks stay identical and linear (nothing to heal on the
-# next stlc build). Manual dispatch by design: a maintainer promotes the accumulated
-# batch to cut a release; release-please then opens its version PR on production.
+# next stlc build). This is a git-only reconciliation, not a release: release-please
+# owns the actual version bump/publish gate on production. Runs on every push to
+# staging main, because any staging-only commit (stlc build or not) forks the trunks
+# against the next production merge; the daily schedule is a backstop and
+# workflow_dispatch an eager promote (back-sync dispatches it on a fork). A fork
+# that merges cleanly and passes staging CI is healed with a merge commit on both
+# trunks (the only non-linear history this writes); anything else fails for a person.
+# Every run is a safe no-op when there's nothing to promote.
on:
+ push:
+ branches: [main]
+ schedule:
+ # 8am PT (15:00 UTC); shifts to 7am during PST since GitHub cron doesn't track DST.
+ - cron: '0 15 * * *'
workflow_dispatch: {}
permissions:
contents: read
+ actions: read
jobs:
promote:
@@ -18,6 +30,9 @@ jobs:
# Optional gate: add required reviewers to this environment to approve each
# promote. With none it only scopes secrets and adds no gate. Remove if unused.
environment: production
+ concurrency:
+ group: stlc-promote
+ cancel-in-progress: true
env:
PRODUCTION_REPO: runloopai/api-client-python
GH_TOKEN: ${{ secrets.PRODUCTION_REPO_TOKEN }}
@@ -40,7 +55,12 @@ jobs:
# After a release, production has release-please commits staging lacks, so compare trees.
MERGED=$(git merge-tree --write-tree production/main origin/main) || MERGED=conflict
PRODUCTION_TREE=$(git rev-parse 'production/main^{tree}')
- if [ "$MERGED" = "$PRODUCTION_TREE" ]; then
+ # A fork needs healing even when production already has staging's content.
+ if ! git merge-base --is-ancestor origin/main production/main &&
+ ! git merge-base --is-ancestor production/main origin/main; then
+ echo "Staging and production main have forked."
+ echo "synced=false" >> "$GITHUB_OUTPUT"
+ elif [ "$MERGED" = "$PRODUCTION_TREE" ]; then
echo "Production already contains staging's content. Nothing to promote."
echo "synced=true" >> "$GITHUB_OUTPUT"
else
@@ -49,12 +69,16 @@ jobs:
- name: Promote staging to production (fast-forward)
if: steps.diff.outputs.synced == 'false'
+ env:
+ PRODUCTION_REPO_TOKEN: ${{ secrets.PRODUCTION_REPO_TOKEN }}
+ GH_TOKEN: ${{ github.token }}
run: |
- # Refuse unless production is an ancestor of staging: otherwise the trunks
- # have forked (production advanced without a back-sync) and FF is unsafe.
+ # Production not an ancestor of staging means the trunks have forked
+ # (production advanced without a back-sync) and FF is unsafe: heal with a
+ # clean merge commit, else fail.
if ! git merge-base --is-ancestor production/main origin/main; then
- echo "::error title=Promote blocked::production/main is not an ancestor of staging main. Back-sync production into staging first."
- exit 1
+ bash .github/scripts/stlc-heal-fork.sh
+ exit 0
fi
git push production origin/main:refs/heads/main
echo "Fast-forwarded production/main to staging/main."
diff --git a/.github/workflows/stlc-sync.yml b/.github/workflows/stlc-sync.yml
index 5140c3b84..dfd1ec16e 100644
--- a/.github/workflows/stlc-sync.yml
+++ b/.github/workflows/stlc-sync.yml
@@ -28,6 +28,7 @@ jobs:
(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'repository_dispatch')
permissions:
contents: write
+ actions: write
concurrency:
group: stlc-back-sync
cancel-in-progress: true
@@ -68,16 +69,36 @@ jobs:
else
echo "behind=true" >> "$GITHUB_OUTPUT"
fi
+ if git merge-base --is-ancestor origin/main production/main ||
+ git merge-base --is-ancestor production/main origin/main; then
+ echo "forked=false" >> "$GITHUB_OUTPUT"
+ else
+ echo "forked=true" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Hand a forked trunk to promote
+ if: steps.diff.outputs.forked == 'true'
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ # Healing writes production main, so it runs only in promote's
+ # production-environment job; a conflicting fork needs a person.
+ if ! git merge-tree --write-tree origin/main production/main >/dev/null 2>&1; then
+ bash .github/scripts/stlc-fork-hint.sh
+ exit 1
+ fi
+ gh workflow run stlc-promote.yml -R "$GITHUB_REPOSITORY" --ref main
+ echo "::warning title=Trunk fork::staging and production main have forked but merge cleanly; dispatched Promote SDKs to heal it."
- name: Sync production to staging (fast-forward)
- if: steps.diff.outputs.behind == 'true'
+ if: steps.diff.outputs.behind == 'true' && steps.diff.outputs.forked != 'true'
env:
PRODUCTION_REPO_TOKEN: ${{ secrets.PRODUCTION_REPO_TOKEN }}
run: |
# Refuse unless staging is an ancestor of production: otherwise the
# trunks have forked and a fast-forward would be unsafe.
if ! git merge-base --is-ancestor origin/main production/main; then
- echo "::error title=Back-sync blocked::staging main is not an ancestor of production/main."
+ bash .github/scripts/stlc-fork-hint.sh
exit 1
fi
if [ -z "${PRODUCTION_REPO_TOKEN:-}" ]; then
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 81d2de2d2..4191c8899 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "1.31.0"
+ ".": "3.0.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 253775320..27c1472b7 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1 +1 @@
-configured_endpoints: 123
+configured_endpoints: 87
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 67c0b4ab0..12b6e58a7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,98 @@
# Changelog
+## [3.0.0](https://github.com/runloopai/api-client-python/compare/v2.0.0...v3.0.0) (2026-10-23)
+
+
+### ⚠ BREAKING CHANGES
+
+* remove the agent API and agent mounts
+* **sdk:** finish retired agent and shell API cleanup ([#17](https://github.com/runloopai/api-client-python/issues/17))
+* **exec:** remove stateful shell execution from the exec stack
+* **sdk:** remove named shell helpers and API references ([#16](https://github.com/runloopai/api-client-python/issues/16))
+
+### Features
+
+* **exec:** remove stateful shell execution from the exec stack ([855d22d](https://github.com/runloopai/api-client-python/commit/855d22d4aabb625b2a70236d96f41b3bc31b925f))
+* remove the agent API and agent mounts ([26abd67](https://github.com/runloopai/api-client-python/commit/26abd674496a671df6e8ece5dbc65c3d5512bce3))
+* **sdk:** remove named shell helpers and API references ([#16](https://github.com/runloopai/api-client-python/issues/16)) ([d0caf3e](https://github.com/runloopai/api-client-python/commit/d0caf3e889885203911a6ca9188ba46d764c290e))
+
+
+### Bug Fixes
+
+* **sdk:** finish retired agent and shell API cleanup ([#17](https://github.com/runloopai/api-client-python/issues/17)) ([1484c74](https://github.com/runloopai/api-client-python/commit/1484c746c9f3da572dda66f6eff17cf27f647557))
+
+## [2.0.0](https://github.com/runloopai/api-client-python/compare/v1.32.0...v2.0.0) (2026-10-08)
+
+
+### ⚠ BREAKING CHANGES
+
+* remove benchmark identities and shared provisioning behavior
+* **api:** remove the benchmark and scenario APIs
+* **sdk:** drop InputContextPreview
+* **sdk:** drop custom edits to generated benchmark and scenario files
+* **sdk:** remove hosted benchmark and scenario wrappers
+
+### Features
+
+* **api:** remove the benchmark and scenario APIs ([fe3aca1](https://github.com/runloopai/api-client-python/commit/fe3aca1d53f2f5ad3dc48da321bbb0d01c7a03f7))
+* implement Azure storage, runtime secrets, and service lifecycle coverage ([6d46b7b](https://github.com/runloopai/api-client-python/commit/6d46b7b58c770af8fe548cb270530cd083d0f5b4))
+* remove benchmark identities and shared provisioning behavior ([9922030](https://github.com/runloopai/api-client-python/commit/9922030a5057e3b01c656eb1ab708d1cff4d41e8))
+* **sdk:** add sync and async execution stdin helpers ([#15](https://github.com/runloopai/api-client-python/issues/15)) ([3388fd6](https://github.com/runloopai/api-client-python/commit/3388fd659e62a4fa61ac9b120b4047555600175c))
+* **sdk:** remove hosted benchmark and scenario wrappers ([f14c4c2](https://github.com/runloopai/api-client-python/commit/f14c4c2aad5ec0e76a21538fac5fab58d08f1964))
+* **secrets:** add ID endpoints and latest-name updates ([9e3d9bd](https://github.com/runloopai/api-client-python/commit/9e3d9bd2fffb506151ddd4281a307037dd4525b8))
+* **secrets:** add sync and async exact ID handles ([#11](https://github.com/runloopai/api-client-python/issues/11)) ([a930959](https://github.com/runloopai/api-client-python/commit/a9309592d5ede888cd8222c60bc10087d9ff41af))
+
+
+### Bug Fixes
+
+* **api:** allow CUSTOM_SIZE with 0.5 CPU cores ([4c0df1f](https://github.com/runloopai/api-client-python/commit/4c0df1f95fa7f4d1d10bb72b3ed274333139ef61))
+* **api:** require a body when sending execution stdin ([0b5c51d](https://github.com/runloopai/api-client-python/commit/0b5c51d9037d51e31e8bacbd24943bee1ad69c72))
+* **axon:** keep deleted axons readable and reject writes ([c95f902](https://github.com/runloopai/api-client-python/commit/c95f902f58844cc879c8e2a65d7aea7d6aa9ffdd))
+* **mux:** return client errors for malformed entity ids ([aff1b73](https://github.com/runloopai/api-client-python/commit/aff1b7331d9acac55eab9bb304cb8fcee129d1a0))
+* **mux:** validate scenario custom scorer references ([a3b50f5](https://github.com/runloopai/api-client-python/commit/a3b50f5b6e42753d497686818a72f484b0987811))
+* **release:** keep the Python lockfile version consistent ([#12](https://github.com/runloopai/api-client-python/issues/12)) ([0369ef5](https://github.com/runloopai/api-client-python/commit/0369ef5d0cd1dceb619f1ad5fdf8b6fa460b76c9))
+* require scoped handles in backend workflows ([e666db1](https://github.com/runloopai/api-client-python/commit/e666db1d07eaac7d39bc66368a4cdd9a16b74e19))
+* **sdk:** drop custom edits to generated benchmark and scenario files ([a01ed98](https://github.com/runloopai/api-client-python/commit/a01ed98a7eedc32834528923a246450be3839745))
+* **sdk:** drop InputContextPreview ([ed06423](https://github.com/runloopai/api-client-python/commit/ed064233c4b228b2a30b846414e22a9ca95ac91f))
+* **storage:** preserve signed upload headers and isolate credentials ([#13](https://github.com/runloopai/api-client-python/issues/13)) ([386bd06](https://github.com/runloopai/api-client-python/commit/386bd06fc4dd5738f486a1fd808ef2fb6f3261eb))
+
+
+### Chores
+
+* **security:** Improve security docs and tighten JWT audience ([53adee2](https://github.com/runloopai/api-client-python/commit/53adee2578b0040853a9d1a13e8c14b48b9738a8))
+
+
+### Documentation
+
+* **sdk:** clarify that axon lists include deleted entries ([#14](https://github.com/runloopai/api-client-python/issues/14)) ([b9cdcc7](https://github.com/runloopai/api-client-python/commit/b9cdcc75fe73aa0543a31a0f9c5c1eafe923fdf5))
+
+## [1.32.0](https://github.com/runloopai/api-client-python/compare/v1.31.0...v1.32.0) (2026-09-08)
+
+
+### Features
+
+* **axon:** accept user metadata at creation ([d2a4242](https://github.com/runloopai/api-client-python/commit/d2a42420be6a0b355a5511eefedc83044d7f11fe))
+* **axon:** add axon metadata update endpoint ([f66ff15](https://github.com/runloopai/api-client-python/commit/f66ff15436bf522020ebc8dd5b891fd21f0081af))
+* **axon:** filter axon lists by metadata and add metadata discovery endpoints ([a8dc212](https://github.com/runloopai/api-client-python/commit/a8dc212b840327a430d7f98f45172fd110f7a9f6))
+* **stlc:** automate SDK generation ([a33c2a4](https://github.com/runloopai/api-client-python/commit/a33c2a470b6701da37a2c9869d9216d67daa0d2a))
+
+
+### Bug Fixes
+
+* **ci:** authenticate SDK back-sync pushes ([#4](https://github.com/runloopai/api-client-python/issues/4)) ([04cae15](https://github.com/runloopai/api-client-python/commit/04cae15abe12cbf205b1d645592a89c9a530752f))
+* **docs:** deploy documentation for releases ([#835](https://github.com/runloopai/api-client-python/issues/835)) ([68023fb](https://github.com/runloopai/api-client-python/commit/68023fb7c86dd5c9d23360fb8f4e680914d99e52))
+
+
+### Chores
+
+* **stlc:** run Promote SDKs on a daily schedule ([582ab9d](https://github.com/runloopai/api-client-python/commit/582ab9d957684efadcf585f3632ec58084232bcc))
+* **stlc:** run Promote SDKs on a daily schedule ([63c6ef1](https://github.com/runloopai/api-client-python/commit/63c6ef146a0ec0ea5cf5487f01e4ae51ca0e2b54))
+
+
+### Documentation
+
+* **readme:** lead with the sandbox category and add package keywords ([#840](https://github.com/runloopai/api-client-python/issues/840)) ([8353d09](https://github.com/runloopai/api-client-python/commit/8353d097d2ae185cf9082f4c59db468a6fcd15c8))
+
## [1.31.0](https://github.com/runloopai/api-client-python/compare/v1.30.0...v1.31.0) (2026-08-25)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e1abe960c..4802618ef 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -150,6 +150,18 @@ $ ./scripts/format
## Publishing and releases
+The checked-in `uv.lock` must match `pyproject.toml` before dependency installation.
+CI checks this with its pinned uv version (0.10.2). Refresh with that version of
+`uv lock`, without upgrading dependencies, when changing package metadata.
+
+Release Please updates the editable root package in `uv.lock` through its TOML
+extra-file updater. The name filter uses `name.value` because the pinned
+Release Please 17.3.0 parser represents TOML strings as tagged values. When upgrading
+the release action, verify a simulated version bump changes only the root package's
+version and that `uv lock --check` still succeeds; never target a positional package
+index or apply a version replacement to all dependencies.
+
+
Changes made to this repository via the automated release PR pipeline should publish to PyPI automatically. If
the changes aren't made through the automated pipeline, you may want to make releases manually.
diff --git a/README-SDK.md b/README-SDK.md
index 95bc71ef1..33e8fdd6a 100644
--- a/README-SDK.md
+++ b/README-SDK.md
@@ -10,6 +10,7 @@ The `RunloopSDK` builds on top of the underlying REST client and provides a Pyth
- [Core Concepts](#core-concepts)
- [RunloopSDK](#runloopsdk)
- [Available Resources](#available-resources)
+ - [Secrets: names and exact IDs](#secrets-names-and-exact-ids)
- [Devbox](#devbox)
- [Command Execution](#command-execution)
- [Execution Management](#execution-management)
@@ -120,17 +121,27 @@ The SDK provides object-oriented interfaces for all major Runloop resources:
- **`runloop.blueprint`** - Blueprint management (create, list, build blueprints)
- **`runloop.snapshot`** - Snapshot management (list disk snapshots)
- **`runloop.storage_object`** - Storage object management (upload, download, list objects)
-- **`runloop.agent`** - Agent management (create, list agents from npm/pip/git)
- **`runloop.axon`** - [Beta] Axon management (create, publish events, subscribe to SSE streams, SQL queries)
-- **`runloop.scenario`** - Scenario management (list scenarios, start runs)
-- **`runloop.scorer`** - Scorer management (create, list, update)
-- **`runloop.benchmark`** - Benchmark management (create, list, run benchmarks)
- **`runloop.network_policy`** - Network policy management (create, list, update egress rules)
- **`runloop.gateway_config`** - Gateway config management (create, list API proxy configurations)
- **`runloop.mcp_config`** - MCP config management (create, list MCP server configurations)
- **`runloop.secret`** - Secret management (create, update, list, delete encrypted key-value pairs)
- **`runloop.api`** - Direct access to the underlying REST API client
+### Secrets: names and exact IDs
+
+`Secret` / `AsyncSecret` objects from `create()`, `list()`, and `from_name()` always operate by name, even when they carry a captured `.id`. Use a separate `SecretById` / `AsyncSecretById` handle for one exact row:
+
+```python
+created = runloop.secret.create(name="API_TOKEN", value="synthetic-example")
+assert created.id is not None
+exact = runloop.secret.from_id(created.id) # Lazy; never falls back to a name.
+exact.update("synthetic-rotated") # Returns SecretView, as do get_info() and delete().
+exact.delete() # Deletes only this row.
+```
+
+For `AsyncRunloopSDK`, await the operations but not `from_id()`. The handle types are exported from `runloop_api_client.sdk`. ID handles have `.id`, not `.name`; use their instance methods rather than name-only manager helpers. Environment-secret maps still take names; pass `exact.id` explicitly for an ID-based gateway/MCP binding. Name reads/updates select the greatest ID; name deletion removes selected matches nonatomically. IDs identify mutable rows, not value versions, and existing retries can replay writes. Rotation does not rewrite running devbox environments or issued tokens. Avoid debug request logging with real credentials.
+
### Devbox
Object-oriented interface for working with devboxes. Created via `runloop.devbox.create()`, `runloop.devbox.create_from_blueprint_id()`, `runloop.devbox.create_from_blueprint_name()`, `runloop.devbox.create_from_snapshot()`, or `runloop.devbox.from_id()`:
@@ -221,6 +232,23 @@ print("Errors:", result.stderr())
execution.kill()
```
+To send input, start the execution with stdin attached:
+
+```python
+execution = devbox.cmd.exec_async("cat", attach_stdin=True)
+execution.send_std_in("first\n")
+execution.send_std_in("second\n")
+execution.close_std_in()
+result = execution.result()
+assert result.stdout() == "first\nsecond\n"
+```
+
+The async SDK exposes the same helpers with `await`. Send nonempty text, finish each
+send before the next, and wait for pending sends before closing stdin. Concurrent
+calls are not serialized. These helpers preserve the generated client's retry policy;
+a retry after an ambiguous connection failure can replay input (not exactly-once
+execution).
+
**Key methods:**
- `execution.get_state()` - Get current execution state (status, exit_code, etc.)
@@ -408,7 +436,7 @@ async with await runloop.devbox.create(name="temp-devbox") as devbox:
#### Devbox Logs
-Retrieve logs from a devbox, optionally filtered by execution ID or shell name:
+Retrieve logs from a devbox, optionally filtered by execution ID:
```python
# Get all devbox logs
@@ -419,9 +447,6 @@ for log in logs.logs:
# Filter logs by execution ID
result = devbox.cmd.exec('echo "hello"')
exec_logs = devbox.logs(execution_id=result.execution_id)
-
-# Filter logs by shell name
-shell_logs = devbox.logs(shell_name="my-shell")
```
**Key methods:**
@@ -429,7 +454,7 @@ shell_logs = devbox.logs(shell_name="my-shell")
- `devbox.get_info()` - Get devbox details and status
- `devbox.cmd.exec()` - Execute commands synchronously
- `devbox.cmd.exec_async()` - Execute commands asynchronously
-- `devbox.logs()` - Retrieve devbox logs (optionally filter by execution_id or shell_name)
+- `devbox.logs()` - Retrieve devbox logs (optionally filter by execution_id)
- `devbox.file.read()` - Read file contents
- `devbox.file.write()` - Write file contents
- `devbox.file.upload()` - Upload files
@@ -556,6 +581,17 @@ snapshot.delete()
### StorageObject
+Uploads retain the create response's signed URL **and** required headers (including
+Azure's `x-ms-blob-type`). All upload helpers use those instructions before marking
+the object complete. Treat both as credentials; retrieval does not refresh them.
+
+Signed uploads reuse the configured HTTP transport but exclude client-level default
+headers, cookies, and authentication. Custom HTTP transports and event hooks must
+not add API credentials to storage requests. Redirects and SDK-level upload retries
+are disabled. Upload exception messages omit signed credentials and storage response
+bodies; HTTPX request/response objects and third-party debug logging can still contain
+sensitive data and must not be logged indiscriminately.
+
Object-oriented interface for working with storage objects. Created via `runloop.storage_object.create()` or `runloop.storage_object.from_id()`:
```python
diff --git a/README.md b/README.md
index d4625b271..ae21f085a 100644
--- a/README.md
+++ b/README.md
@@ -1,51 +1,70 @@
-# Runloop Python API library
+# Runloop Python SDK — secure sandboxes for AI agents
[)](https://pypi.org/project/runloop_api_client/)
-The Runloop Python library provides convenient access to the Runloop REST API from any Python 3.9+
-application. The library includes type definitions for all request params and response fields,
-and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
+Run AI agents and AI-generated code in **Devboxes**: isolated Linux sandboxes with a shell, a
+filesystem, networking, and optional GPUs. Use this library to create a sandbox, run commands in it,
+read and write files, expose ports over HTTPS, and snapshot the whole machine so you can pause and
+resume it later.
-It is generated with [Stainless](https://www.stainless.com/).
+Why teams pick Runloop:
-## Documentation
-
-The REST API documentation can be found on
-[runloop.ai](https://runloop.ai). The full API of this library can be found in
-[api.md](https://github.com/runloopai/api-client-python/blob/main/api.md).
+- **Safe by default.** Every devbox is isolated at both the VM and container level, so untrusted
+ agent output never touches your infrastructure.
+- **Built for long-running agents.** Devboxes persist for as long as you need, and snapshots let you
+ suspend, resume, and branch an agent's machine state.
+- **Fast starts.** Blueprints bake your dependencies into a reusable image so agents boot in seconds
+ instead of installing tooling every run.
+- **Ready for production.** Secrets injection, AI gateways, network policies, and VPC deployment are
+ part of the platform.
## Installation
```sh
-# install from PyPI
pip install runloop_api_client
```
-## Usage
-
-The full API of this library can be found in [api.md](api.md).
-
-### Object-Oriented SDK
+## Quickstart
-For a higher-level, Pythonic interface, check out the new [`RunloopSDK`](README-SDK.md) which layers an object-oriented API on top of the generated client (including synchronous and asynchronous variants).
+Set `RUNLOOP_API_KEY` in your environment ([get a free key](https://runloop.ai) — new accounts start
+with $50 in credits), then:
```python
from runloop_api_client import RunloopSDK
runloop = RunloopSDK() # Uses RUNLOOP_API_KEY environment variable by default
-# Create a devbox and execute commands with a clean, object-oriented interface
+# Create a sandbox, run a command in it, and clean it up on exit.
with runloop.devbox.create(name="my-devbox") as devbox:
- result = devbox.cmd.exec("echo 'Hello from Runloop!'")
+ devbox.file.write(file_path="main.py", contents="print('hello from a sandbox')")
+ result = devbox.cmd.exec("python main.py")
print(result.stdout())
```
-**See the [SDK documentation](README-SDK.md) for complete examples and API reference.**
+**See the [SDK documentation](README-SDK.md) for complete examples and API reference**, and
+[docs.runloop.ai](https://docs.runloop.ai) for platform guides. Agents and LLM tooling can read
+[llms.txt](llms.txt) for a condensed overview of the whole platform.
+
+## Documentation
+
+- [Platform documentation](https://docs.runloop.ai) — devboxes, blueprints, snapshots, tunnels, agents
+- [`RunloopSDK` reference](README-SDK.md) — the high-level, object-oriented Python interface
+- [api.md](https://github.com/runloopai/api-client-python/blob/main/api.md) — the full generated REST surface
+- [examples/](examples) — runnable end-to-end scripts
+- [llms.txt](llms.txt) — condensed platform summary for AI coding agents
+
+## Usage
+
+This library ships two layers: the object-oriented `RunloopSDK` shown above, and the generated REST
+client it is built on. Both have synchronous and asynchronous variants, include type definitions for
+all request params and response fields, and are powered by
+[httpx](https://github.com/encode/httpx). The generated client is produced with
+[Stainless](https://www.stainless.com/).
### REST API Client
-Alternatively, you can use the generated REST API client directly:
+To use the generated REST API client directly:
```python
import os
diff --git a/api.md b/api.md
index 80a57f551..a75aac87f 100644
--- a/api.md
+++ b/api.md
@@ -3,8 +3,6 @@
```python
from runloop_api_client.types import (
AfterIdle,
- AgentMount,
- AgentSource,
AuthMechanism,
BrokerMount,
CodeMountParameters,
@@ -15,7 +13,6 @@ from runloop_api_client.types import (
Mount,
ObjectMount,
ResumeTriggers,
- RunProfile,
)
```
@@ -31,88 +28,6 @@ Methods:
- client.accounts.me() -> AccountView
-# Benchmarks
-
-Types:
-
-```python
-from runloop_api_client.types import (
- BenchmarkCreateParameters,
- BenchmarkScenarioUpdateParameters,
- BenchmarkUpdateParameters,
- BenchmarkView,
- ScenarioDefinitionListView,
- StartBenchmarkRunParameters,
-)
-```
-
-Methods:
-
-- client.benchmarks.create(\*\*params) -> BenchmarkView
-- client.benchmarks.retrieve(id) -> BenchmarkView
-- client.benchmarks.update(id, \*\*params) -> BenchmarkView
-- client.benchmarks.list(\*\*params) -> SyncBenchmarksCursorIDPage[BenchmarkView]
-- client.benchmarks.definitions(id, \*\*params) -> ScenarioDefinitionListView
-- client.benchmarks.list_public(\*\*params) -> SyncBenchmarksCursorIDPage[BenchmarkView]
-- client.benchmarks.start_run(\*\*params) -> BenchmarkRunView
-- client.benchmarks.update_scenarios(id, \*\*params) -> BenchmarkView
-
-# BenchmarkRuns
-
-Types:
-
-```python
-from runloop_api_client.types import BenchmarkRunListView, BenchmarkRunView
-```
-
-Methods:
-
-- client.benchmark_runs.retrieve(id) -> BenchmarkRunView
-- client.benchmark_runs.list(\*\*params) -> SyncBenchmarkRunsCursorIDPage[BenchmarkRunView]
-- client.benchmark_runs.cancel(id) -> BenchmarkRunView
-- client.benchmark_runs.complete(id) -> BenchmarkRunView
-- client.benchmark_runs.list_scenario_runs(id, \*\*params) -> SyncBenchmarkRunsCursorIDPage[ScenarioRunView]
-
-# BenchmarkJobs
-
-Types:
-
-```python
-from runloop_api_client.types import (
- BenchmarkJobCreateParameters,
- BenchmarkJobListView,
- BenchmarkJobView,
-)
-```
-
-Methods:
-
-- client.benchmark_jobs.create(\*\*params) -> BenchmarkJobView
-- client.benchmark_jobs.retrieve(id) -> BenchmarkJobView
-- client.benchmark_jobs.list(\*\*params) -> BenchmarkJobListView
-
-# Agents
-
-Types:
-
-```python
-from runloop_api_client.types import (
- AgentCreateParameters,
- AgentDevboxCountsView,
- AgentListView,
- AgentView,
-)
-```
-
-Methods:
-
-- client.agents.create(\*\*params) -> AgentView
-- client.agents.retrieve(id) -> AgentView
-- client.agents.list(\*\*params) -> SyncAgentsCursorIDPage[AgentView]
-- client.agents.delete(id) -> object
-- client.agents.devbox_counts() -> AgentDevboxCountsView
-- client.agents.list_public(\*\*params) -> SyncAgentsCursorIDPage[AgentView]
-
# Axons
Types:
@@ -122,6 +37,7 @@ from runloop_api_client.types import (
AxonCreateParams,
AxonEventView,
AxonListView,
+ AxonUpdateParams,
AxonView,
PublishParams,
PublishResultView,
@@ -132,6 +48,7 @@ Methods:
- client.axons.create(\*\*params) -> AxonView
- client.axons.retrieve(id) -> AxonView
+- client.axons.update(id, \*\*params) -> AxonView
- client.axons.list(\*\*params) -> SyncAxonsCursorIDPage[AxonView]
- client.axons.delete(id) -> object
- client.axons.publish(id, \*\*params) -> PublishResultView
@@ -218,7 +135,6 @@ from runloop_api_client.types import (
McpTokenView,
PtyTunnelView,
TunnelView,
- DevboxCreateSSHKeyResponse,
DevboxReadFileContentsResponse,
)
```
@@ -233,7 +149,6 @@ Methods:
- client.devboxes.create_gateway_token(id, \*\*params) -> GatewayTokenView
- client.devboxes.create_mcp_token(id, \*\*params) -> McpTokenView
- client.devboxes.create_pty_tunnel(id) -> PtyTunnelView
-- client.devboxes.create_ssh_key(id) -> DevboxCreateSSHKeyResponse
- client.devboxes.delete_disk_snapshot(id) -> object
- client.devboxes.download_file(id, \*\*params) -> BinaryAPIResponse
- client.devboxes.enable_tunnel(id, \*\*params) -> TunnelView
@@ -314,70 +229,6 @@ Methods:
- client.pty.connect(session_name, \*\*params) -> PtyConnectView
- client.pty.control(session_name, \*\*params) -> PtyControlResultView
-# Scenarios
-
-Types:
-
-```python
-from runloop_api_client.types import (
- InputContext,
- InputContextUpdate,
- ScenarioCreateParameters,
- ScenarioEnvironment,
- ScenarioRunListView,
- ScenarioRunView,
- ScenarioUpdateParameters,
- ScenarioView,
- ScoringContract,
- ScoringContractResultView,
- ScoringContractUpdate,
- ScoringFunction,
- ScoringFunctionResultView,
- StartScenarioRunParameters,
-)
-```
-
-Methods:
-
-- client.scenarios.create(\*\*params) -> ScenarioView
-- client.scenarios.retrieve(id) -> ScenarioView
-- client.scenarios.update(id, \*\*params) -> ScenarioView
-- client.scenarios.list(\*\*params) -> SyncScenariosCursorIDPage[ScenarioView]
-- client.scenarios.archive(id) -> ScenarioView
-- client.scenarios.list_public(\*\*params) -> SyncScenariosCursorIDPage[ScenarioView]
-- client.scenarios.start_run(\*\*params) -> ScenarioRunView
-
-## Runs
-
-Methods:
-
-- client.scenarios.runs.retrieve(id) -> ScenarioRunView
-- client.scenarios.runs.list(\*\*params) -> SyncBenchmarkRunsCursorIDPage[ScenarioRunView]
-- client.scenarios.runs.cancel(id) -> ScenarioRunView
-- client.scenarios.runs.complete(id) -> ScenarioRunView
-- client.scenarios.runs.download_logs(id) -> BinaryAPIResponse
-- client.scenarios.runs.score(id) -> ScenarioRunView
-
-## Scorers
-
-Types:
-
-```python
-from runloop_api_client.types.scenarios import (
- ScorerCreateResponse,
- ScorerRetrieveResponse,
- ScorerUpdateResponse,
- ScorerListResponse,
-)
-```
-
-Methods:
-
-- client.scenarios.scorers.create(\*\*params) -> ScorerCreateResponse
-- client.scenarios.scorers.retrieve(id) -> ScorerRetrieveResponse
-- client.scenarios.scorers.update(id, \*\*params) -> ScorerUpdateResponse
-- client.scenarios.scorers.list(\*\*params) -> SyncScenarioScorersCursorIDPage[ScorerListResponse]
-
# Objects
Types:
@@ -421,6 +272,9 @@ Methods:
- client.secrets.update(name, \*\*params) -> SecretView
- client.secrets.list(\*\*params) -> SecretListView
- client.secrets.delete(name) -> SecretView
+- client.secrets.delete_by_id(id) -> SecretView
+- client.secrets.retrieve_by_id(id) -> SecretView
+- client.secrets.update_by_id(id, \*\*params) -> SecretView
# NetworkPolicies
diff --git a/docs/api/agent.rst b/docs/api/agent.rst
deleted file mode 100644
index 5c163f290..000000000
--- a/docs/api/agent.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-Agent
-=====
-
-.. tabs::
-
- .. tab:: Async
-
- .. automodule:: runloop_api_client.sdk.async_agent
- :members:
-
- .. tab:: Sync
-
- .. automodule:: runloop_api_client.sdk.agent
- :members:
diff --git a/docs/api/benchmark.rst b/docs/api/benchmark.rst
deleted file mode 100644
index 3c61f8885..000000000
--- a/docs/api/benchmark.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-Benchmark
-=========
-
-.. tabs::
-
- .. tab:: Async
-
- .. automodule:: runloop_api_client.sdk.async_benchmark
- :members:
-
- .. tab:: Sync
-
- .. automodule:: runloop_api_client.sdk.benchmark
- :members:
diff --git a/docs/api/benchmark_run.rst b/docs/api/benchmark_run.rst
deleted file mode 100644
index 585788569..000000000
--- a/docs/api/benchmark_run.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-Benchmark Run
-=============
-
-.. tabs::
-
- .. tab:: Async
-
- .. automodule:: runloop_api_client.sdk.async_benchmark_run
- :members:
-
- .. tab:: Sync
-
- .. automodule:: runloop_api_client.sdk.benchmark_run
- :members:
diff --git a/docs/api/index.rst b/docs/api/index.rst
index ae4cf429c..18ebdc126 100644
--- a/docs/api/index.rst
+++ b/docs/api/index.rst
@@ -17,18 +17,6 @@ with ``await`` required on all methods.
snapshot
storage_object
-.. toctree::
- :maxdepth: 1
- :caption: Agents & Evaluation
-
- agent
- scorer
- scenario
- scenario_builder
- scenario_run
- benchmark
- benchmark_run
-
.. toctree::
:maxdepth: 1
:caption: Communication
diff --git a/docs/api/scenario.rst b/docs/api/scenario.rst
deleted file mode 100644
index 7a3dc9497..000000000
--- a/docs/api/scenario.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-Scenario
-========
-
-.. tabs::
-
- .. tab:: Async
-
- .. automodule:: runloop_api_client.sdk.async_scenario
- :members:
-
- .. tab:: Sync
-
- .. automodule:: runloop_api_client.sdk.scenario
- :members:
diff --git a/docs/api/scenario_builder.rst b/docs/api/scenario_builder.rst
deleted file mode 100644
index 8bd21aad7..000000000
--- a/docs/api/scenario_builder.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-Scenario Builder
-================
-
-.. tabs::
-
- .. tab:: Async
-
- .. automodule:: runloop_api_client.sdk.async_scenario_builder
- :members:
-
- .. tab:: Sync
-
- .. automodule:: runloop_api_client.sdk.scenario_builder
- :members:
diff --git a/docs/api/scenario_run.rst b/docs/api/scenario_run.rst
deleted file mode 100644
index c98cec393..000000000
--- a/docs/api/scenario_run.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-Scenario Run
-============
-
-.. tabs::
-
- .. tab:: Async
-
- .. automodule:: runloop_api_client.sdk.async_scenario_run
- :members:
-
- .. tab:: Sync
-
- .. automodule:: runloop_api_client.sdk.scenario_run
- :members:
diff --git a/docs/api/scorer.rst b/docs/api/scorer.rst
deleted file mode 100644
index 4caec75de..000000000
--- a/docs/api/scorer.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-Scorer
-======
-
-.. tabs::
-
- .. tab:: Async
-
- .. automodule:: runloop_api_client.sdk.async_scorer
- :members:
-
- .. tab:: Sync
-
- .. automodule:: runloop_api_client.sdk.scorer
- :members:
diff --git a/docs/api/secret.rst b/docs/api/secret.rst
index e04f1dfb9..2091f87a0 100644
--- a/docs/api/secret.rst
+++ b/docs/api/secret.rst
@@ -8,7 +8,13 @@ Secret
.. automodule:: runloop_api_client.sdk.async_secret
:members:
+ .. automodule:: runloop_api_client.sdk.async_secret_by_id
+ :members:
+
.. tab:: Sync
.. automodule:: runloop_api_client.sdk.secret
:members:
+
+ .. automodule:: runloop_api_client.sdk.secret_by_id
+ :members:
diff --git a/docs/api/types.rst b/docs/api/types.rst
index e436002ca..e1400ea2e 100644
--- a/docs/api/types.rst
+++ b/docs/api/types.rst
@@ -78,26 +78,6 @@ These TypeDicts define parameters for storage object creation, listing, and down
.. autotypeddict:: runloop_api_client.sdk._types.SDKObjectDownloadParams
-Agent Parameters
-----------------
-
-These TypeDicts define parameters for agent creation and listing.
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKAgentCreateParams
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKAgentListParams
-
-Scorer Parameters
------------------
-
-These TypeDicts define parameters for scorer creation, listing, and updating.
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKScorerCreateParams
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKScorerListParams
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKScorerUpdateParams
-
Axon Parameters
---------------
@@ -113,35 +93,7 @@ These TypeDicts define parameters for axon creation, listing, publishing, and SQ
.. autotypeddict:: runloop_api_client.sdk._types.SDKAxonSqlBatchParams
-Scenario Parameters
--------------------
-
-These TypeDicts define parameters for scenario listing, updating, and running.
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKScenarioListParams
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKScenarioUpdateParams
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKScenarioRunParams
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKScenarioRunAsyncParams
-
-Benchmark Parameters
---------------------
-
-These TypeDicts define parameters for benchmark creation, listing, updating, and running.
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKBenchmarkCreateParams
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKBenchmarkListParams
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKBenchmarkUpdateParams
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKBenchmarkStartRunParams
-
-.. autotypeddict:: runloop_api_client.sdk._types.SDKBenchmarkListRunsParams
-.. autotypeddict:: runloop_api_client.sdk._types.SDKBenchmarkRunListScenarioRunsParams
Network Policy Parameters
-------------------------
diff --git a/docs/index.rst b/docs/index.rst
index bae150e1f..f31224088 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -2,7 +2,7 @@ Runloop Python SDK Documentation
==================================
The Runloop Python SDK provides a Pythonic, object-oriented interface for managing
-devboxes, blueprints, snapshots, storage objects, scenarios, benchmarks, and more.
+devboxes, blueprints, snapshots, storage objects, agents, and more.
The SDK offers both asynchronous and synchronous variants with identical interfaces.
Installation
diff --git a/llms.txt b/llms.txt
index 1b8f74f65..08c2cc78b 100644
--- a/llms.txt
+++ b/llms.txt
@@ -25,7 +25,7 @@
- **Prefer `AsyncRunloopSDK` over `RunloopSDK`** for better concurrency and performance; all SDK methods have async equivalents
- Use `async with await runloop.devbox.create()` for automatic cleanup via context manager
-- For resources without SDK coverage (e.g., benchmarks), use `runloop.api.*` as a fallback
+- For resources without SDK coverage, use `runloop.api.*` as a fallback
- Use `await devbox.cmd.exec('command')` for commands expected to return immediately (e.g., `echo`, `pwd`, `cat`)—blocks until completion, returns `ExecutionResult` with stdout/stderr
- Use `await devbox.cmd.exec_async('command')` for long-running or background processes (servers, watchers, builds)—returns immediately with `Execution` handle to check status, get result, or kill
- Both `exec` and `exec_async` support streaming callbacks (`stdout`, `stderr`, `output`) for real-time output
diff --git a/pyproject.toml b/pyproject.toml
index ca20f54f2..da5f535f7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,8 +1,19 @@
[project]
name = "runloop_api_client"
-version = "1.31.0"
-description = "The official Python library for the runloop API"
+version = "3.0.0"
+description = "Official Python SDK for Runloop — secure Linux sandboxes (Devboxes) where AI agents run code, edit files, and expose ports"
dynamic = ["readme"]
+keywords = [
+ "runloop",
+ "sandbox",
+ "devbox",
+ "ai-agents",
+ "code-execution",
+ "code-interpreter",
+ "agent-runtime",
+ "microvm",
+ "llm",
+]
license = "MIT"
authors = [
{ name = "Runloop", email = "support@runloop.ai" },
@@ -34,13 +45,15 @@ classifiers = [
"Operating System :: POSIX :: Linux",
"Operating System :: Microsoft :: Windows",
"Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
"License :: OSI Approved :: MIT License"
]
[project.urls]
-Homepage = "https://github.com/runloopai/api-client-python"
+Homepage = "https://runloop.ai"
Repository = "https://github.com/runloopai/api-client-python"
-Documentation = "https://runloopai.github.io/api-client-python/"
+Documentation = "https://docs.runloop.ai"
+"API Reference" = "https://runloopai.github.io/api-client-python/"
[project.optional-dependencies]
aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"]
diff --git a/release-please-config.json b/release-please-config.json
index 433d96e9f..6ad1b1a0c 100644
--- a/release-please-config.json
+++ b/release-please-config.json
@@ -61,6 +61,11 @@
],
"release-type": "python",
"extra-files": [
- "src/runloop_api_client/_version.py"
+ "src/runloop_api_client/_version.py",
+ {
+ "type": "toml",
+ "path": "uv.lock",
+ "jsonpath": "$.package[?(@.name.value == \"runloop-api-client\")].version"
+ }
]
-}
\ No newline at end of file
+}
diff --git a/scripts/mock b/scripts/mock
index c1dc5bb32..e0dc51950 100755
--- a/scripts/mock
+++ b/scripts/mock
@@ -16,7 +16,7 @@ elif [ -n "$STAINLESS_OPENAPI_SPEC_URL" ]; then
URL="$STAINLESS_OPENAPI_SPEC_URL"
else
# Embedded OpenAPI spec (base64-encoded, gzipped)
- EMBEDDED_SPEC="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"
+ EMBEDDED_SPEC="H4sIAAAAAAAAA+y9aXPjxrIm/FcqeCdG6hsU1d1exiPPnfuqJdrWWC3pipJ9PMcODgiUyDoCARiLJNrR//2NzKwNQAEEt261rU92i4VaM7Oycnnyz16c8MhLRO+o98XgzeB1r98T0V3cO/qzl4s85L2j3nURncdxwo6vznr93gNPMxFHvaPe68GbXr8X8MxPRZLT325mnFntWZZwn+UzL2deGMaPGVvEBctjNouznIXefBJ47K6IfPg8Y14UsFP+MImfeAateORNQs4y3wt5wMI4mrK0iCIRTZkn2GOc3t9Bp4Nev+fHUe75Ocw78uZy2iFO44yNiiSJ07zX7xVp2DvqzfI8yY4OD1NqMvBEr9/jc0/Ajxk1/v+sHz986PcynsLSe0f//LPSi5eIQamn8pboedD2eamARWW9oz8/fPgNJj5P4ohHOfyll3G/SEW+GPkzPsdGvQn3Up4eF/kMG+APvSP5516/ly8SLmdDE4UWHn567PtxEeXvRBiKaHqDDf9UH2R5KqIpLDwq5r2jf/ZGN9dnV8Nev3f882j8/vj6x+HN1fnxCfyFfhpfXV/+n+HJzajX711cXsAPtxfXw5PL7y/O/u/wtPfbh74acpR7eZG1jXZ8cnJ5e3EzHt0c39yOxmcXPx2fn53C6OUfLi/eXR5fn55dfF//bXhx/O586Pjo9GyEv4zf/TI+Pn1/dtHS5L9uL2+Ox9fD45MfXF3dXJ8dn49Pji9OhufOsdTeDC9gkuPr4ejy9vpkOGrZnRvB0y57c3M2vHbsDP753fHo7KT6x6vry+qfhhc3w+ur67PRsPoLrqxllj8J/mjNMp78i/t5jbxlYwZSI5178FfgxySNE57mgmhYBK7l1kWHJzs7Ox30PvQlJ6/yIXyBn+Zyi/9byu96R71/OzR8dig55NA+jbZOoS/sVP5hnGnq7tC9ZIW2Aag/HMJPuZfzYOzlqy0cvxNxxHIx51nuzRPsbkKsv2yqUkLgibeNIrsrHzbInDwVCR/7RZbHc56O6bzL/ZzyJOU+LO6IFRlXfQ3o24H17UBLtX+apUdFGCJ1en4uHvg4KyZW7ysO5uhj2aB08rKfcS5laYfzt6VvdW+bpukaboDSPeW/FyLlAUxTBD3JI5LeaxRaoidDDcjkcB/z4EQEaQcmZydnp9cHEy/jAePTlGcZXegsLULOHkU+YzE29kIGtydLOWwi3et1eeCXR22g77Orhy9xYDYJY/+eiYj5XhRHwvdCBgTI9lGPmIg8Y3/wNH7VZ3wwHbC9N68Hb94OXg9eH775eg8pFCZFoijn86WsexWn+XURcviyPKf9S7nMVwwayW3gAcyH5TOR4YQHbDhP8gWLUxbPRZ7zgM25F2Fr3B/SdZI0zmM/DjOb+Lw09RaG9ipnjjuH55eIY9/nWXbOH3i45DYZjkbj8+FPw/OxvLdLf7seHp9W//bz9dnNUA30I1+cIB1deak35znqQXWaKZ+xW3h/6Pf4UyJSno29fEwnQbKkd9QTUf71l9ZWiCjnU6RrtRmV+QRNd9Ty66fxevnQ793zxTjjfsodQngrC7jmWVykPl+mkillYnzzy9VwfDr86d3lP1CzKP/w7vx2eHV9dnFT/2l0cXw1+uHS8cvlO6XLlf8udQQ6+9SfiZz7eZG2zvPpm6/HuHAvnX/9JX1a5LP33J95kcjm3VSJU34nIp6xWfzI8hlnSSrmXrqAuy3gUS68kImMeUkSCh7A+wAYg2d5Boz0JP8GHxZJlqfcm9cFT96w4fUrD1qw+I55RT6DwX26XudqSUdsb8a9gKd7fbZHyvheHxh+b+Jlwt8bsO/MP/osy+OU4+SIsJiXsST0RMT2ioynR4mXZXvfymXHTwsGsvbrLw945McBz5jIB5Iy65cdTJfmgvoP2wch2Gd7TwdeIg7u+WLv1YBdSyGCgkpNHVf5LRPRgxcK+inOZzzFv2dtV2JFLGEzPPanOLJkxQpyolHOXsCaSLxy5j0tu6rnPPcCL/egTy8IBHVz5SADi6XLg99mPGUBUiMIbuoPiMvLc8+fKTKDudCepVMvEn94VT1Cr9jm/qc4Gj7wKD8XWaOKXd4h/qCeh7KduiI63WV6QBystlaYBtI5LIZGQkqbedl4HpfYfhLHIfciVK/j3AvHqGtUBeAXb1sFYJlu5NKs4X6z9+gyFVMRtUme4T9uhtcXx+fj4U/Dixu4xb4fXtzof92O4Akk/zH6ZXQzfC//+Vt1I3BAFuOIzA+9LBN3kutxQ8r7uPTQMpBNkW/vn9mM2oVRnsr7OIpz0HOY6oVFxXyiXiFPcTTu/KKCYxUoPu/UM0Y/EcYl/uw8vRv1PWhkc9ApM+7HUZCxTMBceRL7Mxwp1ufXiULlcbedDD038PJcvgH0IbUmqcimIp8Vkz7LQs+/f4W9IQ2Ou90M1CPeDdRfUmSzPkuKMBzL24g6TbxFGHsdDun/jC4vpJQPiP+Y/Lau8GuaMkRQOU295XqTSusz81Js1l0KwYgbCaEl8gefZEivH1MA0aoc8uc2CVa4xHZ56aQ8CT2fmytH/Thgl3MBjMEynkNLWCL8N+TeA2dFBKrKlAd9q4kXMY5Pk7mXwL/9kHspahfLr631le2uksmtC0BrVGziu446gHn1riniTmxzylIxF/CQl4Zb0vsptO/S+7d0pI8zEVrnD0owcsugjdDLmlD1zNYkUjiKwibUMkG6TRTl07DmBZO0TU+djY1OO1RNYO3GZuOyh0n7jDKE1Ul4hH93zbvPHmc8osNVptQMG/KAPQiP0aewPC9aXN6hA6JtCdReruAMfDkf+npfkTY+/LamyW41U11V0rqOAykgLHiSiih/V4gw+M4TYZHya+5lcavmFxf5OL4bz/k8TuEekv8ORHYPpi7oa3zniZAHdV0PztD3YElSpNzRqOqfekoM+xn8Gv0alcY7YtAF/shmXgZuKRYXeJFRgwE7IY8Uk/4kJu7IPiQyVkT8KeF+zqFna94tvcLP3fu0V1/tlP46YLcZiZTAy2aT2Evx9RzG8T3z8uoGsDCeZvjUgTsSyXfwKygZTwdwHAfW7iLrlc+G9nsLm1U55ZZ+V9muKrVUu93WhqFwLBP7eTxdfqGGTQa+iiIVT1koIjAwPHDwITL8cGsaP2xrGE/Z/m0knoyboXR3ke4751nmTTto03rK8ov6/UFrrym5aoS6+DiPp1l3nXaivu30njo7rckHXC+c9bqKcZ0eGjVkJKkpj3gKEpkFBUyyJqpqW1hao5xsfd82t+xWp31haWzl/Qpi/56ndyJ0XJSn+jcGLn3YK2CzCaodyHHEX5Ve25TBbJHlfD7OeF4kYz+ez70oKLsClig9xyyUR6C+RrW8iOA/oFcXCcQ1pIxGajHmg7s/4OM5XITdvREnccDfwyfWIbVOMuCMhpBbJyI/LOB9KaLmjatNNfTgBTFOSpRRHtRMCN8ScXQnpnCN4m5QJAcIR+oJZUZ3PeYcPyot2aHGAKVY+7mmWmtZG997CWxi4uUzctLUaPExFTlnE34Hkh2pqu3x1O+BJXdclTSN45OQSVL+IOIiCxdI7znT38MMQHHxMuahkdj6SfugpCho44rKrLpZYbc1M/Yz6LwZz/v010cRhgzsEkieoZfzDG5t9G/dFaEeywg7dDlC46l44BH5/dllFC5YHOE892vb3meONb9i2SwuwgDYBIKV4C26ZOcgEkj4fBX+jXJPRDwVf/BgRF+7jSHWfp9rbrY+Zmps2GvJ1XWmBoUPtHXVFnd3Ao4FfgCWKnN55FrRSWZwYGjMTnIxp8FyL82LhCU8Rf0g8nmrwNiCEcTaALc9RPkBnCLMwXuo2HnpdEvS4TidiDwFpxRdVHL3YAA4EvDiqL2t8WB9duQK2q7gmssAmuyQRw8ijaM5mBRV2BkySkb3Frqh8N8DNqKZaFLxHjwR4gco1+WtV6Ua852XcnZxeUNuLn3PGP4Xc1Dv2PDJmychP2J//to7fTe+Oh6Nfu0dsV97p8c3x++OR0P808+X16e/9j6wuXcPczU+s71asz1rpl7GnEvekyPtdSEVFPRPeasoRIWJyZbyiiXPkHFBVtUydgNyTn0j5LLIMh3fsZPLq1/wtjk+PWWBSDnad7LutyXO6URO3nlTRjyHiMlxEofCX3S7hmCS8jtG36ENMUnChSZz50JV+EemP/dQmDsETxrDD/K7IOYZi+KceXd33M9ZoKJBpQ2J3aXxXN4kauBv4V0HcaWPXtTtC5KeM3glOZbYJ1up+bvZMhZXCVuqNkZJWsEignduXRVfHjv5e8ELHpBe/iAgHJd+xx2l//UePZGDmaVI0N7f78nXrSF04MScu40jFD/UZg6hORxZv4mM0R+ZHBuFNZ2y7yWeD29S5BY/nlsiZvBrZK/jiEFoEjjeIMw3CtgkjnNFMxEHYgH5K6K71MvytMDgBCA4ZCZSRPHoK1MnESRtIzjOO/l/DQ0rW1hZqiIvmKFqySAQCT+XUgG8QzwAo8RBfHcwgbZg0qMHEP46+DVSZprKDKTdQT95QNjSmvAhKClRGyO01UcdbL1D9UugOvwlLiCiikXxI2pu0OHZqbWDKhi71dgjqfGotwVSqNL0UW+XxGCzzFGvlRwcLFVZ8LYIwrBqaYB1SKLO7PUuNyCKskFrDavL5laTdp+iGYjNvdyfISGKMJfepo/jYbRWW3Ez6lVcwZuKP3bbvbL1ZLmfzWlRgYwM0vXgXVU3GlmDlGYKd1NrJJjkgp52ipW/7+bmeWfODRTLR29hczK5H8wLBU41ibE1SBlNnvAMWsgwVZl2Qs8J2UPr1/D6nNE+2UqltPYAf0zAhaBUPjEHsaV4A6UUmUjXj4UHbcNlPeseFB81WuC6BbA71ZLVtQU14FLnW4XKmkZqWBNRxxh2fStO39pqlhu+pRtBRCKb2RNZMq56T6AgXnv8Vldw2YjX/dAt85vrOEy32jwrmbR8NtK9Nk61V6/el3LBURvy1dSISd6AffjZixYrvI9anIzO51IHq90N3YzyoNDi1dEkXTLqjHdoUFI3oeuLzHo7121IupGUdfwpCYUv8nBBchN0OzDiUeIJaGdS+LWaiEQ2TopJKHzHnVud+88zTrGo8HYrqVrURbiQj0oh7RQQXA/BCZn0Qu0oJMfLstgXqOtpK+TEde51GwM9TsegAk9EKPSV0OXQaXdP7E9rE7Z/tS9526ATR8sPqiXRRGeWZFJIV+RuSdbg3Z/G9zx9r/So5UFmnWMbDe+Z4FVGkd9ADmQJiyNpkVGL/oBjYt6Fm6XVryTSJgs6YFwF6RCheOCpjJXVscBTToffURyZTblSk3GKIex3PBGRlzoiv9/h39EPLV0saio6CBzye8EjBDHgt3JBOu+EaER+67GsmEhzDNv3/KQPYbBFwMf/yiBOBXp5kv+fCPyfV+1iDown8GAig1bsWsHP1ITpJiCKHmeCoqxrq6KNGLBTfucVodn+GTwjrT7u0IjU0aAvHV3KStzZLZhOi7miAbT82rSgrKbqIP655/nJ3m87P4WayG0KbFUaO1E2+dDIGIRbC4LZ/sUVOCkDUHUgj4OoWwb2/ATEh1lcj9yjT+ofcnnUt23fhAiTiHdgs0uUMtosCqsTcIRzEXl5jCloUv4sLihrXIbJzr0kkXmTUlJ1GwF2Sbtrl4u7lCfxeD2FHj7VUm7A3i2YPDgiES348XcwS+TsELjkEG7Iwz/1yB9QIuI/48fInZdcnwk2taeCvYgoy70wVE72OrefUQPSshWDoDEW/462E/SoYp+ZAGZu592pyMdIANWhlCkbIr5ZysEhmHrAYOBA8qavUFedcf8+LvK6PDHDq21l9Hn7bPL4njeot5U8ImxpWbBAhECoJ+1l6xhL+NlEGJS52fp7lZcNGZYowWJtVwjCC3W/UPeWqbsbXRJBNjyBthS9VCVI/WJTbnUiiXmnODc056C1tbU3k+foCLXBkFXToKWnrjroiTWcU/fk0cMGjyeYsMsf2z715rcTRHCO5b28kp6GOj2Ef6qPmyeAOd2mnZfqlzGZcODL/wU552Po8H8f/S/dBf2hVRGLjcukokjqDVZRBWZm0pwiv13VuagoVHEMj/JlKcdFpLVmGAy0GgAOwPdHvzf9Q4DSlnt4O0z/oJ7LdNvOfiCgu7Ogat3OOKB8P8Zpx2ejat3WZ2Uz9aStsXDlaEj+AZNpO2YwRMzwk8ratVKaVeqyyW32APcoEwFvyIlGOzd78MICnTk8I4+3Fxl4EYqeiFPmsVDkPPVCRvvzLeNPnp+bqKk9aruHd9UedrrH5gWALGCsW92c3f0sXSnKp6cHx1dX52cnxzdnlxcHPw5/2SNTqkm/d2eAyDWhs1A77eQIOO0BA3iEBDbQjnXBDO2M++M9Ru58jObLvgWzYwCbjc6GlOdFSoyvG7ffjThmfbrncrvLMxvpKBlMAceQENhwGpZehzSfA3TB6VMBD0XpElD52hmPMoEJdDjEWlEIZCA5zhaRP3zifgFLOOW5J8KOjikybnWx3kg3iUAbYsq1svQIMTw4Nie/AVcT6dTtMAFwLNhv4zzRPeB2pzGCl6zgBKGp6g1p8IKcFGnKNZJPbWw5XhAXDpIe5V4UQDoB5C6Y+O7JQm2LjI25EzwMSPNNAawsYkWE/qgoF2FlrZC3oD27S8IZ84Cnacu0eJrG6SeYGH8SNtRSzQFbOfsnkdPbwMRpWwewxZk63Ct0tOM8LSJMl3K9DQJQjiEaU5u1wZ0F3yHZ629ZUKA1OxN/cBaKuchL3Gys5ZUTXGt0nqabjV71G2sZUOFdzW5G0pxUrODNGsl9FD+ifwC1o7GIxvR/dmeoJ2k2XeWZar3nll9i1rNOCioM4DAGZpbNeBhC7pEMmgQlEnIFZByazJ+sGiwj5rG7lGczCOUQkewF0/0xsnCc5YFwPL60k0SDU2BDaQZXkTceCHUdxzlgFzHo4wryQy5kjI14FKA7vPxqvPPCjLeaVZdRhtrl6pGtk3jSHJ5Lm6snSFd+HmOUeMkL0Cp4zKtlbOM1biFId+h8EGnfqSuB4uOHEK8SOHxjonNJ89CmGYSy5CHQ9r8w0a4pQDdTbyy53hWjhDOIP24P/FV2D1KS47uWeOL23d5OtslaKSaYfiizesCgxL0lweU8ytMFsnIry1AmhmKYvi3J6J6ENCe4O+l7OESB8VQikoIDA/eQEEj6s1DccX/hh8ZMN4kLKTP1pPosmxU5eo6D+DEyWdDK9yHM9bskk6XVSW8cxxTEVmQGTEgR3BnINcyczOsbMOE6tE+7e5XVTGG7Kn2WotxLaSijyEuyWQzD95k9Gftfa2aiLEvfUTk7ZqCGDfgI6Ti73IcNMtXIHKrCRYEZrWQ1NuEz70HE2hZsJPI2c9gyuTVO+rX2ren0Pto+fzxoLSlJVgHXeia5lepQ2sV1PXdqtWmTf7Flol3m2Oo1LqKIOwIkrJvjRDIRmfylaULFAv30llEX4JqR4tFOA63dO55qryNWijwGiAzfA4Gjo8JlhDUsQ34QoI3CKB7u1QKXJ3GUrWAgv8H+aZluzp16OQeDURtDtI3wPX0/SrjfnnaGEQVyNLVr5GvJKkYhgg8EEFlU5+75QmUbOXWkJOV34knb477/eTQ+vrj54fry6uwEQkZudDyDGh23mTAKtY0SX0M52EhDwDkHjVbZ5Iwubs0Rrgt4+PIncETJlimnr2HOthJYmRT8Rc5l74jteVE+S+MEQRaVneyI7c0XYxlZAPiHHz60Co65n6x7gu/9xH16/3zHc+83ZmvXJ1euo8M/I7i8DGfrdnCRjYoIfZDPjUcPPIwTyLXDxyDg74ciuoe7DZvR1YcSV6rBJd0NT9cLM4Cd8jm8mq7Px+9Prsa31+el4WbFxHorWqcFjUfDk+vhDR7V3IekexgTjoXg1w7AuBlH4aJ8Zu9/GX9/dvPD7bvxzeWPw4v2U/ug35GnIrtXt173JAR16a6dg1AfvD0EUw/4CXMRzKIrqQgNi9ka2FcRid8LbsF9KY1KzagF/stiIbTAUPC+7RUynSwHBdt6gLhWuLoAo2wdkKt7iGppp0nhHa9gsr8x+aryQgWFFB+GagPAkGhnXdojdcY/sMYpP92WDNUebj2fi3xs4dK0Uxg2V6A0VSJj+3Pvib15/fo182de6vmgMy4JhARr6niyyHmXdIDqZoAltjoHETHsrs9SDgEnD1xpzmXghVa7dTNWmxXNq2nWQTSW5IgfI3gwfifClSx6YAPp6rUFjPmSRgdmE4JcIZhE+V5FawoSBbuCjzAd2WySw/xad/jivMzyhg8EZb8C/uuKrGVimbXtIYdg0IRH6N7kcgYSsZT+MQ64FwCA0pryrCKw9ksSi3l3OU9VLG7dKJIVGUwOkLGOgweRgRkbMzR5lh/wu7s4deWV2f4B1yqsPX8uPkjjt2vvcLkfr1dyt3XsrcX91qt7yVpJoMltZryWdcdZ6xHKrdGrKk/HcZYr5LbLSk89KpFkcgmpxx9FGOper+kdspw87kUYjqWBcTxN4yJp9ahAc6mB5yI1pkn8ku1LK+AReUdeoR0xTwvexw9lsQf6xBi5M9Bbyh15pObnXjqlkAZ8BbT6VvQ+dFd4FTDCZvpuu46rwRc+nYqrl+nUcDsB7vnzwKmlnNW0V0vP0o5BysaGW4fuyy4KymcH8efPG/LhpNuTDaUEXWJPbFDI6sCASwMFwDLXIUwA5ogy70SGCuTGqbvEx69Bx5dzyXk8HSn47UY1Wg4extPOuIdyDiV6HjWCoSuRWkG+K7mGyE3fI9eWSuuC/97Ps2l5oBVwFTdBRDSM2o6EqIwR5HEIVHZXdSMV3CF1e81X11HR6fdcFVUzOXuRRCe3wDrbfccH6uk3MDJbFdMi+bCW7IF+NBi2wsG2/ACgk9Ew4UKXgNznT2jozehL0EZzo5W+smbIQy/JMNF2wynKjmpTxdgOOVdTiS1GDI19iLCI1Dd3LOcpph/BFNlZJJcAugK2SPnUS4MQtAN9oVLaOy7owU+KTdbxcHJ1ywp814qIwb8O1CL2XcfJ5kWYCwpPlamQVIoCZgefa9MGAPmOp5NNJvceOzHT+/7dSpOjOdCHNCuAAd5wTmATa5tRhbYqU4IJ0AtezophfwbhG+NvbKoi9HfAUcMhETMsK+ayOAS5yc3TS8YxpvkmZi2Lsf2VcO95FKwAc2ANY/iheQSsWoDw9+KOZbmMRQDOXxqM1/TGcIRzOeM2my4TSnd0Cb0mSVNhWBef1Km0eqSO2LkRj4JRHpx1f/q4Ue1uJIJdxik+I8vRbaZSn6SktV6D7VauaeQ5fIYj/PuKw3QEu8euMafB4aD74NqvDNPhPoIhQSg7ApLs6sHMJnA5oAxh6ZEJVPCmqoZDgRmOZ1UzooL8Hi/WUmBHhmnsq4R1ytEt2pRGSowkpwd/R5fMCgHZapAWVBo3b2sTKoxsVTCVf3a/avRHytcs0Ul0HxaMlr2d3Qm50ZvkyssCi1CzNXuIBiNlva7NVMLZd08QqAsfOcPOsmcLcZv6fHYcn2L7a5U3RYVnUstJ44RcESmdHQ8nZadDmwfH7YFoO9Da0S23w4lorLIlepLkLFNcBd9Ldg631iYh2J2t5UrEnp3CNXJ7e3b68D9kSpPK1APFSgR8nsQ5j3wyTeep59/LY3/mUd+Ewpzlwkc1QBqglxk3qGXpERUjBCMFichVGKSpPoCZ5TF7+5X6oBz2bf/9UksQQRCt96jBruJhso657wgIR9ivm9TDVKY42u6FAeJUUj28C/UotgLb1ssFvnrcpUJBk80NDhqV3WKxjzdQsCoAFt71WJ0mkF5PBD0zUyb0K8gZ9BVo5nLOt+qZbRRt3znEnoqT1auRuYqRSdZtqEf2cQMR7XJrkrkrBde6rWnFAmuWeX15EqmJaUtSnmHUuMceRJoXHsTDYQgQhgtZoUMDdobgWR4wcBzi1ZJ5UQD9SJJDoC+yp8FKptRxFOgbECSapyHQMRgScRK9jB2fWT4q+GV95MOqL3SF0nBd0jvWlBrdVUxr1juKOZHHv9xOv/oLXQszbbsENENuuQSXDMuuiCSV5qmeRxldd9L8FSqbVutTXkQiF4DU06l425lq3Vi3LZdFnXW/kvJlJEnl8Mzoy9H/OvS4fg7DzawSDIP8KCJjrinRRV9tvWU9LaGhe51L57RFpt/YwU/bmNSoUxjXx6hqWJX7m2FYKk4y0JXWX2dYLmVPfrknRcmqD0eQUx1ALSHlBrJtWhehGrlXoX5tWIf6eZOFjGQfbStxpnyslIrhIoIamn8V1rQq3t0prGv4u9pxHa0EBNVuQcEDGamIgXUIVGghjw3w7tjojhtOs6qit8y0qrWi/I9TgEHAF20gMwdIH29INzBpBFbJTFR/dbqAJUfKpaZAKVOvNGpBAf0QY75qEkCzMUaGoo9BId5GHoA7wOJ7d+i/zmMKTIUlabzeRvT/sqD5zRYtQ+fdC5bh846Y+U3XvL3AeXf4PoN8hGI6syPlW5V/h49Bg6zWwjFtha4UnFkXiBXx5JIFv5UeHlsqvWoMxLrkyIx7YT4bIyLZkqKsOZ+CaSS+05cpFOpMBdQ91y5IKYIbyrKqx5FXKTMK8iEFqAfIP4L8rLq4x/cfWjUBKwcCslKdHoh+PFfB1vp46PazR7sKOcTlphxoCxqBE0RWJQUqKmSqVg4hl+oHTAKHhD/fy2Dg6saWRpelIRyDFm11S3NdPKJUCHfwa+Q4M9eAkAxMTQlwTlXymXtg3yLYCbOFRS5CmcuHvs0wznTk8tx7EvNijmVbAe8oVUdBGWUNp7FaEdqdkoazPO3HIQ4X25XG3zJ5NPC0a8hPSyC9sm2lolq2odMmYqyUWQiH4jwZe4D8rO2xdhzVGGLQQJUKQnh55nk45k8JynSnnJOqddnUZ2tLlZe+3AGYEGjm9HYG8WdPs8SeWouXgC6QWCe3C0RJeealL81vhJfkzS0cnF8jWGLrSNYF7fm0FvBhEsSByRgd/BpZ29Ta483NOcN26sNmpi8dW4kc2zbEcZalb1u3RB1762hdN6VCPK19VralSupLXTygiAUF3dBNJcXIrhGKP+ifJihbxqCU/kFx2zwr5vRnKS165uHZpdqYdbnrGR4xU4t+YJMKoBVYJaYqhaA8WTyKorEwqoLEOy13YMqYbdRltYCZgoUQmW6+Vvkq4DZr/03Hyk6BdWZoN7XLKZKWdB5R/caKHkxLAlQ6XzKT/Ka6BxCmuHA0Nwdf+iJQcU0cxleGIwn7kngpvQnx26zUUWX3VT+Z9yBTvaOYiqhTyA0yHjjg9FgoPSuYCL9GigxbJgkJS+X5qePxTIiV6VFSs1PrqncRp/ow0LQkRY3mfzhcp+Cuh2Qotu+6Ga0C0jA9RK7sgKeMMCkJr9U7rdeC2xpfVQWb6XqLnGWJy+pOuHmrLFVL36zKXSWZ7OxpM/6y5XzLRFfiMOvCcKqu6/CYffWUOt0Cl1n37c+eyL+LU+n97xwXQ/fepqbDanKV07xdS+xianA7RGDAjnPwbYIFL9L3ssJ/JbBVJSx0vpzMB9TgpbCzcRwRUhS9ahBaZ6EueNWPnoJOYIHnkxWruyTcwU6UbY98MM75FSIeVsmrVwf5W5Uk6Ew+MkWUYx3qxKD0yQ0pwIaM+OwI4IvXbgJ483pbBAAYbh8j10TjxemckjwuJ6Cskl9Cxcl4uThnyxRub747+EYFcNQx7OTDFabVlrxijQo7OMS4s+si5I2ho5WYR/yA7cdFjlhzr3RN5xQ6IRBMdkF/u8JyzvX4BCwVOfZCOxOvKZRWp3pC4gan0fPUu7sTvlJPHuX9SgjNCD0rIeWnEeA/y0pAMLXj8/PLn8fH5+dUglpIjqAJyeDbPJb/t+Ls4kczswnPH7nE2ZPo4HsZwyelytxEjwn6PKw58GAMqPeI+dgmmpb4WU8vRgeATxDIqpzoKkJn0KMIAx9sT9K8pRFlsiP2T4kaM/DjOQDG/PsgSub/ygZxOoV/eokYQJErT2CD3wZsiPE3pnN9rv9BMKZz7kUQwlatTb5/Orz4BU9B4b69Km2BL4J0bcl8TJ2ciCB11G07O71u2JlYijgqYaDqqoNy05dI8g/cWqZ9UhYNURkx6atag4BU3oYkdLvOlcKFaoKhMpMAj9GWp6CQiNxASvWiVZrDG5nLRfFVEnDvqmuZKMyAHK+5Xy5Q4L4AYMKgz1G03smsiO67xc7Hd3cZtxWMlbJ/AEuEURd0R4PpFQZXGcEEJ0yRqUWedIFDuDHQkaYH6rV+MLJT2C64MjuWz6KM+Q5TkZn1cAlSjoa+Kg0GoiwlZF+By3uWRTW0eVzevlS2qNehWpNBlC1Xa7L+Xt0ruWozS6tOE2zerSxP3kXXcE1N04us+oEFNj6hRtIEzSJ94oTP1wVMuhERVHVvBXp4LOKPrDTERrUvirkXIexZ3QddHoRdINAy6AmhN+EhBWCCswRVbPnyREewBvddPr7EllAfMUBzU17/WZ4n2dHhIVykGk0P71J5/RX5bDznEOoqsvnSe67IZ+9VY5Rd1fn8ED+WilgYFrTqoMD1Af+rIAYHbIRhAOT/xTi+/T2qqgFKwIR7KU/3XpHCpa5MCAVAvWtQu3IbC2pZfzbxBeqOq9vqmzGnEKZsTFNcAY+0WlGmHmFTLR+Tte2bRPHJZY0ups8RH3rzOMvZN+jeEOWiIe0FYWUtHE2DNSKp8WcbdkA1gijxppQgrWOJSl05aqqrIBw6n7V1tNIw7t3HW61hVu0YJy3JeGBpSjmZnFJMULRrM0/4Io5kKkCCYBgd4FJc2etUOac2cfJCImIL0snvBS/XpVv2+K3ufgV5pTRalzSERjmNznDogMy+jD8Jcv5WROhxGNrvLS0PDhiATlq5C7KRsmBg32TC7pIKccEfN5bk7XUIggb8dhh5bZHeNmRd1tcHrtT6063pkZ0k4QJNAZaEd8nzzgF49QvFGYfXKt1h2s9esMMklWip1QjLBuyaElNkaJsiffyC8KnCxbcYzaFTUPA3nYMClgoJnqn7XOkOqPJyV3le+kh6WCBY2K8BSKtHo+2cLwfUld6cmNKiuLkEoqW5AlhutkzhWD9VpRHXtKI5AmuRvaUxlF5Va0OsSyg1+BjJ4F5fq5/NVLmZKuqctJRfL6rni/JYVx5HGAF7MPH8e8gNIuEBlWh5CpGaZHCnKNnJwhTR+1ZViYFbMQIUM+02aIWj31EeVVkyVfOaKuAkrxoAQZYqw7XpW1oRgocvlaFWYxOUXDa/lYVpNUrZYZijdCwVOf2pcN6bVPlusqW8eGlCGLATymT0qr+fnbL96aM/fnp6eoXoMlim5Jhk4EQo0Cm8EQFsI4LKmCq5QPYhs/E9KqJRqVW5fMbyTGTNUbUldjVPPXnZFHLMUjWYW/Pu6TlUKKub7bCsv68BWVvZZindYaffvP3Cm/h2Sc81ul+6Z037Yq3IHt7apBuI/d/IdgTcNReRepKUeYhSC8j5dGpxGv095HlmB3n6noQbBeZQYBM65r2yyRbnFVKCmx3pG84jEQvAMfWNg9hcHt49AyZ8vmyEFLITyGcV2qvpAcJLZKEs3JBt86UZiS56rVlLi4Ks7t5BpcNOLDwnezDMA3yH9ji1LrL8gnoWpyq4m9QPWbzIw1K90hmqb5s4FWgIwpgxhaNBO2Tl4BVpB+RUm05BAVVzt242yB7KMVIht0vFwT/++7+9efvFt5QsBf//1bdYDsOZXiQiXavZmuRaKjOIAa0221usjy/emZZcOlGUL1lVeLhV5RbkKJdUJqqjU1xmUDwLQn7sKxW8MarZBO3LELN60DH1gpTr3XOp5mocr5Cy4FNMI0vFdMpTDKq0AzZVIkE56FQOeMRG9D/lX1uCMk2MmqtjaylHPUfXuOuwO6WSTKvEncCiST3tCpXosDIe4zbWuuozxAoLuYp8V+Y22HtC35INlib4GwJY5UQnnOqiwxAuHb66dDMfpLoSqkAL4Zk6tV4iMEDyLuRP2Ect+XiphlH9QgWdLuICLTr40gNcybxcwE5bjfPYLiIKUvmRQ2mfemYkFkBOSKc1D2IDaCbLUJaxHOZeek+hXnsyBHOvrkbInESNPGy/Y5eEiox4buHRq0JmiD9SSvLt29Mrp2moKpJpEbW+LdXujbFKR2pC9So6H8Ba6FpmZsv3AQW1z66P3yPGp3jXZ1zXIadwWPzzK1lnMqdCi6P3EGgiKMN8wP4xxj8csdeDrxj1+Aa+gn777Ev8X+hswGS7N7LVW3er98PTs9v3R+ytbPalafaN1ez8+Pr7oWn1jWn15mur2T/GsuGXam5fN7XUTb+RTb9429D05HZ0c/l+PDr7v8MjJEJp9QDATD9OobiJ/Mt0IrP4+iX/HjzOqAEiaKocvG5mZQWSPBJ/OLEj+z3NS2MIvXFSb6NorOczm1j735hJkseeMWIApZbm3oqz3CpeDjgqMn6sjagr+WpNQZVNQPBWGKVdBsQuAVKuz6duMB1MCRN9w2ZxkYJihYl78Kcvv8G/ZWz/zf94+81rHWz5qtXh1O/hLPQlUQvCkNU85fxMAVqKqVKpVhjOLfIFcqLpEcPhgSft5dEms4Z9xB4mMURdmW6weqgaemD/kJrasgsKpp15CtgQbA+obcrEU1cHVgXJbtTdpgw4ib3KeyVCCeJiEtYRdcjcyABRGr8ZsPcyTvj14Ks+e4MIvp5CREZt+i0SA9LG1/aJRwpdrWoC1azfwcsp5yMzejXqsnhnJvambUpffwlt2+mwKnC6T6sMBW1Pqj4fgY/Bt3iZzLtPzkv9mcg5Zru4nQzyZWE3dFVDLiIk18CKi3765uvx11+u4Laz5+IkOYj2aa3Za0d8YGiQKVYvEyitKeuLVdVm/n/wyf8zvl3pbu02e0CHW8YySokcQ9Ch8PlK+o25Ak7IhsFT8QcPRtSVqbY+4ZQNxQOC/vYsHUxaJsGC+eCFInB3pWL3jYFWROWK0GaM1itFRsuOMUR5sayOm7FCqChb+g5N0+Akhv85VVHHpNFZYKcZt3LvTYFqOHQLvaqept83fZbQpUQuxXo0g8opMpqCZlRECLfPnwBRUYDNN37gaSqCgC/x02hhXd+Kc30H1d2c+rbAHCpTZnrAzFWGbeQEISbbeS/sC3kL4XJ0Xdw4OcACIqz9DsKQj1d9NQv51M0qw9GvY/VrWQWzrtpZHN9Xv9X/N8ZfVyicrT5c6RKz0wXHueBpC4X22XGYzLxXQPpUaAtxZEh2wBLh8QZ/C70/hKMAsXSLJik/AGc75cSITCIsetA3u+MeZTdGBIOgy4nDxxIlVBlqozgX/gr665W11htYagP++dKdXP4UbaFk22ayIkEb2nRT4AZ0Z6itkuCxdSVyfeZzKoBX6EClDkndh02GSFS87SVg1g7VwMqmO5QIdUQaMlSenTuhs/NbrJhz1XUD4Fv5cFuc8BXSGKiJaTsEmhHsoBLZgIKX6+YO+XP2Lcu4QoYxncEJ4f0WR+htWdBlWiRrSLsfcGnLOPkHtQFd+ZZYxOJXuaKGrcnABII23nChDEYWfoXsAzWyts2SybwyAi6eAk7upMhZEKOyNglj/56ZhGfyjKkJWTUh2aQIQGm1tdEvXgP6+TzroyiF7viTz3nAvtY/ENL1A9DqJI3vAd0x9UTEkrDIauum2n2Y00jjUvUG6BIgGxWB4DkDs0+8KICroE88r5IPM7nOvhYHOtVSqGKAnKxZ+giq9KRLndQIqi9L26OrEV/lxHt1QVdd3iqa6YllcluTUcgDjGFnEUz790LwzAeIIT6Po/Jbv6ZmOiigg9XiVJ9bpaIMUH3p/OFkamQfFJi/qA/l1EFr+tp3kBvSz8egF/AGg60FnWLoJ3iYk88ZegBIduUM/el9ewzvh37vvZ/sMntCd7+rzAkzQJdYW5QvdyLN4KE4RdO3JOG9gz1liVXyLrMeYjReHIcsjbGai45fq5azZ5hOLf+85+rr1fruQCu/rTWYbu4nA07Zk3Yoncxgg3VsksP5fRhPgP8hCiiTj0oMJ5KFgqF7PAeVAis9tyKzFlBO7/z3vd8oRgLCH+D7vsn5zDjYKsb/vqd3ezDl+Vh9orAh9Yw2DdYzSI/POVCvSGQ6nSNSzw6CWCW8d3nwt/ILJ6mYI/67FUjSPr2yQFXZOluOAV+eo1Jigd9sAbhZeoruxvHmgGzQDdNSdPfLU1LsmXw26Shm0hunoti7XUlD0aPsJgXFuos+VfrJatfh5qknDRfSkgyT6j3U9eKBgacrXj5r3DON90qL4Fwv9+S5XDXPPe+ke1ZSJRfJfVdt+9axRUvHKyQyrNqaDuPHUcT9XMKOeZG5WIF29t/HAQ/R6A9VHK/SOI/9OHyldaxOyTFNnLP9lBjrObCthJWaxNtSsspfSNVWc+6iUcepS0q+etGpbZ36I2e+7Cq4cw0lfocFjYw83F4STokLuyXlyJIEy0W4Kk9A7V2FApoycer1Amp5OArUpiEDp3QJUFfLEnH60q8ax6FyTtDYEECfxqFxcShQHVrEnkayom1se950E7DlSgqlRIFSmQXIEoC+n12qjnV+66YbWHtWyjiwC2Isjdg1nayRGGBttLweoLtPlKxTXkk1Uee9n6yZpCN5tDlXxxT/aMrQmaZeVM7R0dV1KCqlzor1xB2z2f0uWTtdufRZcujnwGOfMKNnW0ybVym0XPRp01QedQE921QeNUFM5dGrsIApQM+aehA2WU7ruT4fvz+52m32jksolNJ4YObVFJ6dPBnUI5heClb6F9RgXJatUxXLzZk6dbOqLEv0I190LP1+zxerLJte3XKUn1Cv7jyOcxdJN195BsreGUe8gz3hEudEH4ElofU1Ewe8W0uDl7es5Tt0g8q2kBMlgHSgGmceY6iU3KvFBb7Nacn93txLEsT5/lPuqsSl67BKwKgLeHt7s9K+jXq3dLX9Hrl129uX1gxnVsKA3Y3fcwnM7HZ8n+VBuqLGrfuKr4633MSrcHSbA0S7IejuV7BxF6/KziSEdLXnYxwclQm5cHQ7zW4DBF3HVDH3QO0QpRjCOLkKHiynVVBRaWjSZY1VnN6u14a18O1j9P7Wbt+ogutujqZrH+PVw5dsR+i6avlH7J9/7sH0947Y3pvXgzdvB68Hrw/ffA0bgQk+e9gE/nfviH355RcffvvQYVPqoL2dqXUDuN41mauC7bvZTNtRfVecocst/Fv1IljF/6t8JXYHoMohmUjnMJYvMxVLHZeAHUy/QfXY0iqW+4crc96Vk3hznzAunp4bCBKbJXGUtZcLrx50dYcr/uDSzu3GJ1y5oxv8wl39vTTHYAWtoPWWblUF1Fi70wTWv/63c9dvCyJ/d5eyOoO/wI2slrLL63jj+3T1S3Tzm3GV67D9lqtKtK63Wekj7Y2WE6gUSDC1NBQvDJi+/nxpnTMepIlK08r6+gOKdFUFrGQAs8NqubG3uSKltuVxdj67tvHOck+7NUYHz2gZX1ZLqbjWK0/bHK+VHbclr9+NKsJe9fyV6a+D96/fozipTWZzDvWNqJvNJtPmiqTjcdTwrswflRHKf+5SHoLsKzLqowuHTwoRBlSa5yln0m89WYCmQn3VuU/aeVazUUOFY/yuzx5nULZXVwMCbYfyTFWItVeeVadSCXKBpTIJ8m+1chJ6/lZtBLlxOzH5NG1kd5ljCxnVm6lEMVab024zxLY3ZLZz8J8sVgF9OYbSxeQhU06HSlyVK24sUVkydIBRCUXVH7rA89zDkBUclDYaJU2cTr1IOhJscafPQos7KIvbKRWklAhmc7cctsLG/RLChd2uaowJOBX8xWJjlJSTsVz49yYhp/y5StBe5dVCwqN07IZ4T+PHCIp73KZh1/tdPZ1sH5sHaauZmMIpBbJL9KWgH7RZLKi2486OmpZxStRX9UPYA5nlbxalTX04dA3qY+0nOPXrfnufl8bGeMl8jXf3TQo6zt1HDM4mQA5r25jnp3GGDzPseaXyeGqHK09w21WwvMLROneSxY+qKA0BjlFnm9S1kd1WkHoAwsWbZHEI9ULxo/hONd0XnB1CeZtDyJ08pL8O8qccgVJ0yRs4aAToeCg1l38aByJ91f26dNUWKv3SdnXau1S7SKHAZOsEbq/OL49Pzy6+7/V718Pj0/HlxfkvvX7vdHg+vBme9vq94fX15bXVZefwWclOKWAqZaRisCwHUCSGNw7y2YJeiuoa2sEbw7o8N77oM7Wdy0UN7bzzhi/SFCHdoIVrEEA4g2JfnYCgID4D8GpK/WgtQkRYNixj+8DzrIhyEbIioZK/y+CcPqJas8O3y6VToWCZgDhQnsT+TD4GQW8YEx6ANY8Ow+JoVf1EDqvSRqRa0j6N1tP4eJrf5TqaHxGVUjrqqHykYCi9glpb1UhJDPxwc3PFrm5v2D4m4UjBAeKbCGRJYRQ5Bysoec2Nwnmo8GP9nkBsHzNzjPJUi67MGJtGmGjcMPvqDrY8UjMpSkr86AyNvYrTXKUYtEn9m5OrXr93e3pVR109/xLcTNiDjLgjM18hHQYwBBgqOql38CVUDMEeEElFR8Fj12BltA0bdfEPny4VBy4FaWbGwuH33xx8/dVXX3z1Cu/xfCbfBvgmtiaoQonGauDu1Y3PIO8/A4UAorRUFWWz9H34fzmJAbucy6rHHsiAaUhNl8iAxDrbxnns3Zxc7cEi925Pr/YqWZ3w2wr4NTY9Lc/mxD1DOqzi3rRBAUtoHzhTBcV6lUO5h6hF26hAKfAMRhu7L3h5cRduzld7OoYQbWkKrDWSST7uN5W+v2xVRb0SwMSM0rXpV78cTNaGVpnGj12bEmBuHnaGCv76y5qWqRZlrcAcDsS6Lsd5BsH/B8owMY08iEnzwzjjlX5WAUH29JithFud4842Wi7MFXxmL/EaX4G7pmY55g36vTor6gYJ9+rmF+k0k+Xf0RBhRWwGLIewRDDcmCDrUlB2xpXbjZJoAPGPlP7SIww9DNBgyiOeSgg4ns0QTwV0ERmVCo81b8qPmMrm+hOEzIeDP2mQ8T1ffBjQ/w/SIgrjOBl4grSJfxxc018OaEMOShGxRypo9k9MOsHIyQ/1C8iM0+3lwCM/XWBsrdxIqG+JFlUwR8ZRlqeFTyGmsgFk4yrFaO7lKy31zyCeeyL6oBHmO0YVl+KF0dBDcymXjQNv+KO3sFQgaGqIBDxLHpgbsvX2ulY01+x1aTkoLYpJKLLZ8IFH+SUGKrfJneE/bobXF8fn4+FPw4ubXr93/P3w4kb/63Y0vJb/qMPOS62BP6DqiUORwyWhKZRzreS8UIR1EF+EZ9yNlKit0iRoPgTaQ17jPstCz7+XaCUP9lOtA50+6OcY9ZkU2azPkiIMFbw2dRzrzW4VuPXjqY46tDZ0QAWLF5UiyA3zpS9l8zrZyD0t7YGetxnGoqLVpDEAbZXOrPOj9DiT7x7ViTTZkcVP+azWffE2+bwqb9vqbqn1VGYA21PC225hLwmE3uv31H8JyrzX7yGweK/fkxDj8H/mfy008TrjVU0ZNRB34EEpZfDaCcMY7iQs+lCCZveTgj2xN4BiLnGGn9iXCtP810iDs1O7t03tDDw7Nfyy1PAb01ADtFO7b0rt3nxtGloQ7XKOXze3tUDaqfEXbxsbl2Dab2Lmz2Jw7nkqpx/2tU+BqmB1hnOkGzjM6M2/BVh3IMMa1OsAkafnSyGn2T5AKL/5+tWAvacFzlvQoKk1oi1DpDGANs/dOM1sfy4iC6RZfYPmoqQ4kruJeaC6DxVe9OboLS76zdE3hOoGr7vWGiGKMY56S+lQM89RbwklGvY66i2hRc2ApmUTNVo8etRbSo82Hx/1llJkhdfBYfFCkzukSZTy1zIUiwc/8k5ZE13c4PDc8ONk/eDXEXw9hKIf5H0DXeUpESnPxl7eZGRt9lm51xl0u8tdzoRGvwAWaFiMmzIln+fG2BCtS99+K+LElvcSXil1OxRigFO0HBpRVcQmZdhXOrWCNMlQLiFtmWeBZrbF7j3F0RiVvtaJQDPSdzOJHUL+9dp0Vp8D7HrlHJcSoS5l08WvcpwIpZShb4VqFPMsG2PycYfPj7H5Obam+SJB7ybEhvreOMRGhR7I7qwiDISh70wLxS/3EdQ7KuY8FT4KyiICKz5eIAjt9crKCdk7Pb45fnc8Go6vjkejny+vT/d0omG36VIJOkjNBSOHictTM0e4ceUEMrYBjwJnZQJYYFeV8RCjoiHxVQPn24uYLw4y7hcpP0i8LHuM02CvpvVLVwKt7DdNBZsFa1AfjmANkpnrS0b8vD1Yg9pIr302YD8Z4JZ4LnKV3IwbA0Dgn1Ewh9zWjYI51AlUgjmo640SKWzel9GZdjpFkwRYgadAkBBfVeWA4aaU8M7KcGf0EfCUi9kqLCOB/JazTpVnuvKLpNBS/MM9XxzQLBNPpObenXCiVA7phGQuRdmVkctYl8vwMqdk2EVstjrIteMm1KHdWoi9zYLblm07xDCSh/KJQ5i7z6LFLdwlZHmE3ollRRGHl9/1+r2zi5vh9fXt1Q19+Xv4DjAZOhs2wUkN57q+1P89HKlO5KjONLnRf50zMxqVO4dyQ5x5UG6RwkDBAYCYTXnqRRk5jRy2MDNpe82rmAfljbDRonlijVhb82Wk7h3AhDBLB6R5FqcBmhMrC1Ozkqs6icNiHgEWQ7dFdWN46lXyPGDRCS9rDRIvf37K/dDDiwSN0DfDf9z0GRDh98PrPrseHp/32bvzy3f4NEfMSgdDSF7QYW+j38P/AjzaVY7Qx3VscoSV/XXA9eNOmSi3uo+xCctCBc9Xu6QFMuiF7ROiJ5YhiaOD0fB8eHJj8cgrHTreYS3UsV5LzRavS3tZq6k6juWGykXKoeXxVPpfDu8jQdrKIljXoltW/87MN5c1E20RS1cNFvlZS8Bfw/bP44Cw3CYLdnYxGl7fHN5enR7fDA8pilIf9xiylcahmIsc/rchJsCpimJAkZQCjx7iEUTkkPXImZhijt5cuAK1rR00q22ekjyoqjBefgX83iHgHKQ3IkaTj/Y/D7J8gcEvmYJ9RMVuFocIQkt+osr4q/LKldW5UmQRn05EU3Ir/Gd11OpF8XtotoUnwzSN044QcDwDB3YHJxf0yWRzB1aW7MeaxUperALNAHU7iZQjccRkk+7RQQ5Z66wKxGFl9ZFpwZCzTDVoVhq4fAoNkL+jPBUJf0f1Ns6iu3i52k6fsIms0dEKD0DGZiwfVV8dairUmWrHzk6XZGJD1So+zopJS2IiRrZhQ9W/3X7JGLAr5KBHENI4aI9OTxBMqhQAghMtd1GhwFW7Oani3jUczUkdehk1fLTl/fRWGhJVGUINsOnpctAqPLjPylZHMCyVcoisMnKO+mQIUSp3rvLmK0VPwL1gnrBqdnY0HmwMGBmkweKIgMdksjSmNlHMgc98L/EmIoR4eXgXBjHPMLRGSgizoIkV2NGdoSok4eRisPeOTaXdFUy/Vm0iOUu0jOBrVhVqMyUXTWUwBQqBpec4VdOBTrAZ3OZpeTtzabPBmCN1/ktMyI/ePR/H0Xgja3bQWHG5Zkyu46LUZ6wqzDWWdxzAbEFyyrJUSRnmolaKztRBViXulnSde/c8g2BmH7Bn/OU28JUizgyzyigznknrB+2yiSxTRVuZLF9PlUV1tJmKM0OAAv4EeHoATxIK9TBEjYY98omC4O2z46uzDF8UMYKk4IC1YSTCoGccA1T/kdgNqzphIlAl937AhhhGT2uDYkVoXAkXxryiknqrYWIQ9ETV0MgaZJVyw4K6AmeLAmOfZALieZZj8+Qsfo3WiZkbsONyoJ/c3WwWFyFUE8sw1VLGZK0d8/WZx9cpwd9dmjYm9HvLb4t6WN96YXyXtdwEvRgg070SGe1tGNXXChu/G6ueXHbVnlY36TkusSWvr09xq/UcF9O601z7puoQnYnM4DBEVne5shoghErt7jWUP9izcxEVssilKEUXW3bmusiB9t0sXLeypWNAshCXrOydk1MwDwugmW/PTl+5+rYcnmDQifjUQ4VfjlA/G70kmhNdysr3heofKYbAv72jf/4GyKKQMYr7cZjki8M/7ajzD/DnqYx68KAo1j97tLOQGor/czCa8TC8IThZKAg5h0oFikHRIJhymTZBVAhxw3KQQd1SjtVPiwR3wmpZuTlzleVrzxbvTXlD6jE1MI/2Dqke1cOBZ1BwXACMmVGoF1B6Bb6QlU7tYVAhJ+3iwAfwjKg+y7SP3QDMRuKBY4UUjwORZQVgK51ixRcV1AOHS7a7/TeDwX+8/eprqDidZq+U94eKUR2PTs7OWMhzKp4eiKkAuB6oiAjt9sZ7iLrPHy14fXsPJQqCFyEBpfL1OPGyGcvgFKv50RqcwY8lSCmaGclsYymbVCIeuxUIUKiTAzD2ShZu9KIFOzu8/Naqq4zKKBXmzlRZTMnAlCsFu/EGqnbCIXr3nPG7O8gVxUPRaAlUmwNOpZwwZMMoWJk7bN9Tx5HykDhKpYzD0D/zySj276GKLeRFJqEX8Vd9Vk2lYfszsLXFKLdFZmgAfKMwLGhiGmgiBBcQEQ1ExinqfGWqiZJVj+YaZoew1bRO+7D0zkI6L5PY1VT+Fu15AYMquo3LoB0mEgepHfJA4/ZdJjw6vjojAHvPz7/VGrRU/SwutncTuU4Oz1LvESxqUKzkLkW8JRRratZnh5eSoTDX7UAmnKohcSVgavFVmqViNFR5s8Z1EbfFciWTOM6zPPUSBnfakfVvj/IY0MTFZcFZFUxi83gf44QwuT+iB4KcBgCFUN42ULxMh4KyW3BbSWgoxLyPsaJxpJK85UJnXoLr8HJrGUUyTSH2Ay5OXdtYnnedYvqycoMmNmSe9EG+ELQKcXNzzjKQt6EHNKgOzypqALKsJNgkwJf1K0iFATvWVAydyuA0BcWCTv8EdBmgbwUlBBUQyNtGEZW6BwzDCbEyUq12e2oXLwPBhTV1s4ztZ4U/I98xaVtSkzmkGQMTyWXKlAVVTsS1SiX0PZlpZEtI9cCjojv8QcRQfxplI7pR4MICFQLVj7Ogd6QyAmktV/liRD31pLlaqjX/VL60clJXvwcZFT2J2eBIZC9dMvZEqcSKUg/UpdHlgrjmFRKQR1+9L/G3MJ6CXljeI+BLgaxAlY8tJFFbG0EttEePIVeOWl/viU9Ff3Av8HJphgmq3TCPIshnIMPgwvR8JHUehhndpHB3VDCov3lNK5DGtgE7liB4eMUKaSEp33hgL5EmiuBbshg8CjDZTCOI0YB119bZnCxorVz6xdZa+YyL6SxfZelvv/zkS/8NKISubdQ5375+XX/OXv5ostzR1ArTpDfs4b8yMoabQZfnf+oEYqoE8KVrzPdeCJPl5XtAxhFOOOavhzyaAkhMgZFnqKHQc/Kr11/Ue7R5xscLFO4oiPBJvEdQDvZnVBBJ5nfxp5lXZPAx+rdhpg6N/FBK1x4mxWdrqeYjSEz3tJyWhdU7aOdEL5lspr7XAlEWPqxq2nTj021AmLos4yHJGsDmUZ9/S//E61ZC74qIVdKDiSJRJUpShNJA4kW6RsXSw/C8kD94Ua5kmRSgMvwD8oQoIfnIQew6rg1jMkHAovFwgj5jxVigZ79G62GRH8R3B6StUkSX0ka/hGYqju0RTIl/8ErZNnXPzb2MrlLSBO95CnaEgMOrOc3Y6Oz7n88uTn5Qn4A6PU2xIIu6IeGfCa6MEpGPGP0XMethlMvR2T/U34iy0Z5IkXBsb3T2/c3w+j2Az47Ovv/h9kr+39nFjfy/29H1m71XA3Yb3UeAsGv1lck1myWbweUizHqz0Hvge1njIjAY/F6EYTLdf/tK60N0Cc/AqBKCr3oBZJbLY6LoTNRrG/vtlw4Gdgq1lSMlUHOl+CiiBS7Bvf/h9mrZzrP9Cc/yA353F6f5t0o4ysDRxrnT8y5I46Q0tCm6Q6+UPfO28EHfBOIDXx8m8OVas2tRedDOowO0a2qPU6sBjvtstJqVFI/fqDHP8ndxsKDIn63dNRUcA5LjH//Kq2ANtFx8ZxE9f6R8Nud7VBZu+lEKzywSn31W1IUBhhDgP1Hzb+FI0xIv0S9ff9l+icL1eQfd6Ovx4c2hhP3ODsmOVzFWqV/L99/3nDT8stcE3orIbPKj+vV3LQWIBTdOFbauztD9gHHm+l1W8clQhas+JWshoIII+owemzkajDBgWQYcyMnWOXPK8xN6Ux7TDHofl7zkqJqqzEEkQhWGquol6ieXfRD27ke+qG/2iZ1PonZYvXBr/i48MAL0YwpJRn+1792PyU2mYcp5QL+k8AtKzuvh6PL2+mQ4vvnlajg+Pjm5vL24kRCCmODlkJI4xeOrsx/RFr4LoXKcCFcS38cXK/ZEAsfpP8nM19rZ4w+lk5e1HtUBR5iYVSeASjOkA9XSeQ5PeEHt5BSeYrum5SfZ/6c4sra9X5N0jfuMmTL4c+Memya2fFJYdfQtO7X/KZth4oVIqanyxtUPCBKFjtGgdYzTbFIjbPXB/Rz+ToQYtLegbD7SZJOU34knNoeYbVVeQcIaYkzAEEzrP1HiSXerBMbYd58KBVxtMB7V+m4fc4RtrDFNUc2NxlYxvP+854vfOi2bqGCyMPiB5ZwWXQEUffI67xmdslSVQNxhFe4cPsy2Of9/imiDNUggUjXhJM4yAdkpsiL4/uU1aNwEJ7gpiWG8bftUqSAQgMTpQjsY0AKaiLbugLrx9jVUz3yC//0KXqgrzKyTqQqdRyKaEjRm+6TPATs6x6fvU45JazB73GHVjWUShlWZ18tUgOV9Y26S8mlsp+C1zllW92D7Env3lRKFWPhTd6L9U6rKERtR2TcK4oJgpXtB7ktqT94yoPgJvmAgeQO3GbBZMHYrnXLcm0zmUHZdtQaP+9hWNRDiOmO0w9NCm7jOTtkdsl5JgwcuPFQseKiUyBXvN5M3+2TlJGjZ4rz2rrXfUGWyerY5u9xLKUkPhRqcKS0H6VndsDV9HbbLrrL6kTX2WoHXuuJW3v3DPyEE6tCUWV3tKKSglJiWFM3o149lxVORveIhoIGxOkBJhlsps93OhdKGGxUTCnZpN2uUJpDHbMrtvVjDRPHxScSqztuBrUsLVj6COmf/KQJXHEkzFcHzfNmrwGqD6TanVf005RCwx3RdIAotWEFnlQTSSBJGPWykCJydCQl51iRQel80GGOOn2pWmP4KD75bnSXeerTUTFpETfj+k3IeZASai5n9pkBfyO9yBlHQGAUxYIBniRE1mkrnEA8Qci+lrnXa2hLCmRQo9aXvRmaL18mFfvj0FLObx6+FFPDJH79diRPa/c+GdmCLnwCMkioTQ7RM/+pGzUQ1S6m53Gwvk4CLRHnqyPeA7KYiywHuiftxGkgyzDQd1imOJvvpKe7jkQHqxNe4P1ulBOWVqVFE7R47JPig1ZUiCTska/a0UkypPXiCqrRDUV99yvmmeEiNKwk9ckxobLDCPMXRkNbwqYjm5fn78vz97J+/yEO1N3BHYfTGMb1jDU5GhOK8oerySGIRr2Z5l+i3GKtB8L/xUqnk/Kain9VFjpzeTfzpr6kdOHxLkM+fwNNbAzHeqXpUJ77s9/BwAkb31chvqGBaGH6MUFdlLBfpLLVuPuAJKEPcSJ+qU224rXbpwISRBTcsaBjHyKP/OgcXoD2BMo1DO4UY81cj8Qr6z4dyvglM6iPTvAOaZ1vCV53wKtRPd+ma1G8gMDagd1M2RtGd+IMHZerfgKr/SyoLfzWqriFMPQfKrqFofCrKJiCJCT/MMmdUTSNxj9SXlkpRerVgdN9oNGyk6249yPQdTBqip1KdgvUqSPsYZfwZPHmorpyFvd/q7C0VCyiXlotl3SraFHjvMXrX0VNCPwj/N6XESEjEVIWngons7I4VEcIAYOLFwsQdTvhUYAb85s8orCf0qRR0xUX9nlULTpYoPFBoeOVZQIVreuUf0Na6AtxNnZ2PqfZPwoInqZD2h+qN80792uub/z84V8k1zuArjDjD2t4e09/UWXNEeYPUMr5TwO2qgqDMsuOp1QeatvQ/Fb7KVETqAbmXWLXB9liW84RCeUEZEyqaHFviuKZVDGQt808wjBxiwO8o0pam+HvBCz7os9sEvAOydjnEOef4oYR3tsfRY4xVwz0VvEvfi0zhIt0VYVMEkn0Eu7i3dP/vYE6fNBxMT6UtJsnsR4n40MClf3O4Z8mjG4ZWI6Z9rWDuUhEvdfvW2dybtvjxVgkwkm03ieih+lwdxyNCo2/YPtJw0Gc2m/SZItM+gmXB7/gXTbWvXuJRXgxyfzuDnBYTJYOc4948BBExJtwch17bJq+uCICrk9hKqm0pbAG0LxNcQpWLEOfBobzCRGlI08uLVHuRai9S7UWq1aTasti5NrlmVTDQ2vqS6Lmths1NSgK1FoKjp/5sA+iazmFZFF3bobhD6NwH1PV81g6gW/WEXkLpdhBKZ1EZ4lIQwGI3G8DlBMxlEjfUkQ5+RR2yU4AjSe9EyOlpT69jopIW64D6XN3idmsrKVV7Cn0bwmvAfokLFA8I71PGwbEmhKYBAPaA5PtFXKRSsSAsC4AWgiKQIQvwE5bHceiMgZCbh8/Ev9ErXZ6RkV5wiIDrSdXK0dztoDRUQLd0vUileEe3DJouq0NQoaclN5BTqFVU78/h8nEd1vbvoNZT3PlV5D7gVU5w55dTAyX+ze+shhjwlWzWGPLddhGptO4A0NfCjKC4FIqTBYlnbieT62gsvfRUbVd1NnEkQc4eWZar5uJnSRsuQ6/7fA9N9GyzXlI6URUb23aouk3bQaIvy7OkErhRAo5gp+A4jLwkm8UAGiHsYG7lY9E6yJ0gMJE5WC7wSSmi6RH7fnjDYMGBLEx1SHVlVa//Kes46uHHIviPP+1/fWiK3v07UpQritcx4Ltlh0n3h4LQVMnKdyIF0E518ahrR33Fzk6zZrQJM6YTa6JK8GE8bb9VV9HCQXqZ8UkWwQBuOQe3Ifxq+WyV+o1ir8RRZcrTqziH6f996K6sl8Pi3YYVxeUuQVZF2FriXrWK5jaAW0gMRKpVOJVWL8CrVFApAzZScBa6+p+EggshWQBy3d0OVYArZN+dD/+x7EMyyspPIL4g5OzRE/gEQ+Dq6C71CE8dsPnBcObLAho5wU2XXKnleaC31ssA54VLiLEoDjg5iOXqTQdodt2T0xR/YA80IbUvpkpvWkQZk7jk4B4ES/qB8klnPC8SRlueqXnGbE8i++/ZKIYU/H9HXTT5dvXJ7+LJSJ0/A5QPmkibT1fuQ/29YKo2NvpFNB3SkVr12i174KJRAwNZrrpoRqJayTuhWy+1BnfbtRH19+J9ePE+fObeB6Lp9huyogc7VKHqfXnF00xkOUSmOVQgFBTQp6XkoTbTdI1qyeJsv0TMyEZnp30m4QmM+gV/RPxHXdvQ8RykDk5Fdj+Sw8N8GiUT7dp4GayMnhYSnJJUm2LL1F8oS2IP6ztizcds98YzWx15xh78c0afaVzHCwLNy+30cjt1uJ2qondZ0LwUrc63fcOdtiRiaI37TZqGy9fc8mCiyrW4QUDRSN/XLzfVy031clO93FQvN9Unu6mW3j/LnO9r3EDmytBW8Y8Z8VW69GqPKrVLz9bl3umgljne1zg1tz/eeYI798OvdoQvAWE7cK43kWHHmJ2NxIZUSXcjPez4Ccuss1Egz2crVNYJ6tmabGk75o8V6lOmgBWO+OMG+pQE4osoIuJVQT/LnKjtBCpRA/Us4jsG2hRTB16nRAUgWHoymw4gf5zQ+thDBoUf/Rl5IyG3O7VGkkmrqixdI+ifrdxt4k9XfUiF/pPDJNDxqFld0widnZJ6NSpax+TPEiPvSL3ehRFoSXjTagStI5rK9Nnq6HBFQUHAdNTeiSvkaBuUaiI/MosLtyz3NAGp0J6dEFBHTEE9mQ4QSlhl3Z6sYQF9m0ktxpHl30qD0l+96aWPQCN0fpSYB3QTMS9bRP4sjSOoelkirOZgS1XgtGNHptqbXWniccax6ptdVVJEcNlMUygXBK44iPHnaRqnbM6zDLK8Zc1myhmskzsaE2zJcAyz+nQCujO9y61MeZ4K/rBbyVnaG4peWCZAnaxQJ1/+IBBSKzt8VBBhkmatQoSyfnE5wEh9yR69FIJ1MlP2RxZdagSR0egx/QaYmD4ocgkhZDaNS+PxB+AReVtIQ5YuNHQZmWrIM12Hmc8FKqpcInokPNUhTvh00ZWDEXVUz0MNLOOUTAdeRsUH74oU+UPvDHAJVB4ugPLZO1N7jlgleBBZnC6OjCs8y6HusYx+YkWUi5DBZAPuBaGAAspYio6K9S1Uiehp4aVelHMXe+GpUu9DtXEf+U1VHv1jIb7USb3jY7vtSU19fVQ7nJp+S2zD838vr/My7vr+dZzJzp+9KxzKi1ltl2/ZP7UT9MMhR6w9vMv+1P8/dqe0dK3BC/pTs86kRwGvR/OzQClhUPs8K/VnPxRCD3DxAaO1NoS8TbQiluVBXOSHpGepcrRQrJT5ccBB4VKYDA36FuoRJ1RQuLt/uZFAtYt5Tbg1+7SWDjbUm772eKGX5eNoicPPy3IWMbh18ax0uULa9UPzB6xErxyY2tl2xN68fr0WJMYncX0hReitPcXcrK280Ttx6CFUEu7yZm/i0x+xwn0jexqVTulWyLCB6r28LuqsI28Sa96VHkVqkMkCSySrzDXdi04aUGXjQ2pIfEz2rXLJ6BobQ/PymW2Tj9fFTVyJkXH6JXZ+RvY0oAE9tVWNas+IOePU2uFtcGrGo2Cc5cFYRJswLBa3lxo/SM9RHrAzBWOg+EeP7HhCwvdRHFH1GHgcUOVYWeJYlbhXK8cYDR5QTIaX554/g0WIiPEIdPKAbMuSntgkDhaUc0LEOGCjIklCqAju+SBG4PEX37kmEMXRQVSA95VqJrOfPUF6Jb4RYfvYhEOxZfx/WOYc/iGipMAuhpfffQvPVmWykXOi+jlwgqqgBFz8ZH3IZOiJfOwiHoQnwiLlZMKSwKE4hAOBlUfBKA/OXiTIdi3yalst6fGJwYtrEwMIj08iRPAFDxzI03RM/hnXU9D0AK99haFbliQ3mEVO9h3oTiZcSlsUxV4ZQYACYDQaGkTeOmVt3mGFwfCXUR4M01R6vbbAasa6H+h3x845zgxaks3rjBvf3WW8Q6jgZJGDrIXGBk/ZMuehg3dfIEqyQq/pU7PMgZWsXSL49eegoAPrEdmczIro/pPYzlZl67jIt8nW8MTaKluv12EDW18W+Qtbv7D1X5atIct8fBenY+PXXFft18pw81sdTSmgKtvxkzrXuAJqFoeSp92d7WXK2qZ8KdQ3z5QGT4yWAZXLphxRrCmyPy5yHYL+9iuWcYiTd1h9YYe+i1NpUTsh49vf8U3+WdnYdqX5/1yihmdvPGgxxEvmUSZ4YAzL6Ee+f3CfEoxFI2DLsmcEfPZN/bMbMedw8DbKhU+bqgzceFG5BFvHoLYGZBBVA7fRpO+ufivXuU79W+Aau+4t/LsvIyq6lshtCnxTa147omJNGbRTJrNq3T57GI5l5AYOIrnHEhVtqRNJoacBhdgOpaaoMu2X/FR08AmPxSUcDinYazz1cv7oLcZ5fM87GTQveP4Yp/cO3eY9pDWCXjMFs53smGHH0q7ZJE3oS9kUvdEhB4OfNoTKjfc98nLwp5ynYOY8vjpj+SyNiyllmimcndIU+hJ+FePAUo7FkbxQBctkYNgUiMXjp1yWhFGTx0OQYs76FIJz0vgJvlLWSUBnkFlkcl7gu4UAFYjQYfwpiTNCFso1IQ1+jX6NYDhauMjYBC4EaiQyG3VJyNwCz/d5kssgCW0ZxS2LUzEVEYhtfB2IfMAuYighOCXHTAwqXhxZ4x/h/+vBVcklNUnYbZ6q4eG+SvmByLIC7MiIVGRYAj/PirkrBMdGJvqeNvYGqe0vI5HtVa0Nj/R9iWHmkMG5o5g6e7rLNCFzvkAANT9inyn7gZw9wSbDX4mf2KOX1XSdtX2pluCa+8k2hJas56Zk1/uTq04Sq/TZcsElH3QRKxL5vIeB9t/HAQ/JC/SUs6s0zmM/Dl/JgECnZIPvZsVkVZkWxyHKjiyDo8TsY8Po0Ced2x6G98SPPBgDNnXW19EV9iB5xsO7F9lWlW3v/eQvJtfUitaWaYaddinP1DS3JcsMP3wEOZbki3FeRBHvFP/RLMh0rTzGkxmf89QL2dXNL4z6rjiWlwE+2p14BYT75gJB2O3ucp7OBWhhUrAgamJlBHYDzC4/khufyKD/CscqZpzyCDgM5EDKsxk0QukpCVw5qiU/w+zoHmDZLC5CcC6zxMsyDFYBiFHupQi1j5KABvzHgRSnBzc4s4PjIp/FqfgDqY+RAbKd2a/yBX37139P6KVuyF/bYJogfozC2AvGUG6hC8N8J0JndpPsh01E5KUIucaZ3C1pXtfGjJBniyznc0e+k+ql/DlGSi4YHBvbpwH6GCHRZzz3X1H3dlV6IJAyM7jSouDvakRY1l/nnjmtrW3Ldo3Yz7l2EpSmpeFR6JxW9EA0jrGSzVgPciqyJCaIWcdYGLUDxaBX6n3D6EX0jGCI0FZuqSF2BSrzKteS/uqnt6UPI4pah09r9w4206ZbxiM/XaB2eXt9fgBlpgPr3rJuIXB8gtEVtVpKv5HGBsDkxstHTuH2+pwR+TCRHbFZnifZ0eHhf/+3N2+/+BZwi+H/vvr2gP5AH43v+YL+PKA/DFK6iQaeAI15CDedtnZEbOY9cHTVyNaAIC+HlxoKaM9kZ0X9Be/DOUZBWz/kZtbSdYDLjhMesX2ZHD6Ls5ysrlHMJtat+aoujIgiaJ6f9hrcvjCi9Zzg7n4Co+o6V60O6k2ZFxJONKVulUilSPlWFFdyqHW6fZvcoOQGAY5W/gwKTGT3UfwYIa4p4KHOgbl9DA+u+ECMf7EiKdo7Vn8Ef78JgG4dTKDjTRdmhu7UK1vf3zk5apA7MQmBGmeobwpgKem4M4WjsG0U5/onT5mxRRQg5ajXvZxyNbgaX8D8qBzMqVlQUQZYAmKI/PwWcqukY5iP0VuMy6PvZDMHo9MHy1IlPoYf92/sUMXK5tp1+Ax8PlvKlmjwe0pqQ9bBk1oqi4igN5FIx1b8BMY5V+UIJOpaakIG/TSKHxNRpdk3qn1ditkA+xfFM2OeXikUqZ4iCSpL6vn3toTYy3QmupuPOyU9fXbXNc2GDBd/QRZZQvebkv3+qa6v12dJyL0MKYyVGesVGzVwBxGyInIrC7DlZp542ayNL/rMgwu4dPuZK99iE2wRFzlE9a92G9LV54qYcrPO6IVzPmKmfAPTtFSDLDPHPefJ2AOMgC6s0RCTcM0zGXEgAol0m1KIwbLXKqbmeGzvR84TdgzT2LPTcqq+IRTuIrOUdIwHnRU5GLuAp3AC0r2sJwN6JHdBG8LqcdS/S7xDFRunQWQ2lMGqkkOXAlhyX7rWvfLsB5p6HASNlj5TQGbT0lc7iZ1swNrFCOpSFKvEtsZ9WBPW+tNUE6nW3Nr4toYtOARZtgX6OxcPnEEsvzrdcycVYguMl6pH+Qe2bwbh4+R9msDzKYynkINLaXV49Ua5iApuMgF0+HkqC2RJn/ajTOGNzDcY2Y4/4jwoCpkudp2qB7IvBA9ynRVgz15YYUVW2Fn8/MZ8AKYp9NyMla9kAxfONQdEcgicWM95g987PvXoj5YuS47E25vvDr4hPZPtBZavYs/yPGoAG8IAx/HZ/hR1LGBBLwILw/t3r0ztR+CG8jTMcIx2v8kbBEuAGZyo3fyL6aZqfVtQSpHWk9ATlUnshMrn8cN23CXX2JXlLlF5P026p/rA8oxYPpP612Wqopk/B7P+p63zuuHxQ6zQZi+PYo6P6yLDcrJB05u6saWpvUCwhlgy0/eSHJ8XXqY+wJeElGlUqyVa2LgfSm+VYByUGhR4fA7wc3jdR5zeKwhAq1AGTGxayd6FIWUuqoNV/F1eK5Xo7E7+HX28K7lvGnA/j1W8nI64p5rF/kLDPcgIPCQuAVqbxm5rJnv1YN2A8Efqzdvhjd3Y1NJqEw41OSgGJcvjxJZ94H5ZxAV79Ah/IwNHq4Ia1Zlxp9I/BC3lJQ94tsBxCl5SYVnMihzFLT3aU9VcM5oVTxhlOege4J+wvyLnrmmHLrwIYCsONMSoBloln662EhgQ6H9x3wB9sC9f/0+IL70LhQ95NyH0cRenPv8PrIwCTiXlxXIAY8jePxlrGu0b59yp2kuf1me2BhRcACgyqLYewo0Ya7mquZPpoi+DVk3400sMB1tbROMkGHrL6aLysD1Eq15OEbl0Fdxm3CYQCBJ44GkqgjbWl2OM4bLpwv/tsL9lW7OvalA7IKS7hjO6vg0qF6Xx5mIqml1WE3aBIh4khBW+gQtEYz2VO2F1BJ+ba7cxjHBLcOzPUW//G4CxN3ChdSLlSAxNfLbg6cZR3Z2bS/gKIQaW4F53MnB37WgFHpOGpBr+Nt1s2p6kSs97/j0Aw0WBEmsg0+ZxJEi1MQoo1eriUZDEInIEONSZcWP87b8kR5oDUUr+35Y3SaPbRM+VOmGV0xw44e6GRPTuy83cVajAVx5iIdqh6neVfgMq3RmfHaYoyTxOF1I1zgpIZcrqb0OHAknzf3naLQ3dW/lh1wAXQIFNdvyadfjwzKETaWYSZTjFagXwjoG8F6MsytcOJZkRcWqHVjPLFMk2YvZvk+aI/TzuYvKVPawWrY8+W/VqNFwCKb7wfCpEjtxWM/riP0o2X2RbfncnfEGPUtqXTH4BEUZUpBSuV4gsZm5YA9pcWsxzTATQiziERRzAIrozESyIlvYJAhC2bgwsoATIlrzOKaeqwww7XZKg6vhgDliZfg0/XpG28kecXN32pcSX5RbgfvHjKCvm2DskT/pFKFO0SiblPcgtlXden/EnBdgNDGZslAlPRRzQRE6ubqUeCAMOqGgWzdfUagUPCUCK8tBLMhlvyzIB5VbBloWNMzlFHtCLH7M4LcNZIyDEtdylWzyqv8klVVr09hzdayBlNehIBijLur0ojdpCrjIPCoVd1YaRpWl0Y0ysJgAsHa4Li39mUnlLqE60uE8N6rQmgNO21KyqkqXw2Rr5IhU535rn+2fozeX67qYF0ee2f7nUg/R/e7krLZEUoA66DtmnLcVPv8HZvMhy5StvUm5wjn9l17Ze4DMOuFzjOpAwIAcUzOgk8moTZxY7kxgllIbVYtmN+GO5rQLnWaCGbsFIGCyNEjoQGZ5kMp26T+i+yIGqc0O4oKfM4kcLCyOzQj8IK6gRI6I0yd5OcWhojE2BaOR27tQUWxpqmUx/5wUGBOACMxXV0ejURzbngC0nsjlohIJuASnL39S7vI08mfkPYHTq0oDifSJDE6IFfkBQe9DRF/WOvovTiQgCHg3Ysaz+FsScnviYwylNob6XSCUf+/rKfXfJhFMJA4O5S4OeC1+tlZewZlRphx3aERX4CsNKQ1PLroT+ICvL2UUR6ZY50BCJciosFPec7XkRMF4i/D3knj3IOPXEnjvotTyFRomP/2l1BX5H4XYTCSu4D29RQK6ZQzE4kwXwaq1AvL7j6lk2Dbp6Nhgq417qz9qHG2EbGK4CxgQBiSluxYazQNyg5dDG2AwUaZHzeWbS7LSjFeFkX2tk2a9ev369yswgI3vK017fZO+LKP/ibXnHwEwtoukYS0MsSRsEqxCiToJOlcB7Mr5D4wtT3cgKExiCn/O5MaFSSO3GBywfu+M8zr1wjFzWyd2tUxdf9a0Hs+lEeUyU2B+wEQFOo58bTYL3IpEhudCeHCbA/hOew5ITnuI2w2s7jqTeB3uj0g+6rnoSxyH3os6FRu3StKbKqKpfVxds27+RalHgLS8NbWw5O5Xhvn+Za8et22mM2VXuJEyOWKbcUSNtoCrrI5MFVgQtIvE7+ANREbsTLrieKc+rStfaT4jacMpOUFvKtsvturnAOfSOVbJnSMjO90mZXsov+pUVrpVeLxo2eQmBq3ZWlG75C3YchjZscoxfemETxPFHpnO4NmjgZ/GiLs1tbVzk8gppec/u2aNYzBzw871lds2cLdfSIVWsX9n8cIqfLWdg1a6ZgTEK1dMJTiJNASszExhDW7d5QXefgIlp4GfBxHXX26osS4t5dix7onGaRIS59ZMFEobmG/7Cw3M/aTMZ2j+70HsNbOV7P2ni2UpbtBzq5oTxgymJsnbiKki9K9oQH2fCn1E2BN7uEnK3yXKoJ7kjq6Huf32YV72NO7UW6mFeLIUrWwqXsRDaAfUGOwyFdjuwF5q27bZCt6nPfL1NM1/K78TT38bKZ8EUv1j4Xix8fzkLX1kabfcGebHsNahfTVa9ZfcH2uy66F8V455RHVYy7Nk60fbfSaVVfDyDXmnYHWpMz5Bune8MQxtbNuItI2Zjn1tOzw5bnvloVTveRyLrZ2a/0/Na23ZnVrZTu916D5DP3ma3JT5suGxabHXL+NSY4ZbzqcNkZ/HpGua6j8irn7mZzqxlpya69bjzr2ee2y67RgROc5DEIeStOO1ztTbumD6Jc3MFrRZLYvpKbV0ZpJzy29Mi5BKWP6G2kAyKIGQmJg8BSsKCJ6mIADpBHSwag3SWGwFeONLYyKhVmtGOTHGlMdY2x5X3bqcmudJQK5vlYPvtc5TIDH9xU1w7t6CBzd5W4Upq0Ga4Sst1bHGVLl7i7ta2yMmTVZLoxSr3YpX7y1nlHKJp+zfJi3muTQVrstG1XyxkeFuig1Wsc2VNYiULXVVd2v4TqbaUj2epqw29Y2XqGZKy8+lRppctW+3ayVvH3i2hcIe9rvxFk82OHbCoMD8lgL+ePnDTzQNI4ayvkw1Vy5QnoedDAlwuUk7Kt8v495E55pkZAUtzW9sQWF7hTo2B6z99PnuD4BbZvO2Ka7EMtssCHca3RBY4bIIVWbCGXfATsPFnbh8sr2enNsL1mVbO7i9kJ9w+F8cTQB91mgjVTw01qy/x5yU2QWpET0BJDQ4MOd2KEFG9DGqvgA1EMRS9GV0mPvp0R7Y96nxto55cltOaV8KSt3dJ78y2+Ic6X+u2UycWp5/Fxbeplc9J8Gi2oz1sM+rJFu3GPDrhxJuKSALzNRVxhKe77LLxPnoxMe3MxNTZbqqwjCTp7NyOKjlyjC3WnKDiauij03TapIsE+8BqJZUDz9eeoUbh3WhqxD8jnMj6BmKa1vYMwy/WyxWtl5bs3eJ9WDNXfv73WVmjO4T9G1OhcIfhsfmuu6La4suvvKTUcLW7jQZ5ueFebriXG+7lhvub33BJTeK+XHQrXHTqxXx4zxeucqHNV52ukaN4QJePgK5cpbroze7po4N70PRS+rxeAhwokdydWFjUHHfNE0dH91529yOs6+OizNlDm2q+rTt/+Oc9X3w4JJfKSsdAn1RBbh1H0vVEZId4AGgmrvZtd8o8P42zDD/sfiY/0TKbNJd7vlhqLC5NIo8ZJJ2arXjWiLTlXejq9i8t2ML3rNFVg7PcSUEyi6XJGnktpa19+tLctpJvXNsZt+8bkNMhtRUnu4uzp0HM5bMTW73D1vgMrxunaV1u0BZt6uQQU1j73S3s8gNFF4SsXqfs9156byh/L5PIrVDnQY3ZZ3nqRZmAL2BfAZ8Z0L9vr84vj0/PLr4HorseHp8exFG4aCx+JHv7KCygBvtsPWNyKfIwTOnwbtZ/7f5WR/HClg62dJbBkC1lkQ4RWVQud3M7DN3s4Xays3FYN11RDpe2uaHOTls92gROQfXAsYysRVDwEyg1cMnZXiWXB/yj8PZn7vOWC3E6u0sMrVpcljXYF3be3S2rint31xojYALOTuWH7Pb6HF8JTVyqv/Awjowc1EH1a8Pm6NiGPyuvNu2/8g+arJigSOUB1NVOGlLN8TbdqLhzBw5VI9YWthrTGluQWtw4434cBVm7IehUtgbpLT/QG2Y2HaaEmynyhbYZHbEvvn79eiXjo9us+/WXXVXqEumondupYm0Rwkr+fHM1isjIL6qam6rsnc/Cxb9roQIFsVMB5XjHyppUveWrTdyJc6YVgto7jRctjUkb1C9WHTCf+XECyXNQ8xWdBVHgpearfe9+/Arhkuw+8ZcUfsFer4ejy9vrk+H45per4fj45OTy9uKGYb0L6r8p1uZad/kj31U6XWmM9SNvtmbpcMwn6FzJhA7LIq+M+yl3R1upn9zkNMJflwRbUSNVR1rX60SbEt1HugVdSTzy00WCASqYSyljs7yAW0ZNLGnNePQg0jiag1vlwUsF/iQiXUx3QChtILypoHVWUmhxMjAxacNXATExlAOf8Cw/4Hd3cZqziZeJ7Fvw4PhFmsJoRHsZm3toNAoKn7OgoPNsTv2kle6ISKnztalTnsLyuDDrQKF8DSjydJjbom4aYKW7RM6JqEvW5MRjzvoWKhum6cIUPRFlTIQhn3oh82de6vnP+4bZNIjMycdo5qada/OoyxZLMkLVEUhbMSQ80IsXXWakYmbcL1LQj1LuZXHkYBOwl8vxPmP/+zbcb+pc2E9mQ+O5yPPKXm6Z6/66jjnJAociaLLkO5mEEtkkcetM4EZzfqkhlaK2+MLYgRxc4bTwU3/vFmfBJo8s8KLf2cJ7t2b9loQ3qTIQVcN2fASaXlaHSl0erjpUFappsbM5qUcnkLQTkMPYViWkldNHdkc5n7nFTC6ku8VMfrCvNB5iZZtgX+2SYp+hBN6Yjcij0JmNdE5mOxvJZnB2dEZYkrmZpzg+UKhp4+ujKctyd/z1jJIpaVJrZ1HKNTnTJ5/9m+Kzz67swpB/AqmupQx1VIMwdI+d3bEMbiwVmUlVPaf0iMYoPyiBzkMONo3t6U2dIGgaeRNfky9602p6Uz3dvotE30SW1yhMPVgrVh1knxwUqQiR3DG6ABpAtWYTyWzRpJezMI7viwQ2XAkxMEbRSxIo9TIMeEo0LR9pRQRw8FP48bhuMUKD0YT78ZzWFtJg2MGWrqRdEP3LlfRyJe3oStr0PU832IYPsxWfZOuKHGi90PecTFIQmX5pgGxplkj6O7oujRDCuYo4MmY3L4/nwldBpFI8HUFJbk+ERUpiKOQwvQxEkRJgciLNoisr0gfxwDd4ke5CPL28SF9epLuSNh/6SpJoSn2nIEB7VlCB/uPBubjj/sIPufvnywkMIid/A8V77Hbkuqr/5eD/H+iOdxyqsdsIlYOezoBLKzGu0Q0A5eziktS8ZJxOCM5IzcnBkIQWhKhiBZngKWpkoYr8PBSBVNhN8iii6FeuIUkhn/CMvKEM/XgXJDnYEhwkIfRZdJTdS5BaBGWvFeheJ3BpElsLAG/z7d6utAIA"
SPEC_TMPFILE="$(mktemp)"
trap 'rm -f "$SPEC_TMPFILE"' EXIT
echo "$EMBEDDED_SPEC" | base64 --decode | gunzip > "$SPEC_TMPFILE"
diff --git a/src/runloop_api_client/_client.py b/src/runloop_api_client/_client.py
index bf32e59d6..0daef9583 100644
--- a/src/runloop_api_client/_client.py
+++ b/src/runloop_api_client/_client.py
@@ -39,39 +39,29 @@
from .resources import (
pty,
axons,
- agents,
apikeys,
objects,
secrets,
accounts,
devboxes,
- scenarios,
- benchmarks,
blueprints,
mcp_configs,
- benchmark_jobs,
- benchmark_runs,
gateway_configs,
restricted_keys,
network_policies,
)
from .resources.pty import PtyResource, AsyncPtyResource
- from .resources.agents import AgentsResource, AsyncAgentsResource
from .resources.apikeys import ApikeysResource, AsyncApikeysResource
from .resources.objects import ObjectsResource, AsyncObjectsResource
from .resources.secrets import SecretsResource, AsyncSecretsResource
from .resources.accounts import AccountsResource, AsyncAccountsResource
- from .resources.benchmarks import BenchmarksResource, AsyncBenchmarksResource
from .resources.blueprints import BlueprintsResource, AsyncBlueprintsResource
from .resources.axons.axons import AxonsResource, AsyncAxonsResource
from .resources.mcp_configs import McpConfigsResource, AsyncMcpConfigsResource
- from .resources.benchmark_jobs import BenchmarkJobsResource, AsyncBenchmarkJobsResource
- from .resources.benchmark_runs import BenchmarkRunsResource, AsyncBenchmarkRunsResource
from .resources.gateway_configs import GatewayConfigsResource, AsyncGatewayConfigsResource
from .resources.restricted_keys import RestrictedKeysResource, AsyncRestrictedKeysResource
from .resources.network_policies import NetworkPoliciesResource, AsyncNetworkPoliciesResource
from .resources.devboxes.devboxes import DevboxesResource, AsyncDevboxesResource
- from .resources.scenarios.scenarios import ScenariosResource, AsyncScenariosResource
__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Runloop", "AsyncRunloop", "Client", "AsyncClient"]
@@ -161,30 +151,6 @@ def accounts(self) -> AccountsResource:
return AccountsResource(self)
- @cached_property
- def benchmarks(self) -> BenchmarksResource:
- from .resources.benchmarks import BenchmarksResource
-
- return BenchmarksResource(self)
-
- @cached_property
- def benchmark_runs(self) -> BenchmarkRunsResource:
- from .resources.benchmark_runs import BenchmarkRunsResource
-
- return BenchmarkRunsResource(self)
-
- @cached_property
- def benchmark_jobs(self) -> BenchmarkJobsResource:
- from .resources.benchmark_jobs import BenchmarkJobsResource
-
- return BenchmarkJobsResource(self)
-
- @cached_property
- def agents(self) -> AgentsResource:
- from .resources.agents import AgentsResource
-
- return AgentsResource(self)
-
@cached_property
def axons(self) -> AxonsResource:
from .resources.axons import AxonsResource
@@ -209,12 +175,6 @@ def pty(self) -> PtyResource:
return PtyResource(self)
- @cached_property
- def scenarios(self) -> ScenariosResource:
- from .resources.scenarios import ScenariosResource
-
- return ScenariosResource(self)
-
@cached_property
def objects(self) -> ObjectsResource:
from .resources.objects import ObjectsResource
@@ -473,30 +433,6 @@ def accounts(self) -> AsyncAccountsResource:
return AsyncAccountsResource(self)
- @cached_property
- def benchmarks(self) -> AsyncBenchmarksResource:
- from .resources.benchmarks import AsyncBenchmarksResource
-
- return AsyncBenchmarksResource(self)
-
- @cached_property
- def benchmark_runs(self) -> AsyncBenchmarkRunsResource:
- from .resources.benchmark_runs import AsyncBenchmarkRunsResource
-
- return AsyncBenchmarkRunsResource(self)
-
- @cached_property
- def benchmark_jobs(self) -> AsyncBenchmarkJobsResource:
- from .resources.benchmark_jobs import AsyncBenchmarkJobsResource
-
- return AsyncBenchmarkJobsResource(self)
-
- @cached_property
- def agents(self) -> AsyncAgentsResource:
- from .resources.agents import AsyncAgentsResource
-
- return AsyncAgentsResource(self)
-
@cached_property
def axons(self) -> AsyncAxonsResource:
from .resources.axons import AsyncAxonsResource
@@ -521,12 +457,6 @@ def pty(self) -> AsyncPtyResource:
return AsyncPtyResource(self)
- @cached_property
- def scenarios(self) -> AsyncScenariosResource:
- from .resources.scenarios import AsyncScenariosResource
-
- return AsyncScenariosResource(self)
-
@cached_property
def objects(self) -> AsyncObjectsResource:
from .resources.objects import AsyncObjectsResource
@@ -712,30 +642,6 @@ def accounts(self) -> accounts.AccountsResourceWithRawResponse:
return AccountsResourceWithRawResponse(self._client.accounts)
- @cached_property
- def benchmarks(self) -> benchmarks.BenchmarksResourceWithRawResponse:
- from .resources.benchmarks import BenchmarksResourceWithRawResponse
-
- return BenchmarksResourceWithRawResponse(self._client.benchmarks)
-
- @cached_property
- def benchmark_runs(self) -> benchmark_runs.BenchmarkRunsResourceWithRawResponse:
- from .resources.benchmark_runs import BenchmarkRunsResourceWithRawResponse
-
- return BenchmarkRunsResourceWithRawResponse(self._client.benchmark_runs)
-
- @cached_property
- def benchmark_jobs(self) -> benchmark_jobs.BenchmarkJobsResourceWithRawResponse:
- from .resources.benchmark_jobs import BenchmarkJobsResourceWithRawResponse
-
- return BenchmarkJobsResourceWithRawResponse(self._client.benchmark_jobs)
-
- @cached_property
- def agents(self) -> agents.AgentsResourceWithRawResponse:
- from .resources.agents import AgentsResourceWithRawResponse
-
- return AgentsResourceWithRawResponse(self._client.agents)
-
@cached_property
def axons(self) -> axons.AxonsResourceWithRawResponse:
from .resources.axons import AxonsResourceWithRawResponse
@@ -760,12 +666,6 @@ def pty(self) -> pty.PtyResourceWithRawResponse:
return PtyResourceWithRawResponse(self._client.pty)
- @cached_property
- def scenarios(self) -> scenarios.ScenariosResourceWithRawResponse:
- from .resources.scenarios import ScenariosResourceWithRawResponse
-
- return ScenariosResourceWithRawResponse(self._client.scenarios)
-
@cached_property
def objects(self) -> objects.ObjectsResourceWithRawResponse:
from .resources.objects import ObjectsResourceWithRawResponse
@@ -821,30 +721,6 @@ def accounts(self) -> accounts.AsyncAccountsResourceWithRawResponse:
return AsyncAccountsResourceWithRawResponse(self._client.accounts)
- @cached_property
- def benchmarks(self) -> benchmarks.AsyncBenchmarksResourceWithRawResponse:
- from .resources.benchmarks import AsyncBenchmarksResourceWithRawResponse
-
- return AsyncBenchmarksResourceWithRawResponse(self._client.benchmarks)
-
- @cached_property
- def benchmark_runs(self) -> benchmark_runs.AsyncBenchmarkRunsResourceWithRawResponse:
- from .resources.benchmark_runs import AsyncBenchmarkRunsResourceWithRawResponse
-
- return AsyncBenchmarkRunsResourceWithRawResponse(self._client.benchmark_runs)
-
- @cached_property
- def benchmark_jobs(self) -> benchmark_jobs.AsyncBenchmarkJobsResourceWithRawResponse:
- from .resources.benchmark_jobs import AsyncBenchmarkJobsResourceWithRawResponse
-
- return AsyncBenchmarkJobsResourceWithRawResponse(self._client.benchmark_jobs)
-
- @cached_property
- def agents(self) -> agents.AsyncAgentsResourceWithRawResponse:
- from .resources.agents import AsyncAgentsResourceWithRawResponse
-
- return AsyncAgentsResourceWithRawResponse(self._client.agents)
-
@cached_property
def axons(self) -> axons.AsyncAxonsResourceWithRawResponse:
from .resources.axons import AsyncAxonsResourceWithRawResponse
@@ -869,12 +745,6 @@ def pty(self) -> pty.AsyncPtyResourceWithRawResponse:
return AsyncPtyResourceWithRawResponse(self._client.pty)
- @cached_property
- def scenarios(self) -> scenarios.AsyncScenariosResourceWithRawResponse:
- from .resources.scenarios import AsyncScenariosResourceWithRawResponse
-
- return AsyncScenariosResourceWithRawResponse(self._client.scenarios)
-
@cached_property
def objects(self) -> objects.AsyncObjectsResourceWithRawResponse:
from .resources.objects import AsyncObjectsResourceWithRawResponse
@@ -930,30 +800,6 @@ def accounts(self) -> accounts.AccountsResourceWithStreamingResponse:
return AccountsResourceWithStreamingResponse(self._client.accounts)
- @cached_property
- def benchmarks(self) -> benchmarks.BenchmarksResourceWithStreamingResponse:
- from .resources.benchmarks import BenchmarksResourceWithStreamingResponse
-
- return BenchmarksResourceWithStreamingResponse(self._client.benchmarks)
-
- @cached_property
- def benchmark_runs(self) -> benchmark_runs.BenchmarkRunsResourceWithStreamingResponse:
- from .resources.benchmark_runs import BenchmarkRunsResourceWithStreamingResponse
-
- return BenchmarkRunsResourceWithStreamingResponse(self._client.benchmark_runs)
-
- @cached_property
- def benchmark_jobs(self) -> benchmark_jobs.BenchmarkJobsResourceWithStreamingResponse:
- from .resources.benchmark_jobs import BenchmarkJobsResourceWithStreamingResponse
-
- return BenchmarkJobsResourceWithStreamingResponse(self._client.benchmark_jobs)
-
- @cached_property
- def agents(self) -> agents.AgentsResourceWithStreamingResponse:
- from .resources.agents import AgentsResourceWithStreamingResponse
-
- return AgentsResourceWithStreamingResponse(self._client.agents)
-
@cached_property
def axons(self) -> axons.AxonsResourceWithStreamingResponse:
from .resources.axons import AxonsResourceWithStreamingResponse
@@ -978,12 +824,6 @@ def pty(self) -> pty.PtyResourceWithStreamingResponse:
return PtyResourceWithStreamingResponse(self._client.pty)
- @cached_property
- def scenarios(self) -> scenarios.ScenariosResourceWithStreamingResponse:
- from .resources.scenarios import ScenariosResourceWithStreamingResponse
-
- return ScenariosResourceWithStreamingResponse(self._client.scenarios)
-
@cached_property
def objects(self) -> objects.ObjectsResourceWithStreamingResponse:
from .resources.objects import ObjectsResourceWithStreamingResponse
@@ -1039,30 +879,6 @@ def accounts(self) -> accounts.AsyncAccountsResourceWithStreamingResponse:
return AsyncAccountsResourceWithStreamingResponse(self._client.accounts)
- @cached_property
- def benchmarks(self) -> benchmarks.AsyncBenchmarksResourceWithStreamingResponse:
- from .resources.benchmarks import AsyncBenchmarksResourceWithStreamingResponse
-
- return AsyncBenchmarksResourceWithStreamingResponse(self._client.benchmarks)
-
- @cached_property
- def benchmark_runs(self) -> benchmark_runs.AsyncBenchmarkRunsResourceWithStreamingResponse:
- from .resources.benchmark_runs import AsyncBenchmarkRunsResourceWithStreamingResponse
-
- return AsyncBenchmarkRunsResourceWithStreamingResponse(self._client.benchmark_runs)
-
- @cached_property
- def benchmark_jobs(self) -> benchmark_jobs.AsyncBenchmarkJobsResourceWithStreamingResponse:
- from .resources.benchmark_jobs import AsyncBenchmarkJobsResourceWithStreamingResponse
-
- return AsyncBenchmarkJobsResourceWithStreamingResponse(self._client.benchmark_jobs)
-
- @cached_property
- def agents(self) -> agents.AsyncAgentsResourceWithStreamingResponse:
- from .resources.agents import AsyncAgentsResourceWithStreamingResponse
-
- return AsyncAgentsResourceWithStreamingResponse(self._client.agents)
-
@cached_property
def axons(self) -> axons.AsyncAxonsResourceWithStreamingResponse:
from .resources.axons import AsyncAxonsResourceWithStreamingResponse
@@ -1087,12 +903,6 @@ def pty(self) -> pty.AsyncPtyResourceWithStreamingResponse:
return AsyncPtyResourceWithStreamingResponse(self._client.pty)
- @cached_property
- def scenarios(self) -> scenarios.AsyncScenariosResourceWithStreamingResponse:
- from .resources.scenarios import AsyncScenariosResourceWithStreamingResponse
-
- return AsyncScenariosResourceWithStreamingResponse(self._client.scenarios)
-
@cached_property
def objects(self) -> objects.AsyncObjectsResourceWithStreamingResponse:
from .resources.objects import AsyncObjectsResourceWithStreamingResponse
diff --git a/src/runloop_api_client/_version.py b/src/runloop_api_client/_version.py
index 6e58ef0b7..c163228be 100644
--- a/src/runloop_api_client/_version.py
+++ b/src/runloop_api_client/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "runloop_api_client"
-__version__ = "1.31.0" # x-release-please-version
+__version__ = "3.0.0" # x-release-please-version
diff --git a/src/runloop_api_client/pagination.py b/src/runloop_api_client/pagination.py
index e9804970c..5d9aa5ef8 100644
--- a/src/runloop_api_client/pagination.py
+++ b/src/runloop_api_client/pagination.py
@@ -12,20 +12,8 @@
"AsyncDevboxesCursorIDPage",
"SyncDiskSnapshotsCursorIDPage",
"AsyncDiskSnapshotsCursorIDPage",
- "SyncBenchmarksCursorIDPage",
- "AsyncBenchmarksCursorIDPage",
- "SyncAgentsCursorIDPage",
- "AsyncAgentsCursorIDPage",
"SyncAxonsCursorIDPage",
"AsyncAxonsCursorIDPage",
- "SyncBenchmarkRunsCursorIDPage",
- "AsyncBenchmarkRunsCursorIDPage",
- "SyncScenariosCursorIDPage",
- "AsyncScenariosCursorIDPage",
- "SyncScenarioRunsCursorIDPage",
- "AsyncScenarioRunsCursorIDPage",
- "SyncScenarioScorersCursorIDPage",
- "AsyncScenarioScorersCursorIDPage",
"SyncObjectsCursorIDPage",
"AsyncObjectsCursorIDPage",
"SyncNetworkPoliciesCursorIDPage",
@@ -54,41 +42,11 @@ class DiskSnapshotsCursorIDPageItem(Protocol):
id: str
-@runtime_checkable
-class BenchmarksCursorIDPageItem(Protocol):
- id: str
-
-
-@runtime_checkable
-class AgentsCursorIDPageItem(Protocol):
- id: str
-
-
@runtime_checkable
class AxonsCursorIDPageItem(Protocol):
id: str
-@runtime_checkable
-class BenchmarkRunsCursorIDPageItem(Protocol):
- id: str
-
-
-@runtime_checkable
-class ScenariosCursorIDPageItem(Protocol):
- id: str
-
-
-@runtime_checkable
-class ScenarioRunsCursorIDPageItem(Protocol):
- id: str
-
-
-@runtime_checkable
-class ScenarioScorersCursorIDPageItem(Protocol):
- id: str
-
-
@runtime_checkable
class ObjectsCursorIDPageItem(Protocol):
id: str
@@ -313,142 +271,6 @@ def next_page_info(self) -> Optional[PageInfo]:
return PageInfo(params={"starting_after": item.id})
-class SyncBenchmarksCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
- benchmarks: List[_T]
- has_more: Optional[bool] = None
- total_count: Optional[int] = None
-
- @override
- def _get_page_items(self) -> List[_T]:
- benchmarks = self.benchmarks
- if not benchmarks:
- return []
- return benchmarks
-
- @override
- def has_next_page(self) -> bool:
- has_more = self.has_more
- if has_more is not None and has_more is False:
- return False
-
- return super().has_next_page()
-
- @override
- def next_page_info(self) -> Optional[PageInfo]:
- benchmarks = self.benchmarks
- if not benchmarks:
- return None
-
- item = cast(Any, benchmarks[-1])
- if not isinstance(item, BenchmarksCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
- # TODO emit warning log
- return None
-
- return PageInfo(params={"starting_after": item.id})
-
-
-class AsyncBenchmarksCursorIDPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
- benchmarks: List[_T]
- has_more: Optional[bool] = None
- total_count: Optional[int] = None
-
- @override
- def _get_page_items(self) -> List[_T]:
- benchmarks = self.benchmarks
- if not benchmarks:
- return []
- return benchmarks
-
- @override
- def has_next_page(self) -> bool:
- has_more = self.has_more
- if has_more is not None and has_more is False:
- return False
-
- return super().has_next_page()
-
- @override
- def next_page_info(self) -> Optional[PageInfo]:
- benchmarks = self.benchmarks
- if not benchmarks:
- return None
-
- item = cast(Any, benchmarks[-1])
- if not isinstance(item, BenchmarksCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
- # TODO emit warning log
- return None
-
- return PageInfo(params={"starting_after": item.id})
-
-
-class SyncAgentsCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
- agents: List[_T]
- has_more: Optional[bool] = None
- total_count: Optional[int] = None
-
- @override
- def _get_page_items(self) -> List[_T]:
- agents = self.agents
- if not agents:
- return []
- return agents
-
- @override
- def has_next_page(self) -> bool:
- has_more = self.has_more
- if has_more is not None and has_more is False:
- return False
-
- return super().has_next_page()
-
- @override
- def next_page_info(self) -> Optional[PageInfo]:
- agents = self.agents
- if not agents:
- return None
-
- item = cast(Any, agents[-1])
- if not isinstance(item, AgentsCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
- # TODO emit warning log
- return None
-
- return PageInfo(params={"starting_after": item.id})
-
-
-class AsyncAgentsCursorIDPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
- agents: List[_T]
- has_more: Optional[bool] = None
- total_count: Optional[int] = None
-
- @override
- def _get_page_items(self) -> List[_T]:
- agents = self.agents
- if not agents:
- return []
- return agents
-
- @override
- def has_next_page(self) -> bool:
- has_more = self.has_more
- if has_more is not None and has_more is False:
- return False
-
- return super().has_next_page()
-
- @override
- def next_page_info(self) -> Optional[PageInfo]:
- agents = self.agents
- if not agents:
- return None
-
- item = cast(Any, agents[-1])
- if not isinstance(item, AgentsCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
- # TODO emit warning log
- return None
-
- return PageInfo(params={"starting_after": item.id})
-
-
class SyncAxonsCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
axons: List[_T]
has_more: Optional[bool] = None
@@ -517,278 +339,6 @@ def next_page_info(self) -> Optional[PageInfo]:
return PageInfo(params={"starting_after": item.id})
-class SyncBenchmarkRunsCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
- runs: List[_T]
- has_more: Optional[bool] = None
- total_count: Optional[int] = None
-
- @override
- def _get_page_items(self) -> List[_T]:
- runs = self.runs
- if not runs:
- return []
- return runs
-
- @override
- def has_next_page(self) -> bool:
- has_more = self.has_more
- if has_more is not None and has_more is False:
- return False
-
- return super().has_next_page()
-
- @override
- def next_page_info(self) -> Optional[PageInfo]:
- runs = self.runs
- if not runs:
- return None
-
- item = cast(Any, runs[-1])
- if not isinstance(item, BenchmarkRunsCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
- # TODO emit warning log
- return None
-
- return PageInfo(params={"starting_after": item.id})
-
-
-class AsyncBenchmarkRunsCursorIDPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
- runs: List[_T]
- has_more: Optional[bool] = None
- total_count: Optional[int] = None
-
- @override
- def _get_page_items(self) -> List[_T]:
- runs = self.runs
- if not runs:
- return []
- return runs
-
- @override
- def has_next_page(self) -> bool:
- has_more = self.has_more
- if has_more is not None and has_more is False:
- return False
-
- return super().has_next_page()
-
- @override
- def next_page_info(self) -> Optional[PageInfo]:
- runs = self.runs
- if not runs:
- return None
-
- item = cast(Any, runs[-1])
- if not isinstance(item, BenchmarkRunsCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
- # TODO emit warning log
- return None
-
- return PageInfo(params={"starting_after": item.id})
-
-
-class SyncScenariosCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
- scenarios: List[_T]
- has_more: Optional[bool] = None
- total_count: Optional[int] = None
-
- @override
- def _get_page_items(self) -> List[_T]:
- scenarios = self.scenarios
- if not scenarios:
- return []
- return scenarios
-
- @override
- def has_next_page(self) -> bool:
- has_more = self.has_more
- if has_more is not None and has_more is False:
- return False
-
- return super().has_next_page()
-
- @override
- def next_page_info(self) -> Optional[PageInfo]:
- scenarios = self.scenarios
- if not scenarios:
- return None
-
- item = cast(Any, scenarios[-1])
- if not isinstance(item, ScenariosCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
- # TODO emit warning log
- return None
-
- return PageInfo(params={"starting_after": item.id})
-
-
-class AsyncScenariosCursorIDPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
- scenarios: List[_T]
- has_more: Optional[bool] = None
- total_count: Optional[int] = None
-
- @override
- def _get_page_items(self) -> List[_T]:
- scenarios = self.scenarios
- if not scenarios:
- return []
- return scenarios
-
- @override
- def has_next_page(self) -> bool:
- has_more = self.has_more
- if has_more is not None and has_more is False:
- return False
-
- return super().has_next_page()
-
- @override
- def next_page_info(self) -> Optional[PageInfo]:
- scenarios = self.scenarios
- if not scenarios:
- return None
-
- item = cast(Any, scenarios[-1])
- if not isinstance(item, ScenariosCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
- # TODO emit warning log
- return None
-
- return PageInfo(params={"starting_after": item.id})
-
-
-class SyncScenarioRunsCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
- runs: List[_T]
- has_more: Optional[bool] = None
- total_count: Optional[int] = None
-
- @override
- def _get_page_items(self) -> List[_T]:
- runs = self.runs
- if not runs:
- return []
- return runs
-
- @override
- def has_next_page(self) -> bool:
- has_more = self.has_more
- if has_more is not None and has_more is False:
- return False
-
- return super().has_next_page()
-
- @override
- def next_page_info(self) -> Optional[PageInfo]:
- runs = self.runs
- if not runs:
- return None
-
- item = cast(Any, runs[-1])
- if not isinstance(item, ScenarioRunsCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
- # TODO emit warning log
- return None
-
- return PageInfo(params={"starting_after": item.id})
-
-
-class AsyncScenarioRunsCursorIDPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
- runs: List[_T]
- has_more: Optional[bool] = None
- total_count: Optional[int] = None
-
- @override
- def _get_page_items(self) -> List[_T]:
- runs = self.runs
- if not runs:
- return []
- return runs
-
- @override
- def has_next_page(self) -> bool:
- has_more = self.has_more
- if has_more is not None and has_more is False:
- return False
-
- return super().has_next_page()
-
- @override
- def next_page_info(self) -> Optional[PageInfo]:
- runs = self.runs
- if not runs:
- return None
-
- item = cast(Any, runs[-1])
- if not isinstance(item, ScenarioRunsCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
- # TODO emit warning log
- return None
-
- return PageInfo(params={"starting_after": item.id})
-
-
-class SyncScenarioScorersCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
- scorers: List[_T]
- has_more: Optional[bool] = None
- total_count: Optional[int] = None
-
- @override
- def _get_page_items(self) -> List[_T]:
- scorers = self.scorers
- if not scorers:
- return []
- return scorers
-
- @override
- def has_next_page(self) -> bool:
- has_more = self.has_more
- if has_more is not None and has_more is False:
- return False
-
- return super().has_next_page()
-
- @override
- def next_page_info(self) -> Optional[PageInfo]:
- scorers = self.scorers
- if not scorers:
- return None
-
- item = cast(Any, scorers[-1])
- if not isinstance(item, ScenarioScorersCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
- # TODO emit warning log
- return None
-
- return PageInfo(params={"starting_after": item.id})
-
-
-class AsyncScenarioScorersCursorIDPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
- scorers: List[_T]
- has_more: Optional[bool] = None
- total_count: Optional[int] = None
-
- @override
- def _get_page_items(self) -> List[_T]:
- scorers = self.scorers
- if not scorers:
- return []
- return scorers
-
- @override
- def has_next_page(self) -> bool:
- has_more = self.has_more
- if has_more is not None and has_more is False:
- return False
-
- return super().has_next_page()
-
- @override
- def next_page_info(self) -> Optional[PageInfo]:
- scorers = self.scorers
- if not scorers:
- return None
-
- item = cast(Any, scorers[-1])
- if not isinstance(item, ScenarioScorersCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
- # TODO emit warning log
- return None
-
- return PageInfo(params={"starting_after": item.id})
-
-
class SyncObjectsCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
objects: List[_T]
has_more: Optional[bool] = None
diff --git a/src/runloop_api_client/resources/__init__.py b/src/runloop_api_client/resources/__init__.py
index 2e6ca5d16..b1ab47272 100644
--- a/src/runloop_api_client/resources/__init__.py
+++ b/src/runloop_api_client/resources/__init__.py
@@ -16,14 +16,6 @@
AxonsResourceWithStreamingResponse,
AsyncAxonsResourceWithStreamingResponse,
)
-from .agents import (
- AgentsResource,
- AsyncAgentsResource,
- AgentsResourceWithRawResponse,
- AsyncAgentsResourceWithRawResponse,
- AgentsResourceWithStreamingResponse,
- AsyncAgentsResourceWithStreamingResponse,
-)
from .apikeys import (
ApikeysResource,
AsyncApikeysResource,
@@ -64,22 +56,6 @@
DevboxesResourceWithStreamingResponse,
AsyncDevboxesResourceWithStreamingResponse,
)
-from .scenarios import (
- ScenariosResource,
- AsyncScenariosResource,
- ScenariosResourceWithRawResponse,
- AsyncScenariosResourceWithRawResponse,
- ScenariosResourceWithStreamingResponse,
- AsyncScenariosResourceWithStreamingResponse,
-)
-from .benchmarks import (
- BenchmarksResource,
- AsyncBenchmarksResource,
- BenchmarksResourceWithRawResponse,
- AsyncBenchmarksResourceWithRawResponse,
- BenchmarksResourceWithStreamingResponse,
- AsyncBenchmarksResourceWithStreamingResponse,
-)
from .blueprints import (
BlueprintsResource,
AsyncBlueprintsResource,
@@ -96,22 +72,6 @@
McpConfigsResourceWithStreamingResponse,
AsyncMcpConfigsResourceWithStreamingResponse,
)
-from .benchmark_jobs import (
- BenchmarkJobsResource,
- AsyncBenchmarkJobsResource,
- BenchmarkJobsResourceWithRawResponse,
- AsyncBenchmarkJobsResourceWithRawResponse,
- BenchmarkJobsResourceWithStreamingResponse,
- AsyncBenchmarkJobsResourceWithStreamingResponse,
-)
-from .benchmark_runs import (
- BenchmarkRunsResource,
- AsyncBenchmarkRunsResource,
- BenchmarkRunsResourceWithRawResponse,
- AsyncBenchmarkRunsResourceWithRawResponse,
- BenchmarkRunsResourceWithStreamingResponse,
- AsyncBenchmarkRunsResourceWithStreamingResponse,
-)
from .gateway_configs import (
GatewayConfigsResource,
AsyncGatewayConfigsResource,
@@ -144,30 +104,6 @@
"AsyncAccountsResourceWithRawResponse",
"AccountsResourceWithStreamingResponse",
"AsyncAccountsResourceWithStreamingResponse",
- "BenchmarksResource",
- "AsyncBenchmarksResource",
- "BenchmarksResourceWithRawResponse",
- "AsyncBenchmarksResourceWithRawResponse",
- "BenchmarksResourceWithStreamingResponse",
- "AsyncBenchmarksResourceWithStreamingResponse",
- "BenchmarkRunsResource",
- "AsyncBenchmarkRunsResource",
- "BenchmarkRunsResourceWithRawResponse",
- "AsyncBenchmarkRunsResourceWithRawResponse",
- "BenchmarkRunsResourceWithStreamingResponse",
- "AsyncBenchmarkRunsResourceWithStreamingResponse",
- "BenchmarkJobsResource",
- "AsyncBenchmarkJobsResource",
- "BenchmarkJobsResourceWithRawResponse",
- "AsyncBenchmarkJobsResourceWithRawResponse",
- "BenchmarkJobsResourceWithStreamingResponse",
- "AsyncBenchmarkJobsResourceWithStreamingResponse",
- "AgentsResource",
- "AsyncAgentsResource",
- "AgentsResourceWithRawResponse",
- "AsyncAgentsResourceWithRawResponse",
- "AgentsResourceWithStreamingResponse",
- "AsyncAgentsResourceWithStreamingResponse",
"AxonsResource",
"AsyncAxonsResource",
"AxonsResourceWithRawResponse",
@@ -192,12 +128,6 @@
"AsyncPtyResourceWithRawResponse",
"PtyResourceWithStreamingResponse",
"AsyncPtyResourceWithStreamingResponse",
- "ScenariosResource",
- "AsyncScenariosResource",
- "ScenariosResourceWithRawResponse",
- "AsyncScenariosResourceWithRawResponse",
- "ScenariosResourceWithStreamingResponse",
- "AsyncScenariosResourceWithStreamingResponse",
"ObjectsResource",
"AsyncObjectsResource",
"ObjectsResourceWithRawResponse",
diff --git a/src/runloop_api_client/resources/agents.py b/src/runloop_api_client/resources/agents.py
deleted file mode 100644
index cef357709..000000000
--- a/src/runloop_api_client/resources/agents.py
+++ /dev/null
@@ -1,740 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-
-import httpx
-
-from ..types import agent_list_params, agent_create_params, agent_list_public_params
-from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
-from .._utils import path_template, maybe_transform, async_maybe_transform
-from .._compat import cached_property
-from .._resource import SyncAPIResource, AsyncAPIResource
-from .._response import (
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
-)
-from ..pagination import SyncAgentsCursorIDPage, AsyncAgentsCursorIDPage
-from .._base_client import AsyncPaginator, make_request_options
-from ..types.agent_view import AgentView
-from ..types.agent_devbox_counts_view import AgentDevboxCountsView
-from ..types.shared_params.agent_source import AgentSource
-
-__all__ = ["AgentsResource", "AsyncAgentsResource"]
-
-
-class AgentsResource(SyncAPIResource):
- @cached_property
- def with_raw_response(self) -> AgentsResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return AgentsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AgentsResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return AgentsResourceWithStreamingResponse(self)
-
- def create(
- self,
- *,
- name: str,
- source: Optional[AgentSource] | Omit = omit,
- version: Optional[str] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> AgentView:
- """Create a new Agent with a name and optional public visibility.
-
- The Agent will be
- assigned a unique ID.
-
- Args:
- name: The name of the Agent.
-
- source: Agent source configuration.
-
- version: Optional version identifier for the Agent. For npm/pip sources this is typically
- a semver string (e.g. '2.0.65'). For git sources it can be a branch or tag.
- Semantics are user-defined for object sources.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return self._post(
- "/v1/agents",
- body=maybe_transform(
- {
- "name": name,
- "source": source,
- "version": version,
- },
- agent_create_params.AgentCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=AgentView,
- )
-
- def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AgentView:
- """
- Retrieve a specific Agent by its unique identifier.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._get(
- path_template("/v1/agents/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=AgentView,
- )
-
- def list(
- self,
- *,
- include_total_count: bool | Omit = omit,
- is_public: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- version: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SyncAgentsCursorIDPage[AgentView]:
- """
- List all Agents for the authenticated account with pagination support.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- is_public: Filter agents by public visibility.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter agents by name (partial match supported).
-
- search: Search by agent ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- version: Filter by version. Use 'latest' to get the most recently created agent.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/agents",
- page=SyncAgentsCursorIDPage[AgentView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "is_public": is_public,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- "version": version,
- },
- agent_list_params.AgentListParams,
- ),
- ),
- model=AgentView,
- )
-
- def delete(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> object:
- """Delete an Agent by its unique identifier.
-
- The Agent will be permanently removed.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/v1/agents/{id}/delete", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=object,
- )
-
- def devbox_counts(
- self,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AgentDevboxCountsView:
- """Returns devbox counts grouped by agent name.
-
- This endpoint efficiently
- aggregates devbox counts for all agents in a single request, avoiding N+1 query
- patterns.
- """
- return self._get(
- "/v1/agents/devbox_counts",
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=AgentDevboxCountsView,
- )
-
- def list_public(
- self,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- version: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SyncAgentsCursorIDPage[AgentView]:
- """
- List all public Agents with pagination support.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter agents by name (partial match supported).
-
- search: Search by agent ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- version: Filter by version. Use 'latest' to get the most recently created agent.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/agents/list_public",
- page=SyncAgentsCursorIDPage[AgentView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- "version": version,
- },
- agent_list_public_params.AgentListPublicParams,
- ),
- ),
- model=AgentView,
- )
-
-
-class AsyncAgentsResource(AsyncAPIResource):
- @cached_property
- def with_raw_response(self) -> AsyncAgentsResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return AsyncAgentsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncAgentsResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return AsyncAgentsResourceWithStreamingResponse(self)
-
- async def create(
- self,
- *,
- name: str,
- source: Optional[AgentSource] | Omit = omit,
- version: Optional[str] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> AgentView:
- """Create a new Agent with a name and optional public visibility.
-
- The Agent will be
- assigned a unique ID.
-
- Args:
- name: The name of the Agent.
-
- source: Agent source configuration.
-
- version: Optional version identifier for the Agent. For npm/pip sources this is typically
- a semver string (e.g. '2.0.65'). For git sources it can be a branch or tag.
- Semantics are user-defined for object sources.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return await self._post(
- "/v1/agents",
- body=await async_maybe_transform(
- {
- "name": name,
- "source": source,
- "version": version,
- },
- agent_create_params.AgentCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=AgentView,
- )
-
- async def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AgentView:
- """
- Retrieve a specific Agent by its unique identifier.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._get(
- path_template("/v1/agents/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=AgentView,
- )
-
- def list(
- self,
- *,
- include_total_count: bool | Omit = omit,
- is_public: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- version: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AsyncPaginator[AgentView, AsyncAgentsCursorIDPage[AgentView]]:
- """
- List all Agents for the authenticated account with pagination support.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- is_public: Filter agents by public visibility.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter agents by name (partial match supported).
-
- search: Search by agent ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- version: Filter by version. Use 'latest' to get the most recently created agent.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/agents",
- page=AsyncAgentsCursorIDPage[AgentView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "is_public": is_public,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- "version": version,
- },
- agent_list_params.AgentListParams,
- ),
- ),
- model=AgentView,
- )
-
- async def delete(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> object:
- """Delete an Agent by its unique identifier.
-
- The Agent will be permanently removed.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/v1/agents/{id}/delete", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=object,
- )
-
- async def devbox_counts(
- self,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AgentDevboxCountsView:
- """Returns devbox counts grouped by agent name.
-
- This endpoint efficiently
- aggregates devbox counts for all agents in a single request, avoiding N+1 query
- patterns.
- """
- return await self._get(
- "/v1/agents/devbox_counts",
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=AgentDevboxCountsView,
- )
-
- def list_public(
- self,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- version: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AsyncPaginator[AgentView, AsyncAgentsCursorIDPage[AgentView]]:
- """
- List all public Agents with pagination support.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter agents by name (partial match supported).
-
- search: Search by agent ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- version: Filter by version. Use 'latest' to get the most recently created agent.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/agents/list_public",
- page=AsyncAgentsCursorIDPage[AgentView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- "version": version,
- },
- agent_list_public_params.AgentListPublicParams,
- ),
- ),
- model=AgentView,
- )
-
-
-class AgentsResourceWithRawResponse:
- def __init__(self, agents: AgentsResource) -> None:
- self._agents = agents
-
- self.create = to_raw_response_wrapper(
- agents.create,
- )
- self.retrieve = to_raw_response_wrapper(
- agents.retrieve,
- )
- self.list = to_raw_response_wrapper(
- agents.list,
- )
- self.delete = to_raw_response_wrapper(
- agents.delete,
- )
- self.devbox_counts = to_raw_response_wrapper(
- agents.devbox_counts,
- )
- self.list_public = to_raw_response_wrapper(
- agents.list_public,
- )
-
-
-class AsyncAgentsResourceWithRawResponse:
- def __init__(self, agents: AsyncAgentsResource) -> None:
- self._agents = agents
-
- self.create = async_to_raw_response_wrapper(
- agents.create,
- )
- self.retrieve = async_to_raw_response_wrapper(
- agents.retrieve,
- )
- self.list = async_to_raw_response_wrapper(
- agents.list,
- )
- self.delete = async_to_raw_response_wrapper(
- agents.delete,
- )
- self.devbox_counts = async_to_raw_response_wrapper(
- agents.devbox_counts,
- )
- self.list_public = async_to_raw_response_wrapper(
- agents.list_public,
- )
-
-
-class AgentsResourceWithStreamingResponse:
- def __init__(self, agents: AgentsResource) -> None:
- self._agents = agents
-
- self.create = to_streamed_response_wrapper(
- agents.create,
- )
- self.retrieve = to_streamed_response_wrapper(
- agents.retrieve,
- )
- self.list = to_streamed_response_wrapper(
- agents.list,
- )
- self.delete = to_streamed_response_wrapper(
- agents.delete,
- )
- self.devbox_counts = to_streamed_response_wrapper(
- agents.devbox_counts,
- )
- self.list_public = to_streamed_response_wrapper(
- agents.list_public,
- )
-
-
-class AsyncAgentsResourceWithStreamingResponse:
- def __init__(self, agents: AsyncAgentsResource) -> None:
- self._agents = agents
-
- self.create = async_to_streamed_response_wrapper(
- agents.create,
- )
- self.retrieve = async_to_streamed_response_wrapper(
- agents.retrieve,
- )
- self.list = async_to_streamed_response_wrapper(
- agents.list,
- )
- self.delete = async_to_streamed_response_wrapper(
- agents.delete,
- )
- self.devbox_counts = async_to_streamed_response_wrapper(
- agents.devbox_counts,
- )
- self.list_public = async_to_streamed_response_wrapper(
- agents.list_public,
- )
diff --git a/src/runloop_api_client/resources/axons/axons.py b/src/runloop_api_client/resources/axons/axons.py
index 232f6f4c2..59dd70ebd 100644
--- a/src/runloop_api_client/resources/axons/axons.py
+++ b/src/runloop_api_client/resources/axons/axons.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Optional
+from typing import Dict, Optional
from typing_extensions import Literal
import httpx
@@ -23,7 +23,13 @@
EventsResourceWithStreamingResponse,
AsyncEventsResourceWithStreamingResponse,
)
-from ...types import axon_list_params, axon_create_params, axon_publish_params, axon_subscribe_sse_params
+from ...types import (
+ axon_list_params,
+ axon_create_params,
+ axon_update_params,
+ axon_publish_params,
+ axon_subscribe_sse_params,
+)
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import path_template, maybe_transform, async_maybe_transform
from ..._compat import cached_property
@@ -75,6 +81,7 @@ def with_streaming_response(self) -> AxonsResourceWithStreamingResponse:
def create(
self,
*,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
name: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -88,6 +95,8 @@ def create(
[Beta] Create a new axon.
Args:
+ metadata: User defined metadata to attach to the axon for organization.
+
name: (Optional) Name for the axon.
extra_headers: Send extra headers
@@ -102,7 +111,13 @@ def create(
"""
return self._post(
"/v1/axons",
- body=maybe_transform({"name": name}, axon_create_params.AxonCreateParams),
+ body=maybe_transform(
+ {
+ "metadata": metadata,
+ "name": name,
+ },
+ axon_create_params.AxonCreateParams,
+ ),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -124,8 +139,10 @@ def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AxonView:
- """
- [Beta] Get an axon given ID.
+ """[Beta] Get an axon by ID.
+
+ Deleted axons remain readable and include their
+ deletion time.
Args:
extra_headers: Send extra headers
@@ -146,12 +163,62 @@ def retrieve(
cast_to=AxonView,
)
+ def update(
+ self,
+ id: str,
+ *,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> AxonView:
+ """[Beta] Updates the specified axon fields.
+
+ Omitted fields are left unchanged. An
+ empty metadata map clears the metadata. Deleted axons remain readable but cannot
+ be updated.
+
+ Args:
+ metadata: User defined metadata to replace the axon metadata. Omit or set to null to leave
+ unchanged, or set to an empty map to clear it.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._post(
+ path_template("/v1/axons/{id}", id=id),
+ body=maybe_transform({"metadata": metadata}, axon_update_params.AxonUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=AxonView,
+ )
+
def list(
self,
*,
id: str | Omit = omit,
include_total_count: bool | Omit = omit,
limit: int | Omit = omit,
+ metadata_key: str | Omit = omit,
+ metadata_key_in: str | Omit = omit,
name: str | Omit = omit,
search: str | Omit = omit,
starting_after: str | Omit = omit,
@@ -162,8 +229,10 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SyncAxonsCursorIDPage[AxonView]:
- """
- [Beta] List all active axons.
+ """[Beta] List axons, including deleted axons.
+
+ Deleted axons include their deletion
+ time.
Args:
id: Filter by axon ID.
@@ -173,6 +242,11 @@ def list(
limit: The limit of items to return. Default is 20. Max is 5000.
+ metadata_key: Filter axons by metadata key-value pair. Can be used multiple times for
+ different keys.
+
+ metadata_key_in: Filter axons by metadata key with multiple possible values (OR condition).
+
name: Filter by axon name (prefix match supported).
search: Search by axon ID or name.
@@ -200,6 +274,8 @@ def list(
"id": id,
"include_total_count": include_total_count,
"limit": limit,
+ "metadata_key": metadata_key,
+ "metadata_key_in": metadata_key_in,
"name": name,
"search": search,
"starting_after": starting_after,
@@ -222,8 +298,9 @@ def delete(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
idempotency_key: str | None = None,
) -> object:
- """
- [Beta] Mark an axon deleted.
+ """[Beta] Delete an axon's event data.
+
+ The axon's registry record remains readable.
Args:
extra_headers: Send extra headers
@@ -390,6 +467,7 @@ def with_streaming_response(self) -> AsyncAxonsResourceWithStreamingResponse:
async def create(
self,
*,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
name: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -403,6 +481,8 @@ async def create(
[Beta] Create a new axon.
Args:
+ metadata: User defined metadata to attach to the axon for organization.
+
name: (Optional) Name for the axon.
extra_headers: Send extra headers
@@ -417,7 +497,13 @@ async def create(
"""
return await self._post(
"/v1/axons",
- body=await async_maybe_transform({"name": name}, axon_create_params.AxonCreateParams),
+ body=await async_maybe_transform(
+ {
+ "metadata": metadata,
+ "name": name,
+ },
+ axon_create_params.AxonCreateParams,
+ ),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -439,8 +525,10 @@ async def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AxonView:
- """
- [Beta] Get an axon given ID.
+ """[Beta] Get an axon by ID.
+
+ Deleted axons remain readable and include their
+ deletion time.
Args:
extra_headers: Send extra headers
@@ -461,12 +549,62 @@ async def retrieve(
cast_to=AxonView,
)
+ async def update(
+ self,
+ id: str,
+ *,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> AxonView:
+ """[Beta] Updates the specified axon fields.
+
+ Omitted fields are left unchanged. An
+ empty metadata map clears the metadata. Deleted axons remain readable but cannot
+ be updated.
+
+ Args:
+ metadata: User defined metadata to replace the axon metadata. Omit or set to null to leave
+ unchanged, or set to an empty map to clear it.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._post(
+ path_template("/v1/axons/{id}", id=id),
+ body=await async_maybe_transform({"metadata": metadata}, axon_update_params.AxonUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=AxonView,
+ )
+
def list(
self,
*,
id: str | Omit = omit,
include_total_count: bool | Omit = omit,
limit: int | Omit = omit,
+ metadata_key: str | Omit = omit,
+ metadata_key_in: str | Omit = omit,
name: str | Omit = omit,
search: str | Omit = omit,
starting_after: str | Omit = omit,
@@ -477,8 +615,10 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AsyncPaginator[AxonView, AsyncAxonsCursorIDPage[AxonView]]:
- """
- [Beta] List all active axons.
+ """[Beta] List axons, including deleted axons.
+
+ Deleted axons include their deletion
+ time.
Args:
id: Filter by axon ID.
@@ -488,6 +628,11 @@ def list(
limit: The limit of items to return. Default is 20. Max is 5000.
+ metadata_key: Filter axons by metadata key-value pair. Can be used multiple times for
+ different keys.
+
+ metadata_key_in: Filter axons by metadata key with multiple possible values (OR condition).
+
name: Filter by axon name (prefix match supported).
search: Search by axon ID or name.
@@ -515,6 +660,8 @@ def list(
"id": id,
"include_total_count": include_total_count,
"limit": limit,
+ "metadata_key": metadata_key,
+ "metadata_key_in": metadata_key_in,
"name": name,
"search": search,
"starting_after": starting_after,
@@ -537,8 +684,9 @@ async def delete(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
idempotency_key: str | None = None,
) -> object:
- """
- [Beta] Mark an axon deleted.
+ """[Beta] Delete an axon's event data.
+
+ The axon's registry record remains readable.
Args:
extra_headers: Send extra headers
@@ -684,6 +832,9 @@ def __init__(self, axons: AxonsResource) -> None:
self.retrieve = to_raw_response_wrapper(
axons.retrieve,
)
+ self.update = to_raw_response_wrapper(
+ axons.update,
+ )
self.list = to_raw_response_wrapper(
axons.list,
)
@@ -716,6 +867,9 @@ def __init__(self, axons: AsyncAxonsResource) -> None:
self.retrieve = async_to_raw_response_wrapper(
axons.retrieve,
)
+ self.update = async_to_raw_response_wrapper(
+ axons.update,
+ )
self.list = async_to_raw_response_wrapper(
axons.list,
)
@@ -748,6 +902,9 @@ def __init__(self, axons: AxonsResource) -> None:
self.retrieve = to_streamed_response_wrapper(
axons.retrieve,
)
+ self.update = to_streamed_response_wrapper(
+ axons.update,
+ )
self.list = to_streamed_response_wrapper(
axons.list,
)
@@ -780,6 +937,9 @@ def __init__(self, axons: AsyncAxonsResource) -> None:
self.retrieve = async_to_streamed_response_wrapper(
axons.retrieve,
)
+ self.update = async_to_streamed_response_wrapper(
+ axons.update,
+ )
self.list = async_to_streamed_response_wrapper(
axons.list,
)
diff --git a/src/runloop_api_client/resources/benchmark_jobs.py b/src/runloop_api_client/resources/benchmark_jobs.py
deleted file mode 100644
index 57dc8b5cd..000000000
--- a/src/runloop_api_client/resources/benchmark_jobs.py
+++ /dev/null
@@ -1,412 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-
-import httpx
-
-from ..types import benchmark_job_list_params, benchmark_job_create_params
-from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
-from .._utils import path_template, maybe_transform, async_maybe_transform
-from .._compat import cached_property
-from .._resource import SyncAPIResource, AsyncAPIResource
-from .._response import (
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
-)
-from .._base_client import make_request_options
-from ..types.benchmark_job_view import BenchmarkJobView
-from ..types.benchmark_job_list_view import BenchmarkJobListView
-
-__all__ = ["BenchmarkJobsResource", "AsyncBenchmarkJobsResource"]
-
-
-class BenchmarkJobsResource(SyncAPIResource):
- @cached_property
- def with_raw_response(self) -> BenchmarkJobsResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return BenchmarkJobsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> BenchmarkJobsResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return BenchmarkJobsResourceWithStreamingResponse(self)
-
- def create(
- self,
- *,
- name: Optional[str] | Omit = omit,
- spec: Optional[benchmark_job_create_params.Spec] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkJobView:
- """
- [Beta] Create a BenchmarkJob that runs a set of scenarios entirely on runloop.
-
- Args:
- name: The name of the BenchmarkJob. If not provided, name will be generated based on
- target dataset.
-
- spec: The job specification. Exactly one spec type must be set.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return self._post(
- "/v1/benchmark_jobs",
- body=maybe_transform(
- {
- "name": name,
- "spec": spec,
- },
- benchmark_job_create_params.BenchmarkJobCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkJobView,
- )
-
- def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> BenchmarkJobView:
- """
- [Beta] Get a BenchmarkJob given ID.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._get(
- path_template("/v1/benchmark_jobs/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=BenchmarkJobView,
- )
-
- def list(
- self,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> BenchmarkJobListView:
- """
- [Beta] List all BenchmarkJobs matching filter.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter by name
-
- search: Search by benchmark job ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get(
- "/v1/benchmark_jobs",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- },
- benchmark_job_list_params.BenchmarkJobListParams,
- ),
- ),
- cast_to=BenchmarkJobListView,
- )
-
-
-class AsyncBenchmarkJobsResource(AsyncAPIResource):
- @cached_property
- def with_raw_response(self) -> AsyncBenchmarkJobsResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return AsyncBenchmarkJobsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncBenchmarkJobsResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return AsyncBenchmarkJobsResourceWithStreamingResponse(self)
-
- async def create(
- self,
- *,
- name: Optional[str] | Omit = omit,
- spec: Optional[benchmark_job_create_params.Spec] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkJobView:
- """
- [Beta] Create a BenchmarkJob that runs a set of scenarios entirely on runloop.
-
- Args:
- name: The name of the BenchmarkJob. If not provided, name will be generated based on
- target dataset.
-
- spec: The job specification. Exactly one spec type must be set.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return await self._post(
- "/v1/benchmark_jobs",
- body=await async_maybe_transform(
- {
- "name": name,
- "spec": spec,
- },
- benchmark_job_create_params.BenchmarkJobCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkJobView,
- )
-
- async def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> BenchmarkJobView:
- """
- [Beta] Get a BenchmarkJob given ID.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._get(
- path_template("/v1/benchmark_jobs/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=BenchmarkJobView,
- )
-
- async def list(
- self,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> BenchmarkJobListView:
- """
- [Beta] List all BenchmarkJobs matching filter.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter by name
-
- search: Search by benchmark job ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return await self._get(
- "/v1/benchmark_jobs",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- },
- benchmark_job_list_params.BenchmarkJobListParams,
- ),
- ),
- cast_to=BenchmarkJobListView,
- )
-
-
-class BenchmarkJobsResourceWithRawResponse:
- def __init__(self, benchmark_jobs: BenchmarkJobsResource) -> None:
- self._benchmark_jobs = benchmark_jobs
-
- self.create = to_raw_response_wrapper(
- benchmark_jobs.create,
- )
- self.retrieve = to_raw_response_wrapper(
- benchmark_jobs.retrieve,
- )
- self.list = to_raw_response_wrapper(
- benchmark_jobs.list,
- )
-
-
-class AsyncBenchmarkJobsResourceWithRawResponse:
- def __init__(self, benchmark_jobs: AsyncBenchmarkJobsResource) -> None:
- self._benchmark_jobs = benchmark_jobs
-
- self.create = async_to_raw_response_wrapper(
- benchmark_jobs.create,
- )
- self.retrieve = async_to_raw_response_wrapper(
- benchmark_jobs.retrieve,
- )
- self.list = async_to_raw_response_wrapper(
- benchmark_jobs.list,
- )
-
-
-class BenchmarkJobsResourceWithStreamingResponse:
- def __init__(self, benchmark_jobs: BenchmarkJobsResource) -> None:
- self._benchmark_jobs = benchmark_jobs
-
- self.create = to_streamed_response_wrapper(
- benchmark_jobs.create,
- )
- self.retrieve = to_streamed_response_wrapper(
- benchmark_jobs.retrieve,
- )
- self.list = to_streamed_response_wrapper(
- benchmark_jobs.list,
- )
-
-
-class AsyncBenchmarkJobsResourceWithStreamingResponse:
- def __init__(self, benchmark_jobs: AsyncBenchmarkJobsResource) -> None:
- self._benchmark_jobs = benchmark_jobs
-
- self.create = async_to_streamed_response_wrapper(
- benchmark_jobs.create,
- )
- self.retrieve = async_to_streamed_response_wrapper(
- benchmark_jobs.retrieve,
- )
- self.list = async_to_streamed_response_wrapper(
- benchmark_jobs.list,
- )
diff --git a/src/runloop_api_client/resources/benchmark_runs.py b/src/runloop_api_client/resources/benchmark_runs.py
deleted file mode 100644
index 075246c30..000000000
--- a/src/runloop_api_client/resources/benchmark_runs.py
+++ /dev/null
@@ -1,645 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import Literal
-
-import httpx
-
-from ..types import benchmark_run_list_params, benchmark_run_list_scenario_runs_params
-from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
-from .._utils import path_template, maybe_transform
-from .._compat import cached_property
-from .._resource import SyncAPIResource, AsyncAPIResource
-from .._response import (
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
-)
-from ..pagination import SyncBenchmarkRunsCursorIDPage, AsyncBenchmarkRunsCursorIDPage
-from .._base_client import AsyncPaginator, make_request_options
-from ..types.scenario_run_view import ScenarioRunView
-from ..types.benchmark_run_view import BenchmarkRunView
-
-__all__ = ["BenchmarkRunsResource", "AsyncBenchmarkRunsResource"]
-
-
-class BenchmarkRunsResource(SyncAPIResource):
- @cached_property
- def with_raw_response(self) -> BenchmarkRunsResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return BenchmarkRunsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> BenchmarkRunsResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return BenchmarkRunsResourceWithStreamingResponse(self)
-
- def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> BenchmarkRunView:
- """
- Get a BenchmarkRun given ID.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._get(
- path_template("/v1/benchmark_runs/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=BenchmarkRunView,
- )
-
- def list(
- self,
- *,
- benchmark_id: str | Omit = omit,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- state: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SyncBenchmarkRunsCursorIDPage[BenchmarkRunView]:
- """
- List all BenchmarkRuns matching filter.
-
- Args:
- benchmark_id: The Benchmark ID to filter by.
-
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter by name
-
- search: Search by benchmark run ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- state: Filter by state
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/benchmark_runs",
- page=SyncBenchmarkRunsCursorIDPage[BenchmarkRunView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "benchmark_id": benchmark_id,
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- "state": state,
- },
- benchmark_run_list_params.BenchmarkRunListParams,
- ),
- ),
- model=BenchmarkRunView,
- )
-
- def cancel(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkRunView:
- """Cancel a Benchmark run.
-
- This will do the following: 1. Cancel all running
- scenarios and shutdown the underlying Devbox resources 2. Update the benchmark
- state to CANCELED 3. Calculate final score from completed scenarios
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/v1/benchmark_runs/{id}/cancel", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkRunView,
- )
-
- def complete(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkRunView:
- """
- Complete a currently running BenchmarkRun.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/v1/benchmark_runs/{id}/complete", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkRunView,
- )
-
- def list_scenario_runs(
- self,
- id: str,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- state: Literal["running", "scoring", "scored", "completed", "canceled", "timeout", "failed"] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SyncBenchmarkRunsCursorIDPage[ScenarioRunView]:
- """
- List started scenario runs for a benchmark run.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- search: Search by scenario run ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- state: Filter by Scenario Run state
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._get_api_list(
- path_template("/v1/benchmark_runs/{id}/scenario_runs", id=id),
- page=SyncBenchmarkRunsCursorIDPage[ScenarioRunView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "search": search,
- "starting_after": starting_after,
- "state": state,
- },
- benchmark_run_list_scenario_runs_params.BenchmarkRunListScenarioRunsParams,
- ),
- ),
- model=ScenarioRunView,
- )
-
-
-class AsyncBenchmarkRunsResource(AsyncAPIResource):
- @cached_property
- def with_raw_response(self) -> AsyncBenchmarkRunsResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return AsyncBenchmarkRunsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncBenchmarkRunsResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return AsyncBenchmarkRunsResourceWithStreamingResponse(self)
-
- async def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> BenchmarkRunView:
- """
- Get a BenchmarkRun given ID.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._get(
- path_template("/v1/benchmark_runs/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=BenchmarkRunView,
- )
-
- def list(
- self,
- *,
- benchmark_id: str | Omit = omit,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- state: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AsyncPaginator[BenchmarkRunView, AsyncBenchmarkRunsCursorIDPage[BenchmarkRunView]]:
- """
- List all BenchmarkRuns matching filter.
-
- Args:
- benchmark_id: The Benchmark ID to filter by.
-
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter by name
-
- search: Search by benchmark run ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- state: Filter by state
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/benchmark_runs",
- page=AsyncBenchmarkRunsCursorIDPage[BenchmarkRunView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "benchmark_id": benchmark_id,
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- "state": state,
- },
- benchmark_run_list_params.BenchmarkRunListParams,
- ),
- ),
- model=BenchmarkRunView,
- )
-
- async def cancel(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkRunView:
- """Cancel a Benchmark run.
-
- This will do the following: 1. Cancel all running
- scenarios and shutdown the underlying Devbox resources 2. Update the benchmark
- state to CANCELED 3. Calculate final score from completed scenarios
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/v1/benchmark_runs/{id}/cancel", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkRunView,
- )
-
- async def complete(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkRunView:
- """
- Complete a currently running BenchmarkRun.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/v1/benchmark_runs/{id}/complete", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkRunView,
- )
-
- def list_scenario_runs(
- self,
- id: str,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- state: Literal["running", "scoring", "scored", "completed", "canceled", "timeout", "failed"] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AsyncPaginator[ScenarioRunView, AsyncBenchmarkRunsCursorIDPage[ScenarioRunView]]:
- """
- List started scenario runs for a benchmark run.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- search: Search by scenario run ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- state: Filter by Scenario Run state
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._get_api_list(
- path_template("/v1/benchmark_runs/{id}/scenario_runs", id=id),
- page=AsyncBenchmarkRunsCursorIDPage[ScenarioRunView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "search": search,
- "starting_after": starting_after,
- "state": state,
- },
- benchmark_run_list_scenario_runs_params.BenchmarkRunListScenarioRunsParams,
- ),
- ),
- model=ScenarioRunView,
- )
-
-
-class BenchmarkRunsResourceWithRawResponse:
- def __init__(self, benchmark_runs: BenchmarkRunsResource) -> None:
- self._benchmark_runs = benchmark_runs
-
- self.retrieve = to_raw_response_wrapper(
- benchmark_runs.retrieve,
- )
- self.list = to_raw_response_wrapper(
- benchmark_runs.list,
- )
- self.cancel = to_raw_response_wrapper(
- benchmark_runs.cancel,
- )
- self.complete = to_raw_response_wrapper(
- benchmark_runs.complete,
- )
- self.list_scenario_runs = to_raw_response_wrapper(
- benchmark_runs.list_scenario_runs,
- )
-
-
-class AsyncBenchmarkRunsResourceWithRawResponse:
- def __init__(self, benchmark_runs: AsyncBenchmarkRunsResource) -> None:
- self._benchmark_runs = benchmark_runs
-
- self.retrieve = async_to_raw_response_wrapper(
- benchmark_runs.retrieve,
- )
- self.list = async_to_raw_response_wrapper(
- benchmark_runs.list,
- )
- self.cancel = async_to_raw_response_wrapper(
- benchmark_runs.cancel,
- )
- self.complete = async_to_raw_response_wrapper(
- benchmark_runs.complete,
- )
- self.list_scenario_runs = async_to_raw_response_wrapper(
- benchmark_runs.list_scenario_runs,
- )
-
-
-class BenchmarkRunsResourceWithStreamingResponse:
- def __init__(self, benchmark_runs: BenchmarkRunsResource) -> None:
- self._benchmark_runs = benchmark_runs
-
- self.retrieve = to_streamed_response_wrapper(
- benchmark_runs.retrieve,
- )
- self.list = to_streamed_response_wrapper(
- benchmark_runs.list,
- )
- self.cancel = to_streamed_response_wrapper(
- benchmark_runs.cancel,
- )
- self.complete = to_streamed_response_wrapper(
- benchmark_runs.complete,
- )
- self.list_scenario_runs = to_streamed_response_wrapper(
- benchmark_runs.list_scenario_runs,
- )
-
-
-class AsyncBenchmarkRunsResourceWithStreamingResponse:
- def __init__(self, benchmark_runs: AsyncBenchmarkRunsResource) -> None:
- self._benchmark_runs = benchmark_runs
-
- self.retrieve = async_to_streamed_response_wrapper(
- benchmark_runs.retrieve,
- )
- self.list = async_to_streamed_response_wrapper(
- benchmark_runs.list,
- )
- self.cancel = async_to_streamed_response_wrapper(
- benchmark_runs.cancel,
- )
- self.complete = async_to_streamed_response_wrapper(
- benchmark_runs.complete,
- )
- self.list_scenario_runs = async_to_streamed_response_wrapper(
- benchmark_runs.list_scenario_runs,
- )
diff --git a/src/runloop_api_client/resources/benchmarks.py b/src/runloop_api_client/resources/benchmarks.py
deleted file mode 100644
index 7ee045266..000000000
--- a/src/runloop_api_client/resources/benchmarks.py
+++ /dev/null
@@ -1,1129 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Dict, Optional
-
-import httpx
-
-from ..types import (
- benchmark_list_params,
- benchmark_create_params,
- benchmark_update_params,
- benchmark_start_run_params,
- benchmark_definitions_params,
- benchmark_list_public_params,
- benchmark_update_scenarios_params,
-)
-from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
-from .._utils import path_template, maybe_transform, async_maybe_transform
-from .._compat import cached_property
-from .._resource import SyncAPIResource, AsyncAPIResource
-from .._response import (
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
-)
-from ..pagination import SyncBenchmarksCursorIDPage, AsyncBenchmarksCursorIDPage
-from .._base_client import AsyncPaginator, make_request_options
-from ..types.benchmark_view import BenchmarkView
-from ..types.benchmark_run_view import BenchmarkRunView
-from ..types.shared_params.run_profile import RunProfile
-from ..types.scenario_definition_list_view import ScenarioDefinitionListView
-
-__all__ = ["BenchmarksResource", "AsyncBenchmarksResource"]
-
-
-class BenchmarksResource(SyncAPIResource):
- @cached_property
- def with_raw_response(self) -> BenchmarksResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return BenchmarksResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> BenchmarksResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return BenchmarksResourceWithStreamingResponse(self)
-
- def create(
- self,
- *,
- name: str,
- attribution: Optional[str] | Omit = omit,
- description: Optional[str] | Omit = omit,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- required_environment_variables: Optional[SequenceNotStr[str]] | Omit = omit,
- required_secret_names: SequenceNotStr[str] | Omit = omit,
- scenario_ids: Optional[SequenceNotStr[str]] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkView:
- """
- Create a Benchmark with a set of Scenarios.
-
- Args:
- name: The unique name of the Benchmark.
-
- attribution: Attribution information for the benchmark.
-
- description: Detailed description of the benchmark.
-
- metadata: User defined metadata to attach to the benchmark.
-
- required_environment_variables: Environment variables required to run the benchmark. If any required variables
- are not supplied, the benchmark will fail to start.
-
- required_secret_names: Secrets required to run the benchmark with (environment variable name will be
- mapped to the your user secret by name). If any of these secrets are not
- provided or the mapping is incorrect, the benchmark will fail to start.
-
- scenario_ids: The Scenario IDs that make up the Benchmark.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return self._post(
- "/v1/benchmarks",
- body=maybe_transform(
- {
- "name": name,
- "attribution": attribution,
- "description": description,
- "metadata": metadata,
- "required_environment_variables": required_environment_variables,
- "required_secret_names": required_secret_names,
- "scenario_ids": scenario_ids,
- },
- benchmark_create_params.BenchmarkCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkView,
- )
-
- def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> BenchmarkView:
- """
- Get a previously created Benchmark.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._get(
- path_template("/v1/benchmarks/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=BenchmarkView,
- )
-
- def update(
- self,
- id: str,
- *,
- attribution: Optional[str] | Omit = omit,
- description: Optional[str] | Omit = omit,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- name: Optional[str] | Omit = omit,
- required_environment_variables: Optional[SequenceNotStr[str]] | Omit = omit,
- required_secret_names: Optional[SequenceNotStr[str]] | Omit = omit,
- scenario_ids: Optional[SequenceNotStr[str]] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkView:
- """Update a Benchmark.
-
- Fields that are null will preserve the existing value.
- Fields that are provided (including empty values) will replace the existing
- value entirely.
-
- Args:
- attribution: Attribution information for the benchmark. Pass in empty string to clear.
-
- description: Detailed description of the benchmark. Pass in empty string to clear.
-
- metadata: User defined metadata to attach to the benchmark. Pass in empty map to clear.
-
- name: The unique name of the Benchmark. Cannot be blank.
-
- required_environment_variables: Environment variables required to run the benchmark. If any required variables
- are not supplied, the benchmark will fail to start. Pass in empty list to clear.
-
- required_secret_names: Secrets required to run the benchmark with (environment variable name will be
- mapped to the your user secret by name). If any of these secrets are not
- provided or the mapping is incorrect, the benchmark will fail to start. Pass in
- empty list to clear.
-
- scenario_ids: The Scenario IDs that make up the Benchmark. Pass in empty list to clear.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/v1/benchmarks/{id}", id=id),
- body=maybe_transform(
- {
- "attribution": attribution,
- "description": description,
- "metadata": metadata,
- "name": name,
- "required_environment_variables": required_environment_variables,
- "required_secret_names": required_secret_names,
- "scenario_ids": scenario_ids,
- },
- benchmark_update_params.BenchmarkUpdateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkView,
- )
-
- def list(
- self,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SyncBenchmarksCursorIDPage[BenchmarkView]:
- """
- List all Benchmarks matching filter.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter by name
-
- search: Search by benchmark ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/benchmarks",
- page=SyncBenchmarksCursorIDPage[BenchmarkView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- },
- benchmark_list_params.BenchmarkListParams,
- ),
- ),
- model=BenchmarkView,
- )
-
- def definitions(
- self,
- id: str,
- *,
- limit: int | Omit = omit,
- starting_after: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ScenarioDefinitionListView:
- """
- Get scenario definitions for a previously created Benchmark.
-
- Args:
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._get(
- path_template("/v1/benchmarks/{id}/definitions", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "limit": limit,
- "starting_after": starting_after,
- },
- benchmark_definitions_params.BenchmarkDefinitionsParams,
- ),
- ),
- cast_to=ScenarioDefinitionListView,
- )
-
- def list_public(
- self,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SyncBenchmarksCursorIDPage[BenchmarkView]:
- """
- List all public benchmarks matching filter.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter by name
-
- search: Search by benchmark ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/benchmarks/list_public",
- page=SyncBenchmarksCursorIDPage[BenchmarkView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- },
- benchmark_list_public_params.BenchmarkListPublicParams,
- ),
- ),
- model=BenchmarkView,
- )
-
- def start_run(
- self,
- *,
- benchmark_id: str,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- run_name: Optional[str] | Omit = omit,
- run_profile: Optional[RunProfile] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkRunView:
- """
- Start a new BenchmarkRun based on the provided Benchmark.
-
- Args:
- benchmark_id: ID of the Benchmark to run.
-
- metadata: User defined metadata to attach to the benchmark run for organization.
-
- run_name: Display name of the run.
-
- run_profile: Runtime configuration to use for this benchmark run
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return self._post(
- "/v1/benchmarks/start_run",
- body=maybe_transform(
- {
- "benchmark_id": benchmark_id,
- "metadata": metadata,
- "run_name": run_name,
- "run_profile": run_profile,
- },
- benchmark_start_run_params.BenchmarkStartRunParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkRunView,
- )
-
- def update_scenarios(
- self,
- id: str,
- *,
- scenarios_to_add: Optional[SequenceNotStr[str]] | Omit = omit,
- scenarios_to_remove: Optional[SequenceNotStr[str]] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkView:
- """
- Add and/or remove Scenario IDs from an existing Benchmark.
-
- Args:
- scenarios_to_add: Scenario IDs to add to the Benchmark.
-
- scenarios_to_remove: Scenario IDs to remove from the Benchmark.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/v1/benchmarks/{id}/scenarios", id=id),
- body=maybe_transform(
- {
- "scenarios_to_add": scenarios_to_add,
- "scenarios_to_remove": scenarios_to_remove,
- },
- benchmark_update_scenarios_params.BenchmarkUpdateScenariosParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkView,
- )
-
-
-class AsyncBenchmarksResource(AsyncAPIResource):
- @cached_property
- def with_raw_response(self) -> AsyncBenchmarksResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return AsyncBenchmarksResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncBenchmarksResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return AsyncBenchmarksResourceWithStreamingResponse(self)
-
- async def create(
- self,
- *,
- name: str,
- attribution: Optional[str] | Omit = omit,
- description: Optional[str] | Omit = omit,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- required_environment_variables: Optional[SequenceNotStr[str]] | Omit = omit,
- required_secret_names: SequenceNotStr[str] | Omit = omit,
- scenario_ids: Optional[SequenceNotStr[str]] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkView:
- """
- Create a Benchmark with a set of Scenarios.
-
- Args:
- name: The unique name of the Benchmark.
-
- attribution: Attribution information for the benchmark.
-
- description: Detailed description of the benchmark.
-
- metadata: User defined metadata to attach to the benchmark.
-
- required_environment_variables: Environment variables required to run the benchmark. If any required variables
- are not supplied, the benchmark will fail to start.
-
- required_secret_names: Secrets required to run the benchmark with (environment variable name will be
- mapped to the your user secret by name). If any of these secrets are not
- provided or the mapping is incorrect, the benchmark will fail to start.
-
- scenario_ids: The Scenario IDs that make up the Benchmark.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return await self._post(
- "/v1/benchmarks",
- body=await async_maybe_transform(
- {
- "name": name,
- "attribution": attribution,
- "description": description,
- "metadata": metadata,
- "required_environment_variables": required_environment_variables,
- "required_secret_names": required_secret_names,
- "scenario_ids": scenario_ids,
- },
- benchmark_create_params.BenchmarkCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkView,
- )
-
- async def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> BenchmarkView:
- """
- Get a previously created Benchmark.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._get(
- path_template("/v1/benchmarks/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=BenchmarkView,
- )
-
- async def update(
- self,
- id: str,
- *,
- attribution: Optional[str] | Omit = omit,
- description: Optional[str] | Omit = omit,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- name: Optional[str] | Omit = omit,
- required_environment_variables: Optional[SequenceNotStr[str]] | Omit = omit,
- required_secret_names: Optional[SequenceNotStr[str]] | Omit = omit,
- scenario_ids: Optional[SequenceNotStr[str]] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkView:
- """Update a Benchmark.
-
- Fields that are null will preserve the existing value.
- Fields that are provided (including empty values) will replace the existing
- value entirely.
-
- Args:
- attribution: Attribution information for the benchmark. Pass in empty string to clear.
-
- description: Detailed description of the benchmark. Pass in empty string to clear.
-
- metadata: User defined metadata to attach to the benchmark. Pass in empty map to clear.
-
- name: The unique name of the Benchmark. Cannot be blank.
-
- required_environment_variables: Environment variables required to run the benchmark. If any required variables
- are not supplied, the benchmark will fail to start. Pass in empty list to clear.
-
- required_secret_names: Secrets required to run the benchmark with (environment variable name will be
- mapped to the your user secret by name). If any of these secrets are not
- provided or the mapping is incorrect, the benchmark will fail to start. Pass in
- empty list to clear.
-
- scenario_ids: The Scenario IDs that make up the Benchmark. Pass in empty list to clear.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/v1/benchmarks/{id}", id=id),
- body=await async_maybe_transform(
- {
- "attribution": attribution,
- "description": description,
- "metadata": metadata,
- "name": name,
- "required_environment_variables": required_environment_variables,
- "required_secret_names": required_secret_names,
- "scenario_ids": scenario_ids,
- },
- benchmark_update_params.BenchmarkUpdateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkView,
- )
-
- def list(
- self,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AsyncPaginator[BenchmarkView, AsyncBenchmarksCursorIDPage[BenchmarkView]]:
- """
- List all Benchmarks matching filter.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter by name
-
- search: Search by benchmark ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/benchmarks",
- page=AsyncBenchmarksCursorIDPage[BenchmarkView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- },
- benchmark_list_params.BenchmarkListParams,
- ),
- ),
- model=BenchmarkView,
- )
-
- async def definitions(
- self,
- id: str,
- *,
- limit: int | Omit = omit,
- starting_after: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ScenarioDefinitionListView:
- """
- Get scenario definitions for a previously created Benchmark.
-
- Args:
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._get(
- path_template("/v1/benchmarks/{id}/definitions", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {
- "limit": limit,
- "starting_after": starting_after,
- },
- benchmark_definitions_params.BenchmarkDefinitionsParams,
- ),
- ),
- cast_to=ScenarioDefinitionListView,
- )
-
- def list_public(
- self,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AsyncPaginator[BenchmarkView, AsyncBenchmarksCursorIDPage[BenchmarkView]]:
- """
- List all public benchmarks matching filter.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter by name
-
- search: Search by benchmark ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/benchmarks/list_public",
- page=AsyncBenchmarksCursorIDPage[BenchmarkView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- },
- benchmark_list_public_params.BenchmarkListPublicParams,
- ),
- ),
- model=BenchmarkView,
- )
-
- async def start_run(
- self,
- *,
- benchmark_id: str,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- run_name: Optional[str] | Omit = omit,
- run_profile: Optional[RunProfile] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkRunView:
- """
- Start a new BenchmarkRun based on the provided Benchmark.
-
- Args:
- benchmark_id: ID of the Benchmark to run.
-
- metadata: User defined metadata to attach to the benchmark run for organization.
-
- run_name: Display name of the run.
-
- run_profile: Runtime configuration to use for this benchmark run
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return await self._post(
- "/v1/benchmarks/start_run",
- body=await async_maybe_transform(
- {
- "benchmark_id": benchmark_id,
- "metadata": metadata,
- "run_name": run_name,
- "run_profile": run_profile,
- },
- benchmark_start_run_params.BenchmarkStartRunParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkRunView,
- )
-
- async def update_scenarios(
- self,
- id: str,
- *,
- scenarios_to_add: Optional[SequenceNotStr[str]] | Omit = omit,
- scenarios_to_remove: Optional[SequenceNotStr[str]] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BenchmarkView:
- """
- Add and/or remove Scenario IDs from an existing Benchmark.
-
- Args:
- scenarios_to_add: Scenario IDs to add to the Benchmark.
-
- scenarios_to_remove: Scenario IDs to remove from the Benchmark.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/v1/benchmarks/{id}/scenarios", id=id),
- body=await async_maybe_transform(
- {
- "scenarios_to_add": scenarios_to_add,
- "scenarios_to_remove": scenarios_to_remove,
- },
- benchmark_update_scenarios_params.BenchmarkUpdateScenariosParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BenchmarkView,
- )
-
-
-class BenchmarksResourceWithRawResponse:
- def __init__(self, benchmarks: BenchmarksResource) -> None:
- self._benchmarks = benchmarks
-
- self.create = to_raw_response_wrapper(
- benchmarks.create,
- )
- self.retrieve = to_raw_response_wrapper(
- benchmarks.retrieve,
- )
- self.update = to_raw_response_wrapper(
- benchmarks.update,
- )
- self.list = to_raw_response_wrapper(
- benchmarks.list,
- )
- self.definitions = to_raw_response_wrapper(
- benchmarks.definitions,
- )
- self.list_public = to_raw_response_wrapper(
- benchmarks.list_public,
- )
- self.start_run = to_raw_response_wrapper(
- benchmarks.start_run,
- )
- self.update_scenarios = to_raw_response_wrapper(
- benchmarks.update_scenarios,
- )
-
-
-class AsyncBenchmarksResourceWithRawResponse:
- def __init__(self, benchmarks: AsyncBenchmarksResource) -> None:
- self._benchmarks = benchmarks
-
- self.create = async_to_raw_response_wrapper(
- benchmarks.create,
- )
- self.retrieve = async_to_raw_response_wrapper(
- benchmarks.retrieve,
- )
- self.update = async_to_raw_response_wrapper(
- benchmarks.update,
- )
- self.list = async_to_raw_response_wrapper(
- benchmarks.list,
- )
- self.definitions = async_to_raw_response_wrapper(
- benchmarks.definitions,
- )
- self.list_public = async_to_raw_response_wrapper(
- benchmarks.list_public,
- )
- self.start_run = async_to_raw_response_wrapper(
- benchmarks.start_run,
- )
- self.update_scenarios = async_to_raw_response_wrapper(
- benchmarks.update_scenarios,
- )
-
-
-class BenchmarksResourceWithStreamingResponse:
- def __init__(self, benchmarks: BenchmarksResource) -> None:
- self._benchmarks = benchmarks
-
- self.create = to_streamed_response_wrapper(
- benchmarks.create,
- )
- self.retrieve = to_streamed_response_wrapper(
- benchmarks.retrieve,
- )
- self.update = to_streamed_response_wrapper(
- benchmarks.update,
- )
- self.list = to_streamed_response_wrapper(
- benchmarks.list,
- )
- self.definitions = to_streamed_response_wrapper(
- benchmarks.definitions,
- )
- self.list_public = to_streamed_response_wrapper(
- benchmarks.list_public,
- )
- self.start_run = to_streamed_response_wrapper(
- benchmarks.start_run,
- )
- self.update_scenarios = to_streamed_response_wrapper(
- benchmarks.update_scenarios,
- )
-
-
-class AsyncBenchmarksResourceWithStreamingResponse:
- def __init__(self, benchmarks: AsyncBenchmarksResource) -> None:
- self._benchmarks = benchmarks
-
- self.create = async_to_streamed_response_wrapper(
- benchmarks.create,
- )
- self.retrieve = async_to_streamed_response_wrapper(
- benchmarks.retrieve,
- )
- self.update = async_to_streamed_response_wrapper(
- benchmarks.update,
- )
- self.list = async_to_streamed_response_wrapper(
- benchmarks.list,
- )
- self.definitions = async_to_streamed_response_wrapper(
- benchmarks.definitions,
- )
- self.list_public = async_to_streamed_response_wrapper(
- benchmarks.list_public,
- )
- self.start_run = async_to_streamed_response_wrapper(
- benchmarks.start_run,
- )
- self.update_scenarios = async_to_streamed_response_wrapper(
- benchmarks.update_scenarios,
- )
diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py
index c0a20f10e..d07323733 100644
--- a/src/runloop_api_client/resources/devboxes/devboxes.py
+++ b/src/runloop_api_client/resources/devboxes/devboxes.py
@@ -102,7 +102,6 @@
from ...types.devbox_eviction_event_view import DevboxEvictionEventView
from ...types.devbox_resource_usage_view import DevboxResourceUsageView
from ...types.devbox_execution_detail_view import DevboxExecutionDetailView
-from ...types.devbox_create_ssh_key_response import DevboxCreateSSHKeyResponse
from ...types.shared_params.launch_parameters import LaunchParameters
from ...types.devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView
from ...types.shared_params.code_mount_parameters import CodeMountParameters
@@ -776,46 +775,6 @@ def create_pty_tunnel(
cast_to=PtyTunnelView,
)
- def create_ssh_key(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> DevboxCreateSSHKeyResponse:
- """
- Create an SSH key for a Devbox to enable remote access.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/v1/devboxes/{id}/create_ssh_key", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=DevboxCreateSSHKeyResponse,
- )
-
def delete_disk_snapshot(
self,
id: str,
@@ -926,10 +885,12 @@ def enable_tunnel(
URL-based access to the Devbox without exposing internal IDs. The tunnel URL
format is: https://{port}-{tunnel_key}.tunnel.runloop.ai
- Each Devbox can have one tunnel.
+ Each Devbox can have one tunnel. If tunnel config is omitted, or auth_mode is
+ omitted, the tunnel defaults to open (public hostname, no bearer token).
Args:
- auth_mode: Authentication mode for the tunnel. Defaults to 'public' if not specified.
+ auth_mode: Authentication mode for the tunnel. Defaults to open if omitted: the hostname is
+ a public capability and does not require a tunnel bearer token.
http_keep_alive: When true, HTTP traffic through the tunnel counts as activity for idle lifecycle
policies, resetting the idle timer. Defaults to true if not specified.
@@ -979,7 +940,6 @@ def execute(
command_id: str | None = None,
last_n: str | Omit = omit,
optimistic_timeout: Optional[int] | Omit = omit,
- shell_name: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -996,10 +956,8 @@ def execute(
support.
Args:
- command: The command to execute via the Devbox shell. By default, commands are run from
- the user home directory unless shell_name is specified. If shell_name is
- specified the command is run from the directory based on the recent state of the
- persistent shell.
+ command: The command to execute via the Devbox shell. Commands are run from the user home
+ directory in a fresh login shell.
command_id: The command ID in UUIDv7 string format for idempotency and tracking.
A fresh UUID is generated per call if not provided.
@@ -1009,10 +967,6 @@ def execute(
optimistic_timeout: Timeout in seconds to wait for command completion, up to 25 seconds. Defaults to
25 seconds. Operation is not killed.
- shell_name: The name of the persistent shell to create or use if already created. When using
- a persistent shell, the command will run from the directory at the end of the
- previous command and environment variables will be preserved.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -1036,7 +990,6 @@ def execute(
"command": command,
"command_id": command_id,
"optimistic_timeout": optimistic_timeout,
- "shell_name": shell_name,
},
devbox_execute_params.DevboxExecuteParams,
),
@@ -1059,7 +1012,6 @@ def execute_and_await_completion(
command_id: str | None = None,
last_n: str | Omit = omit,
optimistic_timeout: Optional[int] | Omit = omit,
- shell_name: Optional[str] | Omit = omit,
polling_config: PollingConfig | None = None,
# The following are forwarded to the initial execute request
extra_headers: Headers | None = None,
@@ -1086,7 +1038,6 @@ def execute_and_await_completion(
command_id=command_id,
last_n=last_n,
optimistic_timeout=optimistic_timeout,
- shell_name=shell_name,
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
@@ -1127,7 +1078,6 @@ def execute_async(
*,
command: str,
attach_stdin: Optional[bool] | Omit = omit,
- shell_name: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -1141,18 +1091,12 @@ def execute_async(
execution that can be used to track the command's progress.
Args:
- command: The command to execute via the Devbox shell. By default, commands are run from
- the user home directory unless shell_name is specified. If shell_name is
- specified the command is run from the directory based on the recent state of the
- persistent shell.
+ command: The command to execute via the Devbox shell. Commands are run from the user home
+ directory in a fresh login shell.
attach_stdin: Whether to attach stdin streaming for async commands. Not valid for execute_sync
endpoint. Defaults to false if not specified.
- shell_name: The name of the persistent shell to create or use if already created. When using
- a persistent shell, the command will run from the directory at the end of the
- previous command and environment variables will be preserved.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -1171,7 +1115,6 @@ def execute_async(
{
"command": command,
"attach_stdin": attach_stdin,
- "shell_name": shell_name,
},
devbox_execute_async_params.DevboxExecuteAsyncParams,
),
@@ -1193,7 +1136,6 @@ def execute_sync(
*,
command: str,
attach_stdin: Optional[bool] | Omit = omit,
- shell_name: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -1211,18 +1153,12 @@ def execute_sync(
Use execute, execute_async, or execute_and_await_completion instead.
Args:
- command: The command to execute via the Devbox shell. By default, commands are run from
- the user home directory unless shell_name is specified. If shell_name is
- specified the command is run from the directory based on the recent state of the
- persistent shell.
+ command: The command to execute via the Devbox shell. Commands are run from the user home
+ directory in a fresh login shell.
attach_stdin: Whether to attach stdin streaming for async commands. Not valid for execute_sync
endpoint. Defaults to false if not specified.
- shell_name: The name of the persistent shell to create or use if already created. When using
- a persistent shell, the command will run from the directory at the end of the
- previous command and environment variables will be preserved.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -1243,7 +1179,6 @@ def execute_sync(
{
"command": command,
"attach_stdin": attach_stdin,
- "shell_name": shell_name,
},
devbox_execute_sync_params.DevboxExecuteSyncParams,
),
@@ -2612,46 +2547,6 @@ async def create_pty_tunnel(
cast_to=PtyTunnelView,
)
- async def create_ssh_key(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> DevboxCreateSSHKeyResponse:
- """
- Create an SSH key for a Devbox to enable remote access.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/v1/devboxes/{id}/create_ssh_key", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=DevboxCreateSSHKeyResponse,
- )
-
async def delete_disk_snapshot(
self,
id: str,
@@ -2762,10 +2657,12 @@ async def enable_tunnel(
URL-based access to the Devbox without exposing internal IDs. The tunnel URL
format is: https://{port}-{tunnel_key}.tunnel.runloop.ai
- Each Devbox can have one tunnel.
+ Each Devbox can have one tunnel. If tunnel config is omitted, or auth_mode is
+ omitted, the tunnel defaults to open (public hostname, no bearer token).
Args:
- auth_mode: Authentication mode for the tunnel. Defaults to 'public' if not specified.
+ auth_mode: Authentication mode for the tunnel. Defaults to open if omitted: the hostname is
+ a public capability and does not require a tunnel bearer token.
http_keep_alive: When true, HTTP traffic through the tunnel counts as activity for idle lifecycle
policies, resetting the idle timer. Defaults to true if not specified.
@@ -2815,7 +2712,6 @@ async def execute(
command_id: str | None = None,
last_n: str | Omit = omit,
optimistic_timeout: Optional[int] | Omit = omit,
- shell_name: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -2832,10 +2728,8 @@ async def execute(
support.
Args:
- command: The command to execute via the Devbox shell. By default, commands are run from
- the user home directory unless shell_name is specified. If shell_name is
- specified the command is run from the directory based on the recent state of the
- persistent shell.
+ command: The command to execute via the Devbox shell. Commands are run from the user home
+ directory in a fresh login shell.
command_id: The command ID in UUIDv7 string format for idempotency and tracking.
A fresh UUID is generated per call if not provided.
@@ -2845,10 +2739,6 @@ async def execute(
optimistic_timeout: Timeout in seconds to wait for command completion, up to 25 seconds. Defaults to
25 seconds. Operation is not killed.
- shell_name: The name of the persistent shell to create or use if already created. When using
- a persistent shell, the command will run from the directory at the end of the
- previous command and environment variables will be preserved.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -2872,7 +2762,6 @@ async def execute(
"command": command,
"command_id": command_id,
"optimistic_timeout": optimistic_timeout,
- "shell_name": shell_name,
},
devbox_execute_params.DevboxExecuteParams,
),
@@ -2895,7 +2784,6 @@ async def execute_and_await_completion(
command_id: str | None = None,
last_n: str | Omit = omit,
optimistic_timeout: Optional[int] | Omit = omit,
- shell_name: Optional[str] | Omit = omit,
polling_config: PollingConfig | None = None,
# The following are forwarded to the initial execute request
extra_headers: Headers | None = None,
@@ -2921,7 +2809,6 @@ async def execute_and_await_completion(
command_id=command_id,
last_n=last_n,
optimistic_timeout=optimistic_timeout,
- shell_name=shell_name,
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
@@ -2962,7 +2849,6 @@ async def execute_async(
*,
command: str,
attach_stdin: Optional[bool] | Omit = omit,
- shell_name: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -2976,18 +2862,12 @@ async def execute_async(
execution that can be used to track the command's progress.
Args:
- command: The command to execute via the Devbox shell. By default, commands are run from
- the user home directory unless shell_name is specified. If shell_name is
- specified the command is run from the directory based on the recent state of the
- persistent shell.
+ command: The command to execute via the Devbox shell. Commands are run from the user home
+ directory in a fresh login shell.
attach_stdin: Whether to attach stdin streaming for async commands. Not valid for execute_sync
endpoint. Defaults to false if not specified.
- shell_name: The name of the persistent shell to create or use if already created. When using
- a persistent shell, the command will run from the directory at the end of the
- previous command and environment variables will be preserved.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -3006,7 +2886,6 @@ async def execute_async(
{
"command": command,
"attach_stdin": attach_stdin,
- "shell_name": shell_name,
},
devbox_execute_async_params.DevboxExecuteAsyncParams,
),
@@ -3028,7 +2907,6 @@ async def execute_sync(
*,
command: str,
attach_stdin: Optional[bool] | Omit = omit,
- shell_name: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -3046,18 +2924,12 @@ async def execute_sync(
Use execute, execute_async, or execute_and_await_completion instead.
Args:
- command: The command to execute via the Devbox shell. By default, commands are run from
- the user home directory unless shell_name is specified. If shell_name is
- specified the command is run from the directory based on the recent state of the
- persistent shell.
+ command: The command to execute via the Devbox shell. Commands are run from the user home
+ directory in a fresh login shell.
attach_stdin: Whether to attach stdin streaming for async commands. Not valid for execute_sync
endpoint. Defaults to false if not specified.
- shell_name: The name of the persistent shell to create or use if already created. When using
- a persistent shell, the command will run from the directory at the end of the
- previous command and environment variables will be preserved.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -3078,7 +2950,6 @@ async def execute_sync(
{
"command": command,
"attach_stdin": attach_stdin,
- "shell_name": shell_name,
},
devbox_execute_sync_params.DevboxExecuteSyncParams,
),
@@ -3831,9 +3702,6 @@ def __init__(self, devboxes: DevboxesResource) -> None:
self.create_pty_tunnel = to_raw_response_wrapper(
devboxes.create_pty_tunnel,
)
- self.create_ssh_key = to_raw_response_wrapper(
- devboxes.create_ssh_key,
- )
self.delete_disk_snapshot = to_raw_response_wrapper(
devboxes.delete_disk_snapshot,
)
@@ -3936,9 +3804,6 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None:
self.create_pty_tunnel = async_to_raw_response_wrapper(
devboxes.create_pty_tunnel,
)
- self.create_ssh_key = async_to_raw_response_wrapper(
- devboxes.create_ssh_key,
- )
self.delete_disk_snapshot = async_to_raw_response_wrapper(
devboxes.delete_disk_snapshot,
)
@@ -4041,9 +3906,6 @@ def __init__(self, devboxes: DevboxesResource) -> None:
self.create_pty_tunnel = to_streamed_response_wrapper(
devboxes.create_pty_tunnel,
)
- self.create_ssh_key = to_streamed_response_wrapper(
- devboxes.create_ssh_key,
- )
self.delete_disk_snapshot = to_streamed_response_wrapper(
devboxes.delete_disk_snapshot,
)
@@ -4146,9 +4008,6 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None:
self.create_pty_tunnel = async_to_streamed_response_wrapper(
devboxes.create_pty_tunnel,
)
- self.create_ssh_key = async_to_streamed_response_wrapper(
- devboxes.create_ssh_key,
- )
self.delete_disk_snapshot = async_to_streamed_response_wrapper(
devboxes.delete_disk_snapshot,
)
diff --git a/src/runloop_api_client/resources/devboxes/executions.py b/src/runloop_api_client/resources/devboxes/executions.py
index 4b0e3d4c4..d42dc81e6 100755
--- a/src/runloop_api_client/resources/devboxes/executions.py
+++ b/src/runloop_api_client/resources/devboxes/executions.py
@@ -158,7 +158,6 @@ def execute_async(
*,
command: str,
attach_stdin: Optional[bool] | Omit = omit,
- shell_name: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -172,18 +171,12 @@ def execute_async(
execution that can be used to track the command's progress.
Args:
- command: The command to execute via the Devbox shell. By default, commands are run from
- the user home directory unless shell_name is specified. If shell_name is
- specified the command is run from the directory based on the recent state of the
- persistent shell.
+ command: The command to execute via the Devbox shell. Commands are run from the user home
+ directory in a fresh login shell.
attach_stdin: Whether to attach stdin streaming for async commands. Not valid for execute_sync
endpoint. Defaults to false if not specified.
- shell_name: The name of the persistent shell to create or use if already created. When using
- a persistent shell, the command will run from the directory at the end of the
- previous command and environment variables will be preserved.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -202,7 +195,6 @@ def execute_async(
{
"command": command,
"attach_stdin": attach_stdin,
- "shell_name": shell_name,
},
execution_execute_async_params.ExecutionExecuteAsyncParams,
),
@@ -223,7 +215,6 @@ def execute_sync(
*,
command: str,
attach_stdin: Optional[bool] | Omit = omit,
- shell_name: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -238,18 +229,12 @@ def execute_sync(
execution.
Args:
- command: The command to execute via the Devbox shell. By default, commands are run from
- the user home directory unless shell_name is specified. If shell_name is
- specified the command is run from the directory based on the recent state of the
- persistent shell.
+ command: The command to execute via the Devbox shell. Commands are run from the user home
+ directory in a fresh login shell.
attach_stdin: Whether to attach stdin streaming for async commands. Not valid for execute_sync
endpoint. Defaults to false if not specified.
- shell_name: The name of the persistent shell to create or use if already created. When using
- a persistent shell, the command will run from the directory at the end of the
- previous command and environment variables will be preserved.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -270,7 +255,6 @@ def execute_sync(
{
"command": command,
"attach_stdin": attach_stdin,
- "shell_name": shell_name,
},
execution_execute_sync_params.ExecutionExecuteSyncParams,
),
@@ -352,8 +336,12 @@ def send_std_in(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
idempotency_key: str | None = None,
) -> DevboxSendStdInResult:
- """
- Send content to the Std In of a running execution.
+ """Send nonempty text or a signal to an execution started with attach_stdin
+ enabled.
+
+ The request body is required. Supply exactly one of nonempty text or a
+ non-null signal. Wait for each send before sending more input or EOF; concurrent
+ requests are not ordered and retries after connection failures can replay input.
Args:
signal: Signal to send to std in of the running execution.
@@ -690,7 +678,6 @@ async def execute_async(
*,
command: str,
attach_stdin: Optional[bool] | Omit = omit,
- shell_name: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -704,18 +691,12 @@ async def execute_async(
execution that can be used to track the command's progress.
Args:
- command: The command to execute via the Devbox shell. By default, commands are run from
- the user home directory unless shell_name is specified. If shell_name is
- specified the command is run from the directory based on the recent state of the
- persistent shell.
+ command: The command to execute via the Devbox shell. Commands are run from the user home
+ directory in a fresh login shell.
attach_stdin: Whether to attach stdin streaming for async commands. Not valid for execute_sync
endpoint. Defaults to false if not specified.
- shell_name: The name of the persistent shell to create or use if already created. When using
- a persistent shell, the command will run from the directory at the end of the
- previous command and environment variables will be preserved.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -734,7 +715,6 @@ async def execute_async(
{
"command": command,
"attach_stdin": attach_stdin,
- "shell_name": shell_name,
},
execution_execute_async_params.ExecutionExecuteAsyncParams,
),
@@ -755,7 +735,6 @@ async def execute_sync(
*,
command: str,
attach_stdin: Optional[bool] | Omit = omit,
- shell_name: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -770,18 +749,12 @@ async def execute_sync(
execution.
Args:
- command: The command to execute via the Devbox shell. By default, commands are run from
- the user home directory unless shell_name is specified. If shell_name is
- specified the command is run from the directory based on the recent state of the
- persistent shell.
+ command: The command to execute via the Devbox shell. Commands are run from the user home
+ directory in a fresh login shell.
attach_stdin: Whether to attach stdin streaming for async commands. Not valid for execute_sync
endpoint. Defaults to false if not specified.
- shell_name: The name of the persistent shell to create or use if already created. When using
- a persistent shell, the command will run from the directory at the end of the
- previous command and environment variables will be preserved.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -802,7 +775,6 @@ async def execute_sync(
{
"command": command,
"attach_stdin": attach_stdin,
- "shell_name": shell_name,
},
execution_execute_sync_params.ExecutionExecuteSyncParams,
),
@@ -886,8 +858,12 @@ async def send_std_in(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
idempotency_key: str | None = None,
) -> DevboxSendStdInResult:
- """
- Send content to the Std In of a running execution.
+ """Send nonempty text or a signal to an execution started with attach_stdin
+ enabled.
+
+ The request body is required. Supply exactly one of nonempty text or a
+ non-null signal. Wait for each send before sending more input or EOF; concurrent
+ requests are not ordered and retries after connection failures can replay input.
Args:
signal: Signal to send to std in of the running execution.
diff --git a/src/runloop_api_client/resources/devboxes/logs.py b/src/runloop_api_client/resources/devboxes/logs.py
index 1e7383914..4d71e8924 100644
--- a/src/runloop_api_client/resources/devboxes/logs.py
+++ b/src/runloop_api_client/resources/devboxes/logs.py
@@ -46,7 +46,6 @@ def list(
id: str,
*,
execution_id: str | Omit = omit,
- shell_name: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -60,8 +59,6 @@ def list(
Args:
execution_id: ID of execution to filter logs by.
- shell_name: Shell Name to filter logs by.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -79,13 +76,7 @@ def list(
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
- query=maybe_transform(
- {
- "execution_id": execution_id,
- "shell_name": shell_name,
- },
- log_list_params.LogListParams,
- ),
+ query=maybe_transform({"execution_id": execution_id}, log_list_params.LogListParams),
),
cast_to=DevboxLogsListView,
)
@@ -116,7 +107,6 @@ async def list(
id: str,
*,
execution_id: str | Omit = omit,
- shell_name: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -130,8 +120,6 @@ async def list(
Args:
execution_id: ID of execution to filter logs by.
- shell_name: Shell Name to filter logs by.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -149,13 +137,7 @@ async def list(
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
- query=await async_maybe_transform(
- {
- "execution_id": execution_id,
- "shell_name": shell_name,
- },
- log_list_params.LogListParams,
- ),
+ query=await async_maybe_transform({"execution_id": execution_id}, log_list_params.LogListParams),
),
cast_to=DevboxLogsListView,
)
diff --git a/src/runloop_api_client/resources/gateway_configs.py b/src/runloop_api_client/resources/gateway_configs.py
index 36e00fd2c..8e0d349d0 100644
--- a/src/runloop_api_client/resources/gateway_configs.py
+++ b/src/runloop_api_client/resources/gateway_configs.py
@@ -73,8 +73,8 @@ def create(
endpoint: The target endpoint URL (e.g., 'https://api.anthropic.com').
- name: The human-readable name for the GatewayConfig. Must be unique within your
- account.
+ name: The human-readable name for the GatewayConfig. Names are labels and may be
+ reused.
custom_headers: Additional headers applied to proxied requests after the auth mechanism. At most
8 entries.
@@ -178,7 +178,7 @@ def update(
endpoint: New target endpoint URL (e.g., 'https://api.anthropic.com').
- name: New name for the GatewayConfig. Must be unique within your account.
+ name: New name for the GatewayConfig. Names are labels and may be reused.
extra_headers: Send extra headers
@@ -368,8 +368,8 @@ async def create(
endpoint: The target endpoint URL (e.g., 'https://api.anthropic.com').
- name: The human-readable name for the GatewayConfig. Must be unique within your
- account.
+ name: The human-readable name for the GatewayConfig. Names are labels and may be
+ reused.
custom_headers: Additional headers applied to proxied requests after the auth mechanism. At most
8 entries.
@@ -473,7 +473,7 @@ async def update(
endpoint: New target endpoint URL (e.g., 'https://api.anthropic.com').
- name: New name for the GatewayConfig. Must be unique within your account.
+ name: New name for the GatewayConfig. Names are labels and may be reused.
extra_headers: Send extra headers
diff --git a/src/runloop_api_client/resources/mcp_configs.py b/src/runloop_api_client/resources/mcp_configs.py
index c8b37a096..f9917375e 100644
--- a/src/runloop_api_client/resources/mcp_configs.py
+++ b/src/runloop_api_client/resources/mcp_configs.py
@@ -75,7 +75,7 @@ def create(
endpoint: The target MCP server endpoint URL (e.g., 'https://mcp.example.com').
- name: The human-readable name for the McpConfig. Must be unique within your account.
+ name: The human-readable name for the McpConfig. Names are labels and may be reused.
The first segment before '-' is used as the service name for tool routing (e.g.,
'github-readonly' uses 'github' as the service name).
@@ -189,7 +189,7 @@ def update(
endpoint: New target MCP server endpoint URL.
- name: New name for the McpConfig. Must be unique within your account.
+ name: New name for the McpConfig. Names are labels and may be reused.
extra_headers: Send extra headers
@@ -381,7 +381,7 @@ async def create(
endpoint: The target MCP server endpoint URL (e.g., 'https://mcp.example.com').
- name: The human-readable name for the McpConfig. Must be unique within your account.
+ name: The human-readable name for the McpConfig. Names are labels and may be reused.
The first segment before '-' is used as the service name for tool routing (e.g.,
'github-readonly' uses 'github' as the service name).
@@ -495,7 +495,7 @@ async def update(
endpoint: New target MCP server endpoint URL.
- name: New name for the McpConfig. Must be unique within your account.
+ name: New name for the McpConfig. Names are labels and may be reused.
extra_headers: Send extra headers
diff --git a/src/runloop_api_client/resources/network_policies.py b/src/runloop_api_client/resources/network_policies.py
index 135eabea6..474fa7229 100644
--- a/src/runloop_api_client/resources/network_policies.py
+++ b/src/runloop_api_client/resources/network_policies.py
@@ -53,7 +53,6 @@ def create(
allow_all: Optional[bool] | Omit = omit,
allow_devbox_to_devbox: Optional[bool] | Omit = omit,
allow_mcp_gateway: Optional[bool] | Omit = omit,
- allow_runloop_mirrors: Optional[bool] | Omit = omit,
allowed_cidrs: Optional[Iterable[AllowedCidrParam]] | Omit = omit,
allowed_hostnames: Optional[SequenceNotStr[str]] | Omit = omit,
description: Optional[str] | Omit = omit,
@@ -71,8 +70,8 @@ def create(
be applied to blueprints, devboxes, or snapshot resumes.
Args:
- name: The human-readable name for the NetworkPolicy. Must be unique within the
- account.
+ name: The human-readable name for the NetworkPolicy. Names are labels and may be
+ reused.
allow_agent_gateway: (Optional) If true, allows devbox egress to the agent gateway for credential
proxying. Defaults to false.
@@ -87,9 +86,6 @@ def create(
allow_mcp_gateway: (Optional) If true, allows devbox egress to the MCP hub for MCP server access.
Defaults to false.
- allow_runloop_mirrors: (Optional) If true, allows devbox egress to Runloop's package/image registry
- mirrors. Defaults to false. Implicitly allowed when allow_all is true.
-
allowed_cidrs: (Optional) IPv4 CIDR-based allow list with optional port restrictions, additive
with allowed_hostnames. Example: [{'cidr': '10.12.0.0/16', 'ports': [{'port':
443}]}].
@@ -118,7 +114,6 @@ def create(
"allow_all": allow_all,
"allow_devbox_to_devbox": allow_devbox_to_devbox,
"allow_mcp_gateway": allow_mcp_gateway,
- "allow_runloop_mirrors": allow_runloop_mirrors,
"allowed_cidrs": allowed_cidrs,
"allowed_hostnames": allowed_hostnames,
"description": description,
@@ -176,7 +171,6 @@ def update(
allow_all: Optional[bool] | Omit = omit,
allow_devbox_to_devbox: Optional[bool] | Omit = omit,
allow_mcp_gateway: Optional[bool] | Omit = omit,
- allow_runloop_mirrors: Optional[bool] | Omit = omit,
allowed_cidrs: Optional[Iterable[AllowedCidrParam]] | Omit = omit,
allowed_hostnames: Optional[SequenceNotStr[str]] | Omit = omit,
description: Optional[str] | Omit = omit,
@@ -203,9 +197,6 @@ def update(
allow_mcp_gateway: If true, allows devbox egress to the MCP hub.
- allow_runloop_mirrors: If true, allows devbox egress to Runloop's package/image registry mirrors.
- Implicitly allowed when allow_all is true.
-
allowed_cidrs: Updated IPv4 CIDR-based allow list with optional port restrictions, additive
with allowed_hostnames.
@@ -236,7 +227,6 @@ def update(
"allow_all": allow_all,
"allow_devbox_to_devbox": allow_devbox_to_devbox,
"allow_mcp_gateway": allow_mcp_gateway,
- "allow_runloop_mirrors": allow_runloop_mirrors,
"allowed_cidrs": allowed_cidrs,
"allowed_hostnames": allowed_hostnames,
"description": description,
@@ -388,7 +378,6 @@ async def create(
allow_all: Optional[bool] | Omit = omit,
allow_devbox_to_devbox: Optional[bool] | Omit = omit,
allow_mcp_gateway: Optional[bool] | Omit = omit,
- allow_runloop_mirrors: Optional[bool] | Omit = omit,
allowed_cidrs: Optional[Iterable[AllowedCidrParam]] | Omit = omit,
allowed_hostnames: Optional[SequenceNotStr[str]] | Omit = omit,
description: Optional[str] | Omit = omit,
@@ -406,8 +395,8 @@ async def create(
be applied to blueprints, devboxes, or snapshot resumes.
Args:
- name: The human-readable name for the NetworkPolicy. Must be unique within the
- account.
+ name: The human-readable name for the NetworkPolicy. Names are labels and may be
+ reused.
allow_agent_gateway: (Optional) If true, allows devbox egress to the agent gateway for credential
proxying. Defaults to false.
@@ -422,9 +411,6 @@ async def create(
allow_mcp_gateway: (Optional) If true, allows devbox egress to the MCP hub for MCP server access.
Defaults to false.
- allow_runloop_mirrors: (Optional) If true, allows devbox egress to Runloop's package/image registry
- mirrors. Defaults to false. Implicitly allowed when allow_all is true.
-
allowed_cidrs: (Optional) IPv4 CIDR-based allow list with optional port restrictions, additive
with allowed_hostnames. Example: [{'cidr': '10.12.0.0/16', 'ports': [{'port':
443}]}].
@@ -453,7 +439,6 @@ async def create(
"allow_all": allow_all,
"allow_devbox_to_devbox": allow_devbox_to_devbox,
"allow_mcp_gateway": allow_mcp_gateway,
- "allow_runloop_mirrors": allow_runloop_mirrors,
"allowed_cidrs": allowed_cidrs,
"allowed_hostnames": allowed_hostnames,
"description": description,
@@ -511,7 +496,6 @@ async def update(
allow_all: Optional[bool] | Omit = omit,
allow_devbox_to_devbox: Optional[bool] | Omit = omit,
allow_mcp_gateway: Optional[bool] | Omit = omit,
- allow_runloop_mirrors: Optional[bool] | Omit = omit,
allowed_cidrs: Optional[Iterable[AllowedCidrParam]] | Omit = omit,
allowed_hostnames: Optional[SequenceNotStr[str]] | Omit = omit,
description: Optional[str] | Omit = omit,
@@ -538,9 +522,6 @@ async def update(
allow_mcp_gateway: If true, allows devbox egress to the MCP hub.
- allow_runloop_mirrors: If true, allows devbox egress to Runloop's package/image registry mirrors.
- Implicitly allowed when allow_all is true.
-
allowed_cidrs: Updated IPv4 CIDR-based allow list with optional port restrictions, additive
with allowed_hostnames.
@@ -571,7 +552,6 @@ async def update(
"allow_all": allow_all,
"allow_devbox_to_devbox": allow_devbox_to_devbox,
"allow_mcp_gateway": allow_mcp_gateway,
- "allow_runloop_mirrors": allow_runloop_mirrors,
"allowed_cidrs": allowed_cidrs,
"allowed_hostnames": allowed_hostnames,
"description": description,
diff --git a/src/runloop_api_client/resources/scenarios/__init__.py b/src/runloop_api_client/resources/scenarios/__init__.py
deleted file mode 100644
index 4d6bc16cf..000000000
--- a/src/runloop_api_client/resources/scenarios/__init__.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from .runs import (
- RunsResource,
- AsyncRunsResource,
- RunsResourceWithRawResponse,
- AsyncRunsResourceWithRawResponse,
- RunsResourceWithStreamingResponse,
- AsyncRunsResourceWithStreamingResponse,
-)
-from .scorers import (
- ScorersResource,
- AsyncScorersResource,
- ScorersResourceWithRawResponse,
- AsyncScorersResourceWithRawResponse,
- ScorersResourceWithStreamingResponse,
- AsyncScorersResourceWithStreamingResponse,
-)
-from .scenarios import (
- ScenariosResource,
- AsyncScenariosResource,
- ScenariosResourceWithRawResponse,
- AsyncScenariosResourceWithRawResponse,
- ScenariosResourceWithStreamingResponse,
- AsyncScenariosResourceWithStreamingResponse,
-)
-
-__all__ = [
- "RunsResource",
- "AsyncRunsResource",
- "RunsResourceWithRawResponse",
- "AsyncRunsResourceWithRawResponse",
- "RunsResourceWithStreamingResponse",
- "AsyncRunsResourceWithStreamingResponse",
- "ScorersResource",
- "AsyncScorersResource",
- "ScorersResourceWithRawResponse",
- "AsyncScorersResourceWithRawResponse",
- "ScorersResourceWithStreamingResponse",
- "AsyncScorersResourceWithStreamingResponse",
- "ScenariosResource",
- "AsyncScenariosResource",
- "ScenariosResourceWithRawResponse",
- "AsyncScenariosResourceWithRawResponse",
- "ScenariosResourceWithStreamingResponse",
- "AsyncScenariosResourceWithStreamingResponse",
-]
diff --git a/src/runloop_api_client/resources/scenarios/runs.py b/src/runloop_api_client/resources/scenarios/runs.py
deleted file mode 100644
index cb2e15be0..000000000
--- a/src/runloop_api_client/resources/scenarios/runs.py
+++ /dev/null
@@ -1,991 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import httpx
-
-from ..._types import NOT_GIVEN, Body, Omit, Query, Headers, NotGiven, omit, not_given
-from ..._utils import path_template, maybe_transform
-from ..._compat import cached_property
-from ..._resource import SyncAPIResource, AsyncAPIResource
-from ..._response import (
- BinaryAPIResponse,
- AsyncBinaryAPIResponse,
- StreamedBinaryAPIResponse,
- AsyncStreamedBinaryAPIResponse,
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
- async_to_raw_response_wrapper,
- to_custom_raw_response_wrapper,
- async_to_streamed_response_wrapper,
- to_custom_streamed_response_wrapper,
- async_to_custom_raw_response_wrapper,
- async_to_custom_streamed_response_wrapper,
-)
-from ...pagination import SyncBenchmarkRunsCursorIDPage, AsyncBenchmarkRunsCursorIDPage
-from ..._exceptions import RunloopError
-from ...lib.polling import PollingConfig, poll_until
-from ..._base_client import AsyncPaginator, make_request_options
-from ...types.scenarios import run_list_params
-from ...lib.polling_async import async_poll_until
-from ...types.scenario_run_view import ScenarioRunView
-
-__all__ = ["RunsResource", "AsyncRunsResource"]
-
-
-class RunsResource(SyncAPIResource):
- @cached_property
- def with_raw_response(self) -> RunsResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return RunsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> RunsResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return RunsResourceWithStreamingResponse(self)
-
- def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ScenarioRunView:
- """
- Get a ScenarioRun given ID.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._get(
- path_template("/v1/scenarios/runs/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=ScenarioRunView,
- )
-
- def list(
- self,
- *,
- benchmark_run_id: str | Omit = omit,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- scenario_id: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- state: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SyncBenchmarkRunsCursorIDPage[ScenarioRunView]:
- """
- List all ScenarioRuns matching filter.
-
- Args:
- benchmark_run_id: Filter by benchmark run ID
-
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter by name
-
- scenario_id: Filter runs associated to Scenario given ID
-
- search: Search by scenario run ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- state: Filter by state
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/scenarios/runs",
- page=SyncBenchmarkRunsCursorIDPage[ScenarioRunView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "benchmark_run_id": benchmark_run_id,
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "scenario_id": scenario_id,
- "search": search,
- "starting_after": starting_after,
- "state": state,
- },
- run_list_params.RunListParams,
- ),
- ),
- model=ScenarioRunView,
- )
-
- def cancel(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioRunView:
- """Cancel a currently running Scenario run.
-
- This will shutdown the underlying
- Devbox resource.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/v1/scenarios/runs/{id}/cancel", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioRunView,
- )
-
- def complete(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioRunView:
- """Complete a currently running ScenarioRun.
-
- Calling complete will shutdown
- underlying Devbox resource.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/v1/scenarios/runs/{id}/complete", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioRunView,
- )
-
- def download_logs(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> BinaryAPIResponse:
- """
- Download a zip file containing all logs for a Scenario run from the associated
- devbox.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- extra_headers = {"Accept": "application/zip", **(extra_headers or {})}
- return self._post(
- path_template("/v1/scenarios/runs/{id}/download_logs", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=BinaryAPIResponse,
- )
-
- def score(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioRunView:
- """
- Score a currently running ScenarioRun.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/v1/scenarios/runs/{id}/score", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioRunView,
- )
-
- def await_scored(
- self,
- id: str,
- *,
- polling_config: PollingConfig | None = None,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
- ) -> ScenarioRunView:
- """Wait for a scenario run to be scored.
-
- Args:
- id: The ID of the scenario run to wait for
- polling_config: Optional polling configuration
- extra_headers: Send extra headers
- extra_query: Add additional query parameters to the request
- extra_body: Add additional JSON properties to the request
- timeout: Override the client-level default timeout for this request, in seconds
-
- Returns:
- The scored scenario run
-
- Raises:
- PollingTimeout: If polling times out before scenario run is scored
- RunloopError: If scenario run enters a non-scored terminal state
- """
-
- def retrieve_run() -> ScenarioRunView:
- return self.retrieve(
- id, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- )
-
- def is_done_scoring(run: ScenarioRunView) -> bool:
- return run.state not in ["scoring"]
-
- run = poll_until(retrieve_run, is_done_scoring, polling_config)
-
- if run.state != "scored":
- raise RunloopError(f"Scenario run entered non-scored state unexpectedly: {run.state}")
-
- return run
-
- def score_and_await(
- self,
- id: str,
- *,
- polling_config: PollingConfig | None = None,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
- ) -> ScenarioRunView:
- """Score a scenario run and wait for it to be scored.
-
- Args:
- id: The ID of the scenario run to score and wait for
- polling_config: Optional polling configuration
- extra_headers: Send extra headers
- extra_query: Add additional query parameters to the request
- extra_body: Add additional JSON properties to the request
- timeout: Override the client-level default timeout for this request, in seconds
-
- Returns:
- The scored scenario run
-
- Raises:
- PollingTimeout: If polling times out before scenario run is scored
- RunloopError: If scenario run enters a non-scored terminal state
- """
- self.score(
- id,
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- )
-
- return self.await_scored(
- id,
- polling_config=polling_config,
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- )
-
- def score_and_complete(
- self,
- id: str,
- *,
- polling_config: PollingConfig | None = None,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
- ) -> ScenarioRunView:
- """Score a scenario run, wait for it to be scored, then complete it.
-
- Args:
- id: The ID of the scenario run to score, wait for, and complete
- polling_config: Optional polling configuration
- extra_headers: Send extra headers
- extra_query: Add additional query parameters to the request
- extra_body: Add additional JSON properties to the request
- timeout: Override the client-level default timeout for this request, in seconds
-
- Returns:
- The completed scenario run
-
- Raises:
- PollingTimeout: If polling times out before scenario run is scored
- RunloopError: If scenario run enters a non-scored terminal state
- """
- self.score_and_await(
- id,
- polling_config=polling_config,
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- )
-
- return self.complete(
- id,
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- )
-
-
-class AsyncRunsResource(AsyncAPIResource):
- @cached_property
- def with_raw_response(self) -> AsyncRunsResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return AsyncRunsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncRunsResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return AsyncRunsResourceWithStreamingResponse(self)
-
- async def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ScenarioRunView:
- """
- Get a ScenarioRun given ID.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._get(
- path_template("/v1/scenarios/runs/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=ScenarioRunView,
- )
-
- def list(
- self,
- *,
- benchmark_run_id: str | Omit = omit,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- scenario_id: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- state: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AsyncPaginator[ScenarioRunView, AsyncBenchmarkRunsCursorIDPage[ScenarioRunView]]:
- """
- List all ScenarioRuns matching filter.
-
- Args:
- benchmark_run_id: Filter by benchmark run ID
-
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Filter by name
-
- scenario_id: Filter runs associated to Scenario given ID
-
- search: Search by scenario run ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- state: Filter by state
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/scenarios/runs",
- page=AsyncBenchmarkRunsCursorIDPage[ScenarioRunView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "benchmark_run_id": benchmark_run_id,
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "scenario_id": scenario_id,
- "search": search,
- "starting_after": starting_after,
- "state": state,
- },
- run_list_params.RunListParams,
- ),
- ),
- model=ScenarioRunView,
- )
-
- async def cancel(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioRunView:
- """Cancel a currently running Scenario run.
-
- This will shutdown the underlying
- Devbox resource.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/v1/scenarios/runs/{id}/cancel", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioRunView,
- )
-
- async def complete(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioRunView:
- """Complete a currently running ScenarioRun.
-
- Calling complete will shutdown
- underlying Devbox resource.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/v1/scenarios/runs/{id}/complete", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioRunView,
- )
-
- async def download_logs(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> AsyncBinaryAPIResponse:
- """
- Download a zip file containing all logs for a Scenario run from the associated
- devbox.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- extra_headers = {"Accept": "application/zip", **(extra_headers or {})}
- return await self._post(
- path_template("/v1/scenarios/runs/{id}/download_logs", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=AsyncBinaryAPIResponse,
- )
-
- async def score(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioRunView:
- """
- Score a currently running ScenarioRun.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/v1/scenarios/runs/{id}/score", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioRunView,
- )
-
- async def await_scored(
- self,
- id: str,
- *,
- polling_config: PollingConfig | None = None,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
- ) -> ScenarioRunView:
- """Wait for a scenario run to be scored.
-
- Args:
- id: The ID of the scenario run to wait for
- polling_config: Optional polling configuration
- extra_headers: Send extra headers
- extra_query: Add additional query parameters to the request
- extra_body: Add additional JSON properties to the request
- timeout: Override the client-level default timeout for this request, in seconds
-
- Returns:
- The scored scenario run
-
- Raises:
- PollingTimeout: If polling times out before scenario run is scored
- RunloopError: If scenario run enters a non-scored terminal state
- """
-
- async def retrieve_run() -> ScenarioRunView:
- return await self.retrieve(
- id, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- )
-
- def is_done_scoring(run: ScenarioRunView) -> bool:
- return run.state not in ["scoring"]
-
- run = await async_poll_until(retrieve_run, is_done_scoring, polling_config)
-
- if run.state != "scored":
- raise RunloopError(f"Scenario run entered non-scored state unexpectedly: {run.state}")
-
- return run
-
- async def score_and_await(
- self,
- id: str,
- *,
- polling_config: PollingConfig | None = None,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
- ) -> ScenarioRunView:
- """Score a scenario run and wait for it to be scored.
-
- Args:
- id: The ID of the scenario run to score and wait for
- polling_config: Optional polling configuration
- extra_headers: Send extra headers
- extra_query: Add additional query parameters to the request
- extra_body: Add additional JSON properties to the request
- timeout: Override the client-level default timeout for this request, in seconds
-
- Returns:
- The scored scenario run
-
- Raises:
- PollingTimeout: If polling times out before scenario run is scored
- RunloopError: If scenario run enters a non-scored terminal state
- """
- await self.score(
- id,
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- )
-
- return await self.await_scored(
- id,
- polling_config=polling_config,
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- )
-
- async def score_and_complete(
- self,
- id: str,
- *,
- polling_config: PollingConfig | None = None,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
- ) -> ScenarioRunView:
- """Score a scenario run, wait for it to be scored, then complete it.
-
- Args:
- id: The ID of the scenario run to score, wait for, and complete
- polling_config: Optional polling configuration
- extra_headers: Send extra headers
- extra_query: Add additional query parameters to the request
- extra_body: Add additional JSON properties to the request
- timeout: Override the client-level default timeout for this request, in seconds
-
- Returns:
- The completed scenario run
-
- Raises:
- PollingTimeout: If polling times out before scenario run is scored
- RunloopError: If scenario run enters a non-scored terminal state
- """
- await self.score_and_await(
- id,
- polling_config=polling_config,
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- )
-
- return await self.complete(
- id,
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- )
-
-
-class RunsResourceWithRawResponse:
- def __init__(self, runs: RunsResource) -> None:
- self._runs = runs
-
- self.retrieve = to_raw_response_wrapper(
- runs.retrieve,
- )
- self.list = to_raw_response_wrapper(
- runs.list,
- )
- self.cancel = to_raw_response_wrapper(
- runs.cancel,
- )
- self.complete = to_raw_response_wrapper(
- runs.complete,
- )
- self.download_logs = to_custom_raw_response_wrapper(
- runs.download_logs,
- BinaryAPIResponse,
- )
- self.score = to_raw_response_wrapper(
- runs.score,
- )
-
-
-class AsyncRunsResourceWithRawResponse:
- def __init__(self, runs: AsyncRunsResource) -> None:
- self._runs = runs
-
- self.retrieve = async_to_raw_response_wrapper(
- runs.retrieve,
- )
- self.list = async_to_raw_response_wrapper(
- runs.list,
- )
- self.cancel = async_to_raw_response_wrapper(
- runs.cancel,
- )
- self.complete = async_to_raw_response_wrapper(
- runs.complete,
- )
- self.download_logs = async_to_custom_raw_response_wrapper(
- runs.download_logs,
- AsyncBinaryAPIResponse,
- )
- self.score = async_to_raw_response_wrapper(
- runs.score,
- )
-
-
-class RunsResourceWithStreamingResponse:
- def __init__(self, runs: RunsResource) -> None:
- self._runs = runs
-
- self.retrieve = to_streamed_response_wrapper(
- runs.retrieve,
- )
- self.list = to_streamed_response_wrapper(
- runs.list,
- )
- self.cancel = to_streamed_response_wrapper(
- runs.cancel,
- )
- self.complete = to_streamed_response_wrapper(
- runs.complete,
- )
- self.download_logs = to_custom_streamed_response_wrapper(
- runs.download_logs,
- StreamedBinaryAPIResponse,
- )
- self.score = to_streamed_response_wrapper(
- runs.score,
- )
-
-
-class AsyncRunsResourceWithStreamingResponse:
- def __init__(self, runs: AsyncRunsResource) -> None:
- self._runs = runs
-
- self.retrieve = async_to_streamed_response_wrapper(
- runs.retrieve,
- )
- self.list = async_to_streamed_response_wrapper(
- runs.list,
- )
- self.cancel = async_to_streamed_response_wrapper(
- runs.cancel,
- )
- self.complete = async_to_streamed_response_wrapper(
- runs.complete,
- )
- self.download_logs = async_to_custom_streamed_response_wrapper(
- runs.download_logs,
- AsyncStreamedBinaryAPIResponse,
- )
- self.score = async_to_streamed_response_wrapper(
- runs.score,
- )
diff --git a/src/runloop_api_client/resources/scenarios/scenarios.py b/src/runloop_api_client/resources/scenarios/scenarios.py
deleted file mode 100644
index c29613894..000000000
--- a/src/runloop_api_client/resources/scenarios/scenarios.py
+++ /dev/null
@@ -1,1253 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Dict, Optional
-from typing_extensions import Literal
-
-import httpx
-
-from .runs import (
- RunsResource,
- AsyncRunsResource,
- RunsResourceWithRawResponse,
- AsyncRunsResourceWithRawResponse,
- RunsResourceWithStreamingResponse,
- AsyncRunsResourceWithStreamingResponse,
-)
-from ...types import (
- scenario_list_params,
- scenario_create_params,
- scenario_update_params,
- scenario_start_run_params,
- scenario_list_public_params,
-)
-from .scorers import (
- ScorersResource,
- AsyncScorersResource,
- ScorersResourceWithRawResponse,
- AsyncScorersResourceWithRawResponse,
- ScorersResourceWithStreamingResponse,
- AsyncScorersResourceWithStreamingResponse,
-)
-from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
-from ..._utils import path_template, maybe_transform, async_maybe_transform
-from ..._compat import cached_property
-from ..._resource import SyncAPIResource, AsyncAPIResource
-from ..._response import (
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
-)
-from ...pagination import SyncScenariosCursorIDPage, AsyncScenariosCursorIDPage
-from ...lib.polling import PollingConfig
-from ..._base_client import AsyncPaginator, make_request_options
-from ...types.scenario_view import ScenarioView
-from ...types.scenario_run_view import ScenarioRunView
-from ...types.input_context_param import InputContextParam
-from ...types.scoring_contract_param import ScoringContractParam
-from ...types.shared_params.run_profile import RunProfile
-from ...types.input_context_update_param import InputContextUpdateParam
-from ...types.scenario_environment_param import ScenarioEnvironmentParam
-from ...types.scoring_contract_update_param import ScoringContractUpdateParam
-
-__all__ = ["ScenariosResource", "AsyncScenariosResource"]
-
-
-class ScenariosResource(SyncAPIResource):
- @cached_property
- def runs(self) -> RunsResource:
- return RunsResource(self._client)
-
- @cached_property
- def scorers(self) -> ScorersResource:
- return ScorersResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> ScenariosResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return ScenariosResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> ScenariosResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return ScenariosResourceWithStreamingResponse(self)
-
- def create(
- self,
- *,
- input_context: InputContextParam,
- name: str,
- scoring_contract: ScoringContractParam,
- environment_parameters: Optional[ScenarioEnvironmentParam] | Omit = omit,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- reference_output: Optional[str] | Omit = omit,
- required_environment_variables: Optional[SequenceNotStr[str]] | Omit = omit,
- required_secret_names: Optional[SequenceNotStr[str]] | Omit = omit,
- scorer_timeout_sec: Optional[int] | Omit = omit,
- validation_type: Optional[Literal["UNSPECIFIED", "FORWARD", "REVERSE", "EVALUATION"]] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioView:
- """
- Create a Scenario, a repeatable AI coding evaluation test that defines the
- starting environment as well as evaluation success criteria.
-
- Args:
- input_context: The input context for the Scenario.
-
- name: Name of the scenario.
-
- scoring_contract: The scoring contract for the Scenario.
-
- environment_parameters: ScenarioEnvironmentParameters specify the environment in which a Scenario will
- be run.
-
- metadata: User defined metadata to attach to the scenario for organization.
-
- reference_output: A string representation of the reference output to solve the scenario. Commonly
- can be the result of a git diff or a sequence of command actions to apply to the
- environment.
-
- required_environment_variables: Environment variables required to run the scenario. If these variables are not
- provided, the scenario will fail to start.
-
- required_secret_names: Secrets required to run the scenario (user secret name to scenario required
- secret name). If these secrets are not provided or the mapping is incorrect, the
- scenario will fail to start.
-
- scorer_timeout_sec: Timeout for scoring in seconds. Default 30 minutes (1800s).
-
- validation_type: Validation strategy.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return self._post(
- "/v1/scenarios",
- body=maybe_transform(
- {
- "input_context": input_context,
- "name": name,
- "scoring_contract": scoring_contract,
- "environment_parameters": environment_parameters,
- "metadata": metadata,
- "reference_output": reference_output,
- "required_environment_variables": required_environment_variables,
- "required_secret_names": required_secret_names,
- "scorer_timeout_sec": scorer_timeout_sec,
- "validation_type": validation_type,
- },
- scenario_create_params.ScenarioCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioView,
- )
-
- def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ScenarioView:
- """
- Get a previously created scenario.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._get(
- path_template("/v1/scenarios/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=ScenarioView,
- )
-
- def update(
- self,
- id: str,
- *,
- environment_parameters: Optional[ScenarioEnvironmentParam] | Omit = omit,
- input_context: Optional[InputContextUpdateParam] | Omit = omit,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- name: Optional[str] | Omit = omit,
- reference_output: Optional[str] | Omit = omit,
- required_environment_variables: Optional[SequenceNotStr[str]] | Omit = omit,
- required_secret_names: Optional[SequenceNotStr[str]] | Omit = omit,
- scorer_timeout_sec: Optional[int] | Omit = omit,
- scoring_contract: Optional[ScoringContractUpdateParam] | Omit = omit,
- validation_type: Optional[Literal["UNSPECIFIED", "FORWARD", "REVERSE", "EVALUATION"]] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioView:
- """Update a Scenario.
-
- Fields that are null will preserve the existing value. Fields
- that are provided (including empty values) will replace the existing value
- entirely.
-
- Args:
- environment_parameters: ScenarioEnvironmentParameters specify the environment in which a Scenario will
- be run.
-
- input_context: The input context for the Scenario.
-
- metadata: User defined metadata to attach to the scenario. Pass in empty map to clear.
-
- name: Name of the scenario. Cannot be blank.
-
- reference_output: A string representation of the reference output to solve the scenario. Commonly
- can be the result of a git diff or a sequence of command actions to apply to the
- environment. Pass in empty string to clear.
-
- required_environment_variables: Environment variables required to run the scenario. Pass in empty list to clear.
-
- required_secret_names: Secrets required to run the scenario. Pass in empty list to clear.
-
- scorer_timeout_sec: Timeout for scoring in seconds. Default 30 minutes (1800s).
-
- scoring_contract: The scoring contract for the Scenario.
-
- validation_type: Validation strategy. Pass in empty string to clear.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/v1/scenarios/{id}", id=id),
- body=maybe_transform(
- {
- "environment_parameters": environment_parameters,
- "input_context": input_context,
- "metadata": metadata,
- "name": name,
- "reference_output": reference_output,
- "required_environment_variables": required_environment_variables,
- "required_secret_names": required_secret_names,
- "scorer_timeout_sec": scorer_timeout_sec,
- "scoring_contract": scoring_contract,
- "validation_type": validation_type,
- },
- scenario_update_params.ScenarioUpdateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioView,
- )
-
- def list(
- self,
- *,
- benchmark_id: str | Omit = omit,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- validation_type: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SyncScenariosCursorIDPage[ScenarioView]:
- """
- List all Scenarios matching filter.
-
- Args:
- benchmark_id: Filter scenarios by benchmark ID.
-
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Query for Scenarios with a given name.
-
- search: Search by scenario ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- validation_type: Filter by validation type
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/scenarios",
- page=SyncScenariosCursorIDPage[ScenarioView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "benchmark_id": benchmark_id,
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- "validation_type": validation_type,
- },
- scenario_list_params.ScenarioListParams,
- ),
- ),
- model=ScenarioView,
- )
-
- def archive(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioView:
- """Archive a previously created Scenario.
-
- The scenario will no longer appear in
- list endpoints but can still be retrieved by ID.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/v1/scenarios/{id}/archive", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioView,
- )
-
- def list_public(
- self,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SyncScenariosCursorIDPage[ScenarioView]:
- """
- List all public scenarios matching filter.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Query for Scenarios with a given name.
-
- search: Search by scenario ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/scenarios/list_public",
- page=SyncScenariosCursorIDPage[ScenarioView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- },
- scenario_list_public_params.ScenarioListPublicParams,
- ),
- ),
- model=ScenarioView,
- )
-
- def start_run(
- self,
- *,
- scenario_id: str,
- benchmark_run_id: Optional[str] | Omit = omit,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- run_name: Optional[str] | Omit = omit,
- run_profile: Optional[RunProfile] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioRunView:
- """
- Start a new ScenarioRun based on the provided Scenario.
-
- Args:
- scenario_id: ID of the Scenario to run.
-
- benchmark_run_id: Benchmark to associate the run.
-
- metadata: User defined metadata to attach to the run for organization.
-
- run_name: Display name of the run.
-
- run_profile: Runtime configuration to use for this benchmark run
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return self._post(
- "/v1/scenarios/start_run",
- body=maybe_transform(
- {
- "scenario_id": scenario_id,
- "benchmark_run_id": benchmark_run_id,
- "metadata": metadata,
- "run_name": run_name,
- "run_profile": run_profile,
- },
- scenario_start_run_params.ScenarioStartRunParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioRunView,
- )
-
- def start_run_and_await_env_ready(
- self,
- *,
- scenario_id: str,
- benchmark_run_id: Optional[str] | Omit = omit,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- run_name: Optional[str] | Omit = omit,
- run_profile: Optional[scenario_start_run_params.RunProfile] | Omit = omit,
- polling_config: PollingConfig | None = None,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioRunView:
- """Start a new ScenarioRun and wait for its environment to be ready.
-
- Args:
- scenario_id: ID of the Scenario to run
- benchmark_run_id: Benchmark to associate the run
- run_name: Display name of the run
- run_profile: Runtime configuration to use for this benchmark run
- polling_config: Optional polling configuration
- extra_headers: Send extra headers
- extra_query: Add additional query parameters to the request
- extra_body: Add additional JSON properties to the request
- timeout: Override the client-level default timeout for this request, in seconds
- idempotency_key: Specify a custom idempotency key for this request
-
- Returns:
- The scenario run in running state
-
- Raises:
- PollingTimeout: If polling times out before environment is ready
- RunloopError: If environment enters a non-running terminal state
- """
- run = self.start_run(
- scenario_id=scenario_id,
- benchmark_run_id=benchmark_run_id,
- metadata=metadata,
- run_name=run_name,
- run_profile=run_profile,
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- )
-
- self._client.devboxes.await_running(
- run.devbox_id,
- polling_config=polling_config,
- )
-
- return run
-
-
-class AsyncScenariosResource(AsyncAPIResource):
- @cached_property
- def runs(self) -> AsyncRunsResource:
- return AsyncRunsResource(self._client)
-
- @cached_property
- def scorers(self) -> AsyncScorersResource:
- return AsyncScorersResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> AsyncScenariosResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return AsyncScenariosResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncScenariosResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return AsyncScenariosResourceWithStreamingResponse(self)
-
- async def create(
- self,
- *,
- input_context: InputContextParam,
- name: str,
- scoring_contract: ScoringContractParam,
- environment_parameters: Optional[ScenarioEnvironmentParam] | Omit = omit,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- reference_output: Optional[str] | Omit = omit,
- required_environment_variables: Optional[SequenceNotStr[str]] | Omit = omit,
- required_secret_names: Optional[SequenceNotStr[str]] | Omit = omit,
- scorer_timeout_sec: Optional[int] | Omit = omit,
- validation_type: Optional[Literal["UNSPECIFIED", "FORWARD", "REVERSE", "EVALUATION"]] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioView:
- """
- Create a Scenario, a repeatable AI coding evaluation test that defines the
- starting environment as well as evaluation success criteria.
-
- Args:
- input_context: The input context for the Scenario.
-
- name: Name of the scenario.
-
- scoring_contract: The scoring contract for the Scenario.
-
- environment_parameters: ScenarioEnvironmentParameters specify the environment in which a Scenario will
- be run.
-
- metadata: User defined metadata to attach to the scenario for organization.
-
- reference_output: A string representation of the reference output to solve the scenario. Commonly
- can be the result of a git diff or a sequence of command actions to apply to the
- environment.
-
- required_environment_variables: Environment variables required to run the scenario. If these variables are not
- provided, the scenario will fail to start.
-
- required_secret_names: Secrets required to run the scenario (user secret name to scenario required
- secret name). If these secrets are not provided or the mapping is incorrect, the
- scenario will fail to start.
-
- scorer_timeout_sec: Timeout for scoring in seconds. Default 30 minutes (1800s).
-
- validation_type: Validation strategy.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return await self._post(
- "/v1/scenarios",
- body=await async_maybe_transform(
- {
- "input_context": input_context,
- "name": name,
- "scoring_contract": scoring_contract,
- "environment_parameters": environment_parameters,
- "metadata": metadata,
- "reference_output": reference_output,
- "required_environment_variables": required_environment_variables,
- "required_secret_names": required_secret_names,
- "scorer_timeout_sec": scorer_timeout_sec,
- "validation_type": validation_type,
- },
- scenario_create_params.ScenarioCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioView,
- )
-
- async def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ScenarioView:
- """
- Get a previously created scenario.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._get(
- path_template("/v1/scenarios/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=ScenarioView,
- )
-
- async def update(
- self,
- id: str,
- *,
- environment_parameters: Optional[ScenarioEnvironmentParam] | Omit = omit,
- input_context: Optional[InputContextUpdateParam] | Omit = omit,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- name: Optional[str] | Omit = omit,
- reference_output: Optional[str] | Omit = omit,
- required_environment_variables: Optional[SequenceNotStr[str]] | Omit = omit,
- required_secret_names: Optional[SequenceNotStr[str]] | Omit = omit,
- scorer_timeout_sec: Optional[int] | Omit = omit,
- scoring_contract: Optional[ScoringContractUpdateParam] | Omit = omit,
- validation_type: Optional[Literal["UNSPECIFIED", "FORWARD", "REVERSE", "EVALUATION"]] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioView:
- """Update a Scenario.
-
- Fields that are null will preserve the existing value. Fields
- that are provided (including empty values) will replace the existing value
- entirely.
-
- Args:
- environment_parameters: ScenarioEnvironmentParameters specify the environment in which a Scenario will
- be run.
-
- input_context: The input context for the Scenario.
-
- metadata: User defined metadata to attach to the scenario. Pass in empty map to clear.
-
- name: Name of the scenario. Cannot be blank.
-
- reference_output: A string representation of the reference output to solve the scenario. Commonly
- can be the result of a git diff or a sequence of command actions to apply to the
- environment. Pass in empty string to clear.
-
- required_environment_variables: Environment variables required to run the scenario. Pass in empty list to clear.
-
- required_secret_names: Secrets required to run the scenario. Pass in empty list to clear.
-
- scorer_timeout_sec: Timeout for scoring in seconds. Default 30 minutes (1800s).
-
- scoring_contract: The scoring contract for the Scenario.
-
- validation_type: Validation strategy. Pass in empty string to clear.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/v1/scenarios/{id}", id=id),
- body=await async_maybe_transform(
- {
- "environment_parameters": environment_parameters,
- "input_context": input_context,
- "metadata": metadata,
- "name": name,
- "reference_output": reference_output,
- "required_environment_variables": required_environment_variables,
- "required_secret_names": required_secret_names,
- "scorer_timeout_sec": scorer_timeout_sec,
- "scoring_contract": scoring_contract,
- "validation_type": validation_type,
- },
- scenario_update_params.ScenarioUpdateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioView,
- )
-
- def list(
- self,
- *,
- benchmark_id: str | Omit = omit,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- validation_type: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AsyncPaginator[ScenarioView, AsyncScenariosCursorIDPage[ScenarioView]]:
- """
- List all Scenarios matching filter.
-
- Args:
- benchmark_id: Filter scenarios by benchmark ID.
-
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Query for Scenarios with a given name.
-
- search: Search by scenario ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- validation_type: Filter by validation type
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/scenarios",
- page=AsyncScenariosCursorIDPage[ScenarioView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "benchmark_id": benchmark_id,
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- "validation_type": validation_type,
- },
- scenario_list_params.ScenarioListParams,
- ),
- ),
- model=ScenarioView,
- )
-
- async def archive(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioView:
- """Archive a previously created Scenario.
-
- The scenario will no longer appear in
- list endpoints but can still be retrieved by ID.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/v1/scenarios/{id}/archive", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioView,
- )
-
- def list_public(
- self,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- name: str | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AsyncPaginator[ScenarioView, AsyncScenariosCursorIDPage[ScenarioView]]:
- """
- List all public scenarios matching filter.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- name: Query for Scenarios with a given name.
-
- search: Search by scenario ID or name.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/scenarios/list_public",
- page=AsyncScenariosCursorIDPage[ScenarioView],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "name": name,
- "search": search,
- "starting_after": starting_after,
- },
- scenario_list_public_params.ScenarioListPublicParams,
- ),
- ),
- model=ScenarioView,
- )
-
- async def start_run(
- self,
- *,
- scenario_id: str,
- benchmark_run_id: Optional[str] | Omit = omit,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- run_name: Optional[str] | Omit = omit,
- run_profile: Optional[RunProfile] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioRunView:
- """
- Start a new ScenarioRun based on the provided Scenario.
-
- Args:
- scenario_id: ID of the Scenario to run.
-
- benchmark_run_id: Benchmark to associate the run.
-
- metadata: User defined metadata to attach to the run for organization.
-
- run_name: Display name of the run.
-
- run_profile: Runtime configuration to use for this benchmark run
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return await self._post(
- "/v1/scenarios/start_run",
- body=await async_maybe_transform(
- {
- "scenario_id": scenario_id,
- "benchmark_run_id": benchmark_run_id,
- "metadata": metadata,
- "run_name": run_name,
- "run_profile": run_profile,
- },
- scenario_start_run_params.ScenarioStartRunParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScenarioRunView,
- )
-
- async def start_run_and_await_env_ready(
- self,
- scenario_id: str,
- benchmark_run_id: Optional[str] | Omit = omit,
- metadata: Optional[Dict[str, str]] | Omit = omit,
- run_name: Optional[str] | Omit = omit,
- run_profile: Optional[scenario_start_run_params.RunProfile] | Omit = omit,
- polling_config: PollingConfig | None = None,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScenarioRunView:
- """Start a new ScenarioRun and wait for its environment to be ready.
-
- Args:
- scenario_id: ID of the Scenario to run
- benchmark_run_id: Benchmark to associate the run
- run_name: Display name of the run
- run_profile: Runtime configuration to use for this benchmark run
- polling_config: Optional polling configuration
-
- Returns:
- The scenario run in running state
-
- Raises:
- PollingTimeout: If polling times out before environment is ready
- RunloopError: If environment enters a non-running terminal state
- """
- run = await self.start_run(
- scenario_id=scenario_id,
- benchmark_run_id=benchmark_run_id,
- metadata=metadata,
- run_name=run_name,
- run_profile=run_profile,
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- )
-
- await self._client.devboxes.await_running(
- run.devbox_id,
- polling_config=polling_config,
- )
-
- return run
-
-
-class ScenariosResourceWithRawResponse:
- def __init__(self, scenarios: ScenariosResource) -> None:
- self._scenarios = scenarios
-
- self.create = to_raw_response_wrapper(
- scenarios.create,
- )
- self.retrieve = to_raw_response_wrapper(
- scenarios.retrieve,
- )
- self.update = to_raw_response_wrapper(
- scenarios.update,
- )
- self.list = to_raw_response_wrapper(
- scenarios.list,
- )
- self.archive = to_raw_response_wrapper(
- scenarios.archive,
- )
- self.list_public = to_raw_response_wrapper(
- scenarios.list_public,
- )
- self.start_run = to_raw_response_wrapper(
- scenarios.start_run,
- )
-
- @cached_property
- def runs(self) -> RunsResourceWithRawResponse:
- return RunsResourceWithRawResponse(self._scenarios.runs)
-
- @cached_property
- def scorers(self) -> ScorersResourceWithRawResponse:
- return ScorersResourceWithRawResponse(self._scenarios.scorers)
-
-
-class AsyncScenariosResourceWithRawResponse:
- def __init__(self, scenarios: AsyncScenariosResource) -> None:
- self._scenarios = scenarios
-
- self.create = async_to_raw_response_wrapper(
- scenarios.create,
- )
- self.retrieve = async_to_raw_response_wrapper(
- scenarios.retrieve,
- )
- self.update = async_to_raw_response_wrapper(
- scenarios.update,
- )
- self.list = async_to_raw_response_wrapper(
- scenarios.list,
- )
- self.archive = async_to_raw_response_wrapper(
- scenarios.archive,
- )
- self.list_public = async_to_raw_response_wrapper(
- scenarios.list_public,
- )
- self.start_run = async_to_raw_response_wrapper(
- scenarios.start_run,
- )
-
- @cached_property
- def runs(self) -> AsyncRunsResourceWithRawResponse:
- return AsyncRunsResourceWithRawResponse(self._scenarios.runs)
-
- @cached_property
- def scorers(self) -> AsyncScorersResourceWithRawResponse:
- return AsyncScorersResourceWithRawResponse(self._scenarios.scorers)
-
-
-class ScenariosResourceWithStreamingResponse:
- def __init__(self, scenarios: ScenariosResource) -> None:
- self._scenarios = scenarios
-
- self.create = to_streamed_response_wrapper(
- scenarios.create,
- )
- self.retrieve = to_streamed_response_wrapper(
- scenarios.retrieve,
- )
- self.update = to_streamed_response_wrapper(
- scenarios.update,
- )
- self.list = to_streamed_response_wrapper(
- scenarios.list,
- )
- self.archive = to_streamed_response_wrapper(
- scenarios.archive,
- )
- self.list_public = to_streamed_response_wrapper(
- scenarios.list_public,
- )
- self.start_run = to_streamed_response_wrapper(
- scenarios.start_run,
- )
-
- @cached_property
- def runs(self) -> RunsResourceWithStreamingResponse:
- return RunsResourceWithStreamingResponse(self._scenarios.runs)
-
- @cached_property
- def scorers(self) -> ScorersResourceWithStreamingResponse:
- return ScorersResourceWithStreamingResponse(self._scenarios.scorers)
-
-
-class AsyncScenariosResourceWithStreamingResponse:
- def __init__(self, scenarios: AsyncScenariosResource) -> None:
- self._scenarios = scenarios
-
- self.create = async_to_streamed_response_wrapper(
- scenarios.create,
- )
- self.retrieve = async_to_streamed_response_wrapper(
- scenarios.retrieve,
- )
- self.update = async_to_streamed_response_wrapper(
- scenarios.update,
- )
- self.list = async_to_streamed_response_wrapper(
- scenarios.list,
- )
- self.archive = async_to_streamed_response_wrapper(
- scenarios.archive,
- )
- self.list_public = async_to_streamed_response_wrapper(
- scenarios.list_public,
- )
- self.start_run = async_to_streamed_response_wrapper(
- scenarios.start_run,
- )
-
- @cached_property
- def runs(self) -> AsyncRunsResourceWithStreamingResponse:
- return AsyncRunsResourceWithStreamingResponse(self._scenarios.runs)
-
- @cached_property
- def scorers(self) -> AsyncScorersResourceWithStreamingResponse:
- return AsyncScorersResourceWithStreamingResponse(self._scenarios.scorers)
diff --git a/src/runloop_api_client/resources/scenarios/scorers.py b/src/runloop_api_client/resources/scenarios/scorers.py
deleted file mode 100644
index 599949a17..000000000
--- a/src/runloop_api_client/resources/scenarios/scorers.py
+++ /dev/null
@@ -1,527 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import httpx
-
-from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
-from ..._utils import path_template, maybe_transform, async_maybe_transform
-from ..._compat import cached_property
-from ..._resource import SyncAPIResource, AsyncAPIResource
-from ..._response import (
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
-)
-from ...pagination import SyncScenarioScorersCursorIDPage, AsyncScenarioScorersCursorIDPage
-from ..._base_client import AsyncPaginator, make_request_options
-from ...types.scenarios import scorer_list_params, scorer_create_params, scorer_update_params
-from ...types.scenarios.scorer_list_response import ScorerListResponse
-from ...types.scenarios.scorer_create_response import ScorerCreateResponse
-from ...types.scenarios.scorer_update_response import ScorerUpdateResponse
-from ...types.scenarios.scorer_retrieve_response import ScorerRetrieveResponse
-
-__all__ = ["ScorersResource", "AsyncScorersResource"]
-
-
-class ScorersResource(SyncAPIResource):
- @cached_property
- def with_raw_response(self) -> ScorersResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return ScorersResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> ScorersResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return ScorersResourceWithStreamingResponse(self)
-
- def create(
- self,
- *,
- bash_script: str,
- type: str,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScorerCreateResponse:
- """
- Create a custom scenario scorer.
-
- Args:
- bash_script: Bash script for the custom scorer taking context as a json object
- $RL_SCORER_CONTEXT.
-
- type: Name of the type of custom scorer.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return self._post(
- "/v1/scenarios/scorers",
- body=maybe_transform(
- {
- "bash_script": bash_script,
- "type": type,
- },
- scorer_create_params.ScorerCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScorerCreateResponse,
- )
-
- def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ScorerRetrieveResponse:
- """
- Retrieve Scenario Scorer.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._get(
- path_template("/v1/scenarios/scorers/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=ScorerRetrieveResponse,
- )
-
- def update(
- self,
- id: str,
- *,
- bash_script: str,
- type: str,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScorerUpdateResponse:
- """
- Update a scenario scorer.
-
- Args:
- bash_script: Bash script for the custom scorer taking context as a json object
- $RL_SCORER_CONTEXT.
-
- type: Name of the type of custom scorer.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/v1/scenarios/scorers/{id}", id=id),
- body=maybe_transform(
- {
- "bash_script": bash_script,
- "type": type,
- },
- scorer_update_params.ScorerUpdateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScorerUpdateResponse,
- )
-
- def list(
- self,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SyncScenarioScorersCursorIDPage[ScorerListResponse]:
- """
- List all Scenario Scorers matching filter.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- search: Search by scenario scorer ID or type.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/scenarios/scorers",
- page=SyncScenarioScorersCursorIDPage[ScorerListResponse],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "search": search,
- "starting_after": starting_after,
- },
- scorer_list_params.ScorerListParams,
- ),
- ),
- model=ScorerListResponse,
- )
-
-
-class AsyncScorersResource(AsyncAPIResource):
- @cached_property
- def with_raw_response(self) -> AsyncScorersResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers
- """
- return AsyncScorersResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncScorersResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
- """
- return AsyncScorersResourceWithStreamingResponse(self)
-
- async def create(
- self,
- *,
- bash_script: str,
- type: str,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScorerCreateResponse:
- """
- Create a custom scenario scorer.
-
- Args:
- bash_script: Bash script for the custom scorer taking context as a json object
- $RL_SCORER_CONTEXT.
-
- type: Name of the type of custom scorer.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- return await self._post(
- "/v1/scenarios/scorers",
- body=await async_maybe_transform(
- {
- "bash_script": bash_script,
- "type": type,
- },
- scorer_create_params.ScorerCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScorerCreateResponse,
- )
-
- async def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ScorerRetrieveResponse:
- """
- Retrieve Scenario Scorer.
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._get(
- path_template("/v1/scenarios/scorers/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=ScorerRetrieveResponse,
- )
-
- async def update(
- self,
- id: str,
- *,
- bash_script: str,
- type: str,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- idempotency_key: str | None = None,
- ) -> ScorerUpdateResponse:
- """
- Update a scenario scorer.
-
- Args:
- bash_script: Bash script for the custom scorer taking context as a json object
- $RL_SCORER_CONTEXT.
-
- type: Name of the type of custom scorer.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
-
- idempotency_key: Specify a custom idempotency key for this request
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/v1/scenarios/scorers/{id}", id=id),
- body=await async_maybe_transform(
- {
- "bash_script": bash_script,
- "type": type,
- },
- scorer_update_params.ScorerUpdateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- idempotency_key=idempotency_key,
- ),
- cast_to=ScorerUpdateResponse,
- )
-
- def list(
- self,
- *,
- include_total_count: bool | Omit = omit,
- limit: int | Omit = omit,
- search: str | Omit = omit,
- starting_after: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AsyncPaginator[ScorerListResponse, AsyncScenarioScorersCursorIDPage[ScorerListResponse]]:
- """
- List all Scenario Scorers matching filter.
-
- Args:
- include_total_count: If true (default), includes total_count in the response. Set to false to skip
- the count query for better performance on large datasets.
-
- limit: The limit of items to return. Default is 20. Max is 5000.
-
- search: Search by scenario scorer ID or type.
-
- starting_after: Load the next page of data starting after the item with the given ID.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/scenarios/scorers",
- page=AsyncScenarioScorersCursorIDPage[ScorerListResponse],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "include_total_count": include_total_count,
- "limit": limit,
- "search": search,
- "starting_after": starting_after,
- },
- scorer_list_params.ScorerListParams,
- ),
- ),
- model=ScorerListResponse,
- )
-
-
-class ScorersResourceWithRawResponse:
- def __init__(self, scorers: ScorersResource) -> None:
- self._scorers = scorers
-
- self.create = to_raw_response_wrapper(
- scorers.create,
- )
- self.retrieve = to_raw_response_wrapper(
- scorers.retrieve,
- )
- self.update = to_raw_response_wrapper(
- scorers.update,
- )
- self.list = to_raw_response_wrapper(
- scorers.list,
- )
-
-
-class AsyncScorersResourceWithRawResponse:
- def __init__(self, scorers: AsyncScorersResource) -> None:
- self._scorers = scorers
-
- self.create = async_to_raw_response_wrapper(
- scorers.create,
- )
- self.retrieve = async_to_raw_response_wrapper(
- scorers.retrieve,
- )
- self.update = async_to_raw_response_wrapper(
- scorers.update,
- )
- self.list = async_to_raw_response_wrapper(
- scorers.list,
- )
-
-
-class ScorersResourceWithStreamingResponse:
- def __init__(self, scorers: ScorersResource) -> None:
- self._scorers = scorers
-
- self.create = to_streamed_response_wrapper(
- scorers.create,
- )
- self.retrieve = to_streamed_response_wrapper(
- scorers.retrieve,
- )
- self.update = to_streamed_response_wrapper(
- scorers.update,
- )
- self.list = to_streamed_response_wrapper(
- scorers.list,
- )
-
-
-class AsyncScorersResourceWithStreamingResponse:
- def __init__(self, scorers: AsyncScorersResource) -> None:
- self._scorers = scorers
-
- self.create = async_to_streamed_response_wrapper(
- scorers.create,
- )
- self.retrieve = async_to_streamed_response_wrapper(
- scorers.retrieve,
- )
- self.update = async_to_streamed_response_wrapper(
- scorers.update,
- )
- self.list = async_to_streamed_response_wrapper(
- scorers.list,
- )
diff --git a/src/runloop_api_client/resources/secrets.py b/src/runloop_api_client/resources/secrets.py
index 0dab937c4..f074344b3 100644
--- a/src/runloop_api_client/resources/secrets.py
+++ b/src/runloop_api_client/resources/secrets.py
@@ -4,7 +4,7 @@
import httpx
-from ..types import secret_list_params, secret_create_params, secret_update_params
+from ..types import secret_list_params, secret_create_params, secret_update_params, secret_update_by_id_params
from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from .._utils import path_template, maybe_transform, async_maybe_transform
from .._compat import cached_property
@@ -55,14 +55,16 @@ def create(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
idempotency_key: str | None = None,
) -> SecretView:
- """Create a new Secret with a globally unique name and value.
+ """Create a new Secret with a name and value.
- The Secret will be
- encrypted at rest and made available as an environment variable in Devboxes.
+ The Secret will be encrypted at rest
+ and made available as an environment variable in Devboxes. Creation rejects an
+ existing name within the account on a best-effort basis; concurrent creates may
+ produce duplicates.
Args:
- name: The globally unique name for the Secret. Must be a valid environment variable
- name (alphanumeric and underscores only). Example: 'DATABASE_PASSWORD'
+ name: The name for the Secret. Must be a valid environment variable name (alphanumeric
+ and underscores only). Example: 'DATABASE_PASSWORD'
value: The value to store for this Secret. This will be encrypted at rest and made
available as an environment variable in Devboxes. Example: 'my-secure-password'
@@ -109,7 +111,8 @@ def retrieve(
) -> SecretView:
"""Retrieve a Secret by name.
- The secret value is not included for security.
+ If several match, the greatest ID is selected. The
+ secret value is not included for security.
Args:
extra_headers: Send extra headers
@@ -145,8 +148,10 @@ def update(
) -> SecretView:
"""Update the value of an existing Secret by name.
- The new value will be encrypted
- at rest.
+ If several Secrets in the
+ account have this name, only the one with the greatest ID at lookup is updated
+ and returned. Older matches are unchanged. A concurrent create may become the
+ latest match. The new value will be encrypted at rest.
Args:
value: The new value for the Secret. This will replace the existing value and be
@@ -230,8 +235,10 @@ def delete(
) -> SecretView:
"""Delete an existing Secret by name.
- This action is irreversible and will remove
- the Secret from all Devboxes.
+ If several Secrets in the account have this
+ name, every selected match is deleted and the one with the greatest selected ID
+ is returned. Deletions are not atomic across matches: a failure may leave some
+ matches deleted. A concurrent create may survive. This action is irreversible.
Args:
extra_headers: Send extra headers
@@ -258,6 +265,128 @@ def delete(
cast_to=SecretView,
)
+ def delete_by_id(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> SecretView:
+ """Delete an existing Secret by ID.
+
+ This action is irreversible.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._post(
+ path_template("/v1/secrets/id/{id}/delete", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=SecretView,
+ )
+
+ def retrieve_by_id(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SecretView:
+ """Retrieve a Secret by ID.
+
+ The secret value is not included for security.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._get(
+ path_template("/v1/secrets/id/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SecretView,
+ )
+
+ def update_by_id(
+ self,
+ id: str,
+ *,
+ value: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> SecretView:
+ """Update the value of an existing Secret by ID.
+
+ The new value will be encrypted at
+ rest.
+
+ Args:
+ value: The new value for the Secret. This will replace the existing value and be
+ encrypted at rest. Example: 'my-updated-secure-password'
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._post(
+ path_template("/v1/secrets/id/{id}/update", id=id),
+ body=maybe_transform({"value": value}, secret_update_by_id_params.SecretUpdateByIDParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=SecretView,
+ )
+
class AsyncSecretsResource(AsyncAPIResource):
@cached_property
@@ -292,14 +421,16 @@ async def create(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
idempotency_key: str | None = None,
) -> SecretView:
- """Create a new Secret with a globally unique name and value.
+ """Create a new Secret with a name and value.
- The Secret will be
- encrypted at rest and made available as an environment variable in Devboxes.
+ The Secret will be encrypted at rest
+ and made available as an environment variable in Devboxes. Creation rejects an
+ existing name within the account on a best-effort basis; concurrent creates may
+ produce duplicates.
Args:
- name: The globally unique name for the Secret. Must be a valid environment variable
- name (alphanumeric and underscores only). Example: 'DATABASE_PASSWORD'
+ name: The name for the Secret. Must be a valid environment variable name (alphanumeric
+ and underscores only). Example: 'DATABASE_PASSWORD'
value: The value to store for this Secret. This will be encrypted at rest and made
available as an environment variable in Devboxes. Example: 'my-secure-password'
@@ -346,7 +477,8 @@ async def retrieve(
) -> SecretView:
"""Retrieve a Secret by name.
- The secret value is not included for security.
+ If several match, the greatest ID is selected. The
+ secret value is not included for security.
Args:
extra_headers: Send extra headers
@@ -382,8 +514,10 @@ async def update(
) -> SecretView:
"""Update the value of an existing Secret by name.
- The new value will be encrypted
- at rest.
+ If several Secrets in the
+ account have this name, only the one with the greatest ID at lookup is updated
+ and returned. Older matches are unchanged. A concurrent create may become the
+ latest match. The new value will be encrypted at rest.
Args:
value: The new value for the Secret. This will replace the existing value and be
@@ -467,8 +601,10 @@ async def delete(
) -> SecretView:
"""Delete an existing Secret by name.
- This action is irreversible and will remove
- the Secret from all Devboxes.
+ If several Secrets in the account have this
+ name, every selected match is deleted and the one with the greatest selected ID
+ is returned. Deletions are not atomic across matches: a failure may leave some
+ matches deleted. A concurrent create may survive. This action is irreversible.
Args:
extra_headers: Send extra headers
@@ -495,6 +631,128 @@ async def delete(
cast_to=SecretView,
)
+ async def delete_by_id(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> SecretView:
+ """Delete an existing Secret by ID.
+
+ This action is irreversible.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._post(
+ path_template("/v1/secrets/id/{id}/delete", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=SecretView,
+ )
+
+ async def retrieve_by_id(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SecretView:
+ """Retrieve a Secret by ID.
+
+ The secret value is not included for security.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._get(
+ path_template("/v1/secrets/id/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SecretView,
+ )
+
+ async def update_by_id(
+ self,
+ id: str,
+ *,
+ value: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> SecretView:
+ """Update the value of an existing Secret by ID.
+
+ The new value will be encrypted at
+ rest.
+
+ Args:
+ value: The new value for the Secret. This will replace the existing value and be
+ encrypted at rest. Example: 'my-updated-secure-password'
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._post(
+ path_template("/v1/secrets/id/{id}/update", id=id),
+ body=await async_maybe_transform({"value": value}, secret_update_by_id_params.SecretUpdateByIDParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=SecretView,
+ )
+
class SecretsResourceWithRawResponse:
def __init__(self, secrets: SecretsResource) -> None:
@@ -515,6 +773,15 @@ def __init__(self, secrets: SecretsResource) -> None:
self.delete = to_raw_response_wrapper(
secrets.delete,
)
+ self.delete_by_id = to_raw_response_wrapper(
+ secrets.delete_by_id,
+ )
+ self.retrieve_by_id = to_raw_response_wrapper(
+ secrets.retrieve_by_id,
+ )
+ self.update_by_id = to_raw_response_wrapper(
+ secrets.update_by_id,
+ )
class AsyncSecretsResourceWithRawResponse:
@@ -536,6 +803,15 @@ def __init__(self, secrets: AsyncSecretsResource) -> None:
self.delete = async_to_raw_response_wrapper(
secrets.delete,
)
+ self.delete_by_id = async_to_raw_response_wrapper(
+ secrets.delete_by_id,
+ )
+ self.retrieve_by_id = async_to_raw_response_wrapper(
+ secrets.retrieve_by_id,
+ )
+ self.update_by_id = async_to_raw_response_wrapper(
+ secrets.update_by_id,
+ )
class SecretsResourceWithStreamingResponse:
@@ -557,6 +833,15 @@ def __init__(self, secrets: SecretsResource) -> None:
self.delete = to_streamed_response_wrapper(
secrets.delete,
)
+ self.delete_by_id = to_streamed_response_wrapper(
+ secrets.delete_by_id,
+ )
+ self.retrieve_by_id = to_streamed_response_wrapper(
+ secrets.retrieve_by_id,
+ )
+ self.update_by_id = to_streamed_response_wrapper(
+ secrets.update_by_id,
+ )
class AsyncSecretsResourceWithStreamingResponse:
@@ -578,3 +863,12 @@ def __init__(self, secrets: AsyncSecretsResource) -> None:
self.delete = async_to_streamed_response_wrapper(
secrets.delete,
)
+ self.delete_by_id = async_to_streamed_response_wrapper(
+ secrets.delete_by_id,
+ )
+ self.retrieve_by_id = async_to_streamed_response_wrapper(
+ secrets.retrieve_by_id,
+ )
+ self.update_by_id = async_to_streamed_response_wrapper(
+ secrets.update_by_id,
+ )
diff --git a/src/runloop_api_client/sdk/__init__.py b/src/runloop_api_client/sdk/__init__.py
index 38b3d0e41..c3ee0313c 100644
--- a/src/runloop_api_client/sdk/__init__.py
+++ b/src/runloop_api_client/sdk/__init__.py
@@ -8,94 +8,65 @@
from .axon import Axon, AxonSqlOps
from .sync import (
AxonOps,
- AgentOps,
DevboxOps,
- ScorerOps,
SecretOps,
RunloopSDK,
- ScenarioOps,
SnapshotOps,
- BenchmarkOps,
BlueprintOps,
McpConfigOps,
GatewayConfigOps,
NetworkPolicyOps,
StorageObjectOps,
)
-from .agent import Agent
-from ._types import ScenarioPreview
from .async_ import (
AsyncAxonOps,
- AsyncAgentOps,
AsyncDevboxOps,
- AsyncScorerOps,
AsyncSecretOps,
AsyncRunloopSDK,
- AsyncScenarioOps,
AsyncSnapshotOps,
- AsyncBenchmarkOps,
AsyncBlueprintOps,
AsyncMcpConfigOps,
AsyncGatewayConfigOps,
AsyncNetworkPolicyOps,
AsyncStorageObjectOps,
)
-from .devbox import Devbox, NamedShell
-from .scorer import Scorer
+from .devbox import Devbox
from .secret import Secret
-from .scenario import Scenario
from .snapshot import Snapshot
-from .benchmark import Benchmark
from .blueprint import Blueprint
from .execution import Execution
from .async_axon import AsyncAxon, AsyncAxonSqlOps
from .mcp_config import McpConfig
-from .async_agent import AsyncAgent
-from .async_devbox import AsyncDevbox, AsyncNamedShell
-from .async_scorer import AsyncScorer
+from .async_devbox import AsyncDevbox
from .async_secret import AsyncSecret
-from .scenario_run import ScenarioRun
-from .benchmark_run import BenchmarkRun
-from .async_scenario import AsyncScenario
+from .secret_by_id import SecretById
from .async_snapshot import AsyncSnapshot
from .gateway_config import GatewayConfig
from .network_policy import NetworkPolicy
from .storage_object import StorageObject
-from .async_benchmark import AsyncBenchmark
from .async_blueprint import AsyncBlueprint
from .async_execution import AsyncExecution
from .async_mcp_config import AsyncMcpConfig
from .execution_result import ExecutionResult
-from .scenario_builder import ScenarioBuilder
-from .async_scenario_run import AsyncScenarioRun
-from .async_benchmark_run import AsyncBenchmarkRun
+from .async_secret_by_id import AsyncSecretById
from .async_gateway_config import AsyncGatewayConfig
from .async_network_policy import AsyncNetworkPolicy
from .async_storage_object import AsyncStorageObject
from .async_execution_result import AsyncExecutionResult
-from .async_scenario_builder import AsyncScenarioBuilder
__all__ = [
# Main SDK entry points
"RunloopSDK",
"AsyncRunloopSDK",
# Management interfaces
- "AgentOps",
- "AsyncAgentOps",
"AxonOps",
"AsyncAxonOps",
- "BenchmarkOps",
- "AsyncBenchmarkOps",
"DevboxOps",
"AsyncDevboxOps",
"BlueprintOps",
"AsyncBlueprintOps",
- "ScenarioOps",
- "AsyncScenarioOps",
"SecretOps",
"AsyncSecretOps",
- "ScorerOps",
- "AsyncScorerOps",
"SnapshotOps",
"AsyncSnapshotOps",
"StorageObjectOps",
@@ -107,17 +78,11 @@
"GatewayConfigOps",
"AsyncGatewayConfigOps",
# Resource classes
- "Agent",
- "AsyncAgent",
"Axon",
"AsyncAxon",
"AxonSqlOps",
"AsyncAxonSqlOps",
"AsyncSecret",
- "Benchmark",
- "AsyncBenchmark",
- "BenchmarkRun",
- "AsyncBenchmarkRun",
"Devbox",
"AsyncDevbox",
"Execution",
@@ -126,17 +91,10 @@
"AsyncExecutionResult",
"Blueprint",
"AsyncBlueprint",
- "Scenario",
- "AsyncScenario",
- "ScenarioRun",
- "AsyncScenarioRun",
- "ScenarioBuilder",
- "AsyncScenarioBuilder",
- "ScenarioPreview",
"Secret",
+ "SecretById",
+ "AsyncSecretById",
"AsyncSecret",
- "Scorer",
- "AsyncScorer",
"Snapshot",
"AsyncSnapshot",
"StorageObject",
@@ -147,6 +105,4 @@
"AsyncMcpConfig",
"GatewayConfig",
"AsyncGatewayConfig",
- "NamedShell",
- "AsyncNamedShell",
]
diff --git a/src/runloop_api_client/sdk/_types.py b/src/runloop_api_client/sdk/_types.py
index 92070988e..0326a5ef0 100644
--- a/src/runloop_api_client/sdk/_types.py
+++ b/src/runloop_api_client/sdk/_types.py
@@ -1,27 +1,17 @@
-from typing import Union, Literal, Callable, Optional
+from typing import Union, Callable, Optional
from typing_extensions import TypedDict
from ..types import (
- InputContext,
- ScenarioView,
AxonListParams,
- AgentListParams,
AxonCreateParams,
DevboxListParams,
ObjectListParams,
- AgentCreateParams,
AxonPublishParams,
DevboxCreateParams,
ObjectCreateParams,
- ScenarioListParams,
- BenchmarkListParams,
BlueprintListParams,
McpConfigListParams,
ObjectDownloadParams,
- ScenarioUpdateParams,
- AgentListPublicParams,
- BenchmarkCreateParams,
- BenchmarkUpdateParams,
BlueprintCreateParams,
McpConfigCreateParams,
McpConfigUpdateParams,
@@ -37,18 +27,13 @@
NetworkPolicyUpdateParams,
DevboxReadFileContentsParams,
DevboxWriteFileContentsParams,
- BenchmarkRunListScenarioRunsParams,
)
from .._types import Body, Query, Headers, Timeout, NotGiven
from ..lib.polling import PollingConfig
from ..types.devboxes import DiskSnapshotListParams, DiskSnapshotUpdateParams
-from ..types.scenarios import ScorerListParams, ScorerCreateParams, ScorerUpdateParams
from ..types.devbox_create_params import DevboxBaseCreateParams
from ..types.axons.sql_batch_params import SqlBatchParams
from ..types.axons.sql_query_params import SqlQueryParams
-from ..types.scenario_start_run_params import ScenarioStartRunBaseParams
-from ..types.benchmark_start_run_params import BenchmarkSelfStartRunParams
-from ..types.devbox_execute_async_params import DevboxNiceExecuteAsyncParams
LogCallback = Callable[[str], None]
@@ -100,11 +85,17 @@ class SDKDevboxCreateFromImageParams(DevboxBaseCreateParams, LongPollingRequestO
pass
-class SDKDevboxExecuteParams(DevboxNiceExecuteAsyncParams, ExecuteStreamingCallbacks, LongPollingRequestOptions):
+class DevboxCommandOptions(TypedDict, total=False):
+ """Optional API fields shared by command execution helpers."""
+
+ attach_stdin: Optional[bool]
+
+
+class SDKDevboxExecuteParams(DevboxCommandOptions, ExecuteStreamingCallbacks, LongPollingRequestOptions):
pass
-class SDKDevboxExecuteAsyncParams(DevboxNiceExecuteAsyncParams, ExecuteStreamingCallbacks, LongRequestOptions):
+class SDKDevboxExecuteAsyncParams(DevboxCommandOptions, ExecuteStreamingCallbacks, LongRequestOptions):
pass
@@ -172,30 +163,6 @@ class SDKObjectDownloadParams(ObjectDownloadParams, BaseRequestOptions):
pass
-class SDKScorerCreateParams(ScorerCreateParams, LongRequestOptions):
- pass
-
-
-class SDKScorerListParams(ScorerListParams, BaseRequestOptions):
- pass
-
-
-class SDKScorerUpdateParams(ScorerUpdateParams, LongRequestOptions):
- pass
-
-
-class SDKAgentCreateParams(AgentCreateParams, LongRequestOptions):
- pass
-
-
-class SDKAgentListParams(AgentListParams, BaseRequestOptions):
- pass
-
-
-class SDKAgentListPublicParams(AgentListPublicParams, BaseRequestOptions):
- pass
-
-
class SDKAxonListParams(AxonListParams, BaseRequestOptions):
pass
@@ -216,71 +183,6 @@ class SDKAxonSqlBatchParams(SqlBatchParams, LongRequestOptions):
pass
-class SDKScenarioListParams(ScenarioListParams, BaseRequestOptions):
- pass
-
-
-class SDKScenarioUpdateParams(ScenarioUpdateParams, LongRequestOptions):
- pass
-
-
-class SDKScenarioRunAsyncParams(ScenarioStartRunBaseParams, LongRequestOptions):
- pass
-
-
-class SDKScenarioRunParams(ScenarioStartRunBaseParams, LongPollingRequestOptions):
- pass
-
-
-class InputContextPreview(InputContext):
- problem_statement: Optional[str] = None # type: ignore[assignment]
- """The problem statement for the Scenario."""
-
-
-class ScenarioPreview(ScenarioView):
- """Preview of scenario configuration with all fields optional."""
-
- id: Optional[str] = None # type: ignore[assignment]
- """The ID of the Scenario."""
-
- input_context: InputContextPreview # type: ignore[assignment]
- """The input context for the Scenario."""
-
- status: Optional[Literal["active", "archived"]] = None # type: ignore[assignment]
- """Status is not set for previews."""
-
-
-class SDKBenchmarkCreateParams(BenchmarkCreateParams, LongRequestOptions):
- pass
-
-
-class SDKBenchmarkListParams(BenchmarkListParams, BaseRequestOptions):
- pass
-
-
-class SDKBenchmarkUpdateParams(BenchmarkUpdateParams, LongRequestOptions):
- pass
-
-
-class SDKBenchmarkStartRunParams(BenchmarkSelfStartRunParams, LongRequestOptions):
- pass
-
-
-class SDKBenchmarkListRunsParams(BaseRequestOptions, total=False):
- limit: int
- """The limit of items to return. Default is 20. Max is 5000."""
-
- name: str
- """Filter by name"""
-
- starting_after: str
- """Load the next page of data starting after the item with the given ID."""
-
-
-class SDKBenchmarkRunListScenarioRunsParams(BenchmarkRunListScenarioRunsParams, BaseRequestOptions):
- pass
-
-
class SDKNetworkPolicyCreateParams(NetworkPolicyCreateParams, LongRequestOptions):
pass
diff --git a/src/runloop_api_client/sdk/agent.py b/src/runloop_api_client/sdk/agent.py
deleted file mode 100644
index c938358aa..000000000
--- a/src/runloop_api_client/sdk/agent.py
+++ /dev/null
@@ -1,86 +0,0 @@
-"""Agent resource class for synchronous operations."""
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import Unpack, override
-
-from ._types import (
- BaseRequestOptions,
- LongRequestOptions,
-)
-from .._client import Runloop
-from ..types.agent_view import AgentView
-
-
-class Agent:
- """Wrapper around synchronous agent operations.
-
- This class provides a Pythonic interface for interacting with agents,
- including retrieving agent information.
-
- Example:
- >>> agent = runloop.agent.create_from_npm(name="my-agent", package_name="@runloop/example-agent")
- >>> info = agent.get_info()
- >>> print(info.name)
- """
-
- def __init__(
- self,
- client: Runloop,
- agent_id: str,
- agent_view: Optional[AgentView] = None,
- ) -> None:
- """Initialize the wrapper.
-
- :param client: Generated Runloop client
- :type client: Runloop
- :param agent_id: Agent identifier returned by the API
- :type agent_id: str
- """
- self._client = client
- self._id = agent_id
- self._agent_view = agent_view
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def id(self) -> str:
- """Return the agent identifier.
-
- :return: Unique agent ID
- :rtype: str
- """
- return self._id
-
- def get_info(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> AgentView:
- """Retrieve the latest agent information.
-
- :param options: Optional request configuration
- :return: Agent details
- :rtype: AgentView
- """
- return self._client.agents.retrieve(
- self._id,
- **options,
- )
-
- def delete(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> object:
- """Delete this agent. This action is irreversible.
-
- :param options: Optional request configuration
- :return: API response acknowledging deletion
- :rtype: object
- """
- return self._client.agents.delete(
- self._id,
- **options,
- )
diff --git a/src/runloop_api_client/sdk/async_.py b/src/runloop_api_client/sdk/async_.py
index a23f35e2d..33462016b 100644
--- a/src/runloop_api_client/sdk/async_.py
+++ b/src/runloop_api_client/sdk/async_.py
@@ -3,7 +3,7 @@
from __future__ import annotations
import asyncio
-from typing import Dict, List, Mapping, Optional
+from typing import Dict, Mapping, Optional
from pathlib import Path
from datetime import timedelta
from typing_extensions import Unpack
@@ -14,21 +14,13 @@
SDKAxonListParams,
BaseRequestOptions,
LongRequestOptions,
- SDKAgentListParams,
SDKAxonCreateParams,
SDKDevboxListParams,
SDKObjectListParams,
- SDKScorerListParams,
- SDKAgentCreateParams,
SDKDevboxCreateParams,
SDKObjectCreateParams,
- SDKScenarioListParams,
- SDKScorerCreateParams,
- SDKBenchmarkListParams,
SDKBlueprintListParams,
SDKMcpConfigListParams,
- SDKAgentListPublicParams,
- SDKBenchmarkCreateParams,
SDKBlueprintCreateParams,
SDKMcpConfigCreateParams,
SDKDiskSnapshotListParams,
@@ -43,24 +35,18 @@
from ._helpers import detect_content_type
from .async_axon import AsyncAxon
from .._constants import DEFAULT_API_POOL_SHARDS, DEFAULT_TRANSFER_POOL_SHARDS, DEFAULT_BACKGROUND_POOL_SHARDS
-from .async_agent import AsyncAgent
from .async_devbox import AsyncDevbox
-from .async_scorer import AsyncScorer
from .async_secret import AsyncSecret
-from .async_scenario import AsyncScenario
from .async_snapshot import AsyncSnapshot
-from .async_benchmark import AsyncBenchmark
from .async_blueprint import AsyncBlueprint
from .async_mcp_config import AsyncMcpConfig
from ..types.secret_view import SecretView
+from .async_secret_by_id import AsyncSecretById
from ..lib.context_loader import TarFilter, build_directory_tar
from .async_gateway_config import AsyncGatewayConfig
from .async_network_policy import AsyncNetworkPolicy
from .async_storage_object import AsyncStorageObject
-from .async_scenario_builder import AsyncScenarioBuilder
from ..types.object_create_params import ContentType
-from ..types.agent_devbox_counts_view import AgentDevboxCountsView
-from ..types.shared_params.agent_source import Git, Npm, Pip, Object
class AsyncDevboxOps:
@@ -346,7 +332,7 @@ async def create(
:rtype: AsyncStorageObject
"""
obj = await self._client.objects.create(**params)
- return AsyncStorageObject(self._client, obj.id, upload_url=obj.upload_url)
+ return AsyncStorageObject(self._client, obj.id, upload_url=obj.upload_url, upload_headers=obj.upload_headers)
def from_id(self, object_id: str) -> AsyncStorageObject:
"""Return a storage object wrapper by identifier.
@@ -549,7 +535,7 @@ def from_id(self, axon_id: str) -> AsyncAxon:
return AsyncAxon(self._client, axon_id)
async def list(self, **params: Unpack[SDKAxonListParams]) -> list[AsyncAxon]:
- """[Beta] List all active axons.
+ """[Beta] List all axons, including deleted axons.
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAxonListParams` for available parameters
:return: Collection of axon wrappers
@@ -559,393 +545,6 @@ async def list(self, **params: Unpack[SDKAxonListParams]) -> list[AsyncAxon]:
return [AsyncAxon(self._client, axon.id) for axon in result.axons]
-class AsyncScorerOps:
- """Create and manage custom scorers (async). Access via ``runloop.scorer``.
-
- Example:
- >>> runloop = AsyncRunloopSDK()
- >>> scorer = await runloop.scorer.create(type="my_scorer", bash_script="echo 'score=1.0'")
- >>> all_scorers = await runloop.scorer.list()
- """
-
- def __init__(self, client: AsyncRunloop) -> None:
- """Initialize AsyncScorerOps.
-
- :param client: AsyncRunloop client instance
- :type client: AsyncRunloop
- """
- self._client = client
-
- async def create(self, **params: Unpack[SDKScorerCreateParams]) -> AsyncScorer:
- """Create a new scorer with the given type and bash script.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerCreateParams` for available parameters
- :return: The newly created scorer
- :rtype: AsyncScorer
- """
- response = await self._client.scenarios.scorers.create(**params)
- return AsyncScorer(self._client, response.id)
-
- def from_id(self, scorer_id: str) -> AsyncScorer:
- """Get an AsyncScorer instance for an existing scorer ID.
-
- :param scorer_id: ID of the scorer
- :type scorer_id: str
- :return: AsyncScorer instance for the given ID
- :rtype: AsyncScorer
- """
- return AsyncScorer(self._client, scorer_id)
-
- async def list(self, **params: Unpack[SDKScorerListParams]) -> list[AsyncScorer]:
- """List all scorers, optionally filtered by parameters.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerListParams` for available parameters
- :return: List of scorers
- :rtype: list[AsyncScorer]
- """
- page = await self._client.scenarios.scorers.list(**params)
- return [AsyncScorer(self._client, item.id) for item in page.scorers]
-
-
-class AsyncAgentOps:
- """High-level async manager for creating and managing agents.
-
- Accessed via ``runloop.agent`` from :class:`AsyncRunloopSDK`, provides
- coroutines to create, retrieve, and list agents from various sources (npm, pip, git, object storage).
-
- Example:
- >>> runloop = AsyncRunloopSDK()
- >>> # Create agent from NPM package
- >>> agent = await runloop.agent.create_from_npm(name="my-agent", package_name="@runloop/example-agent")
- >>> # Create agent from Git repository
- >>> agent = await runloop.agent.create_from_git(
- ... name="git-agent", repository="https://github.com/user/agent-repo", ref="main"
- ... )
- >>> # List all agents
- >>> agents = await runloop.agent.list(limit=10)
- """
-
- def __init__(self, client: AsyncRunloop) -> None:
- """Initialize the manager.
-
- :param client: Generated AsyncRunloop client to wrap
- :type client: AsyncRunloop
- """
- self._client = client
-
- async def create(
- self,
- **params: Unpack[SDKAgentCreateParams],
- ) -> AsyncAgent:
- """Create a new agent.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for available parameters
- :return: Wrapper bound to the newly created agent
- :rtype: AsyncAgent
- """
- agent_view = await self._client.agents.create(
- **params,
- )
- return AsyncAgent(self._client, agent_view.id, agent_view)
-
- async def create_from_npm(
- self,
- *,
- package_name: str,
- registry_url: Optional[str] = None,
- agent_setup: Optional[list[str]] = None,
- **params: Unpack[SDKAgentCreateParams],
- ) -> AsyncAgent:
- """Create an agent from an NPM package.
-
- :param package_name: NPM package name
- :type package_name: str
- :param registry_url: NPM registry URL, defaults to None
- :type registry_url: Optional[str], optional
- :param agent_setup: Setup commands to run after installation, defaults to None
- :type agent_setup: Optional[list[str]], optional
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
- :return: Wrapper bound to the newly created agent
- :rtype: AsyncAgent
- :raises ValueError: If 'source' is provided in params
- """
- if "source" in params:
- raise ValueError(
- "Cannot specify 'source' when using create_from_npm(); source is automatically set to npm configuration"
- )
-
- npm_config: Npm = {"package_name": package_name}
- if registry_url is not None:
- npm_config["registry_url"] = registry_url
- if agent_setup is not None:
- npm_config["agent_setup"] = agent_setup
-
- params["source"] = {"type": "npm", "npm": npm_config}
- return await self.create(**params)
-
- async def create_from_pip(
- self,
- *,
- package_name: str,
- registry_url: Optional[str] = None,
- agent_setup: Optional[list[str]] = None,
- **params: Unpack[SDKAgentCreateParams],
- ) -> AsyncAgent:
- """Create an agent from a Pip package.
-
- :param package_name: Pip package name
- :type package_name: str
- :param registry_url: Pip registry URL, defaults to None
- :type registry_url: Optional[str], optional
- :param agent_setup: Setup commands to run after installation, defaults to None
- :type agent_setup: Optional[list[str]], optional
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
- :return: Wrapper bound to the newly created agent
- :rtype: AsyncAgent
- :raises ValueError: If 'source' is provided in params
- """
- if "source" in params:
- raise ValueError(
- "Cannot specify 'source' when using create_from_pip(); source is automatically set to pip configuration"
- )
-
- pip_config: Pip = {"package_name": package_name}
- if registry_url is not None:
- pip_config["registry_url"] = registry_url
- if agent_setup is not None:
- pip_config["agent_setup"] = agent_setup
-
- params["source"] = {"type": "pip", "pip": pip_config}
- return await self.create(**params)
-
- async def create_from_git(
- self,
- *,
- repository: str,
- ref: Optional[str] = None,
- agent_setup: Optional[list[str]] = None,
- **params: Unpack[SDKAgentCreateParams],
- ) -> AsyncAgent:
- """Create an agent from a Git repository.
-
- :param repository: Git repository URL
- :type repository: str
- :param ref: Optional Git ref (branch/tag/commit), defaults to main/HEAD
- :type ref: Optional[str], optional
- :param agent_setup: Setup commands to run after cloning, defaults to None
- :type agent_setup: Optional[list[str]], optional
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
- :return: Wrapper bound to the newly created agent
- :rtype: AsyncAgent
- :raises ValueError: If 'source' is provided in params
- """
- if "source" in params:
- raise ValueError(
- "Cannot specify 'source' when using create_from_git(); source is automatically set to git configuration"
- )
-
- git_config: Git = {"repository": repository}
- if ref is not None:
- git_config["ref"] = ref
- if agent_setup is not None:
- git_config["agent_setup"] = agent_setup
-
- params["source"] = {"type": "git", "git": git_config}
- return await self.create(**params)
-
- async def create_from_object(
- self,
- *,
- object_id: str,
- agent_setup: Optional[list[str]] = None,
- **params: Unpack[SDKAgentCreateParams],
- ) -> AsyncAgent:
- """Create an agent from a storage object.
-
- :param object_id: Storage object ID
- :type object_id: str
- :param agent_setup: Setup commands to run after unpacking, defaults to None
- :type agent_setup: Optional[list[str]], optional
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
- :return: Wrapper bound to the newly created agent
- :rtype: AsyncAgent
- :raises ValueError: If 'source' is provided in params
- """
- if "source" in params:
- raise ValueError(
- "Cannot specify 'source' when using create_from_object(); source is automatically set to object configuration"
- )
-
- object_config: Object = {"object_id": object_id}
- if agent_setup is not None:
- object_config["agent_setup"] = agent_setup
-
- params["source"] = {"type": "object", "object": object_config}
- return await self.create(**params)
-
- def from_id(self, agent_id: str) -> AsyncAgent:
- """Attach to an existing agent by ID.
-
- :param agent_id: Existing agent ID
- :type agent_id: str
- :return: Wrapper bound to the requested agent
- :rtype: AsyncAgent
- """
- return AsyncAgent(self._client, agent_id)
-
- async def list(
- self,
- **params: Unpack[SDKAgentListParams],
- ) -> list[AsyncAgent]:
- """List agents accessible to the caller.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentListParams` for available parameters
- :return: Collection of agent wrappers
- :rtype: list[AsyncAgent]
- """
- page = await self._client.agents.list(
- **params,
- )
- return [AsyncAgent(self._client, item.id, item) for item in page.agents]
-
- async def list_public(
- self,
- **params: Unpack[SDKAgentListPublicParams],
- ) -> List[AsyncAgent]:
- """List public agents.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentListPublicParams` for available parameters
- :return: Collection of public agent wrappers
- :rtype: list[AsyncAgent]
- """
- page = await self._client.agents.list_public(
- **params,
- )
- return [AsyncAgent(self._client, item.id, item) for item in page.agents]
-
- async def devbox_counts(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> AgentDevboxCountsView:
- """Get devbox counts grouped by agent name.
-
- :param options: Optional request configuration
- :return: Devbox counts per agent name and total
- :rtype: AgentDevboxCountsView
- """
- return await self._client.agents.devbox_counts(
- **options,
- )
-
-
-class AsyncScenarioOps:
- """Manage scenarios (async). Access via ``runloop.scenario``.
-
- Example:
- >>> runloop = AsyncRunloopSDK()
- >>> scenario = runloop.scenario.from_id("scn-xxx")
- >>> run = await scenario.run()
- >>> scenarios = await runloop.scenario.list()
-
- Example using builder:
- >>> builder = (
- ... runloop.scenario.builder("my-scenario")
- ... .from_blueprint(blueprint)
- ... .with_problem_statement("Fix the bug")
- ... .add_test_command_scorer("tests", test_command="pytest")
- ... )
- >>> params = builder.build()
- >>> scenario = await runloop.scenario.create(**params) # equivalent to builder.push()
- """
-
- def __init__(self, client: AsyncRunloop) -> None:
- """Initialize AsyncScenarioOps.
-
- :param client: AsyncRunloop client instance
- :type client: AsyncRunloop
- """
- self._client = client
-
- def builder(self, name: str) -> AsyncScenarioBuilder:
- """Create a new scenario builder.
-
- :param name: Name for the scenario
- :type name: str
- :return: A new AsyncScenarioBuilder instance
- :rtype: AsyncScenarioBuilder
- """
- return AsyncScenarioBuilder(name, self._client)
-
- def from_id(self, scenario_id: str) -> AsyncScenario:
- """Get an AsyncScenario instance for an existing scenario ID.
-
- :param scenario_id: ID of the scenario
- :type scenario_id: str
- :return: AsyncScenario instance for the given ID
- :rtype: AsyncScenario
- """
- return AsyncScenario(self._client, scenario_id)
-
- async def list(self, **params: Unpack[SDKScenarioListParams]) -> list[AsyncScenario]:
- """List all scenarios, optionally filtered by parameters.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioListParams` for available parameters
- :return: List of scenarios
- :rtype: list[AsyncScenario]
- """
- page = await self._client.scenarios.list(**params)
- return [AsyncScenario(self._client, item.id) for item in page.scenarios]
-
-
-class AsyncBenchmarkOps:
- """Manage benchmarks (async). Access via ``runloop.benchmark``.
-
- Example:
- >>> runloop = AsyncRunloopSDK()
- >>> benchmarks = await runloop.benchmark.list()
- >>> benchmark = runloop.benchmark.from_id("bmd_xxx")
- >>> run = await benchmark.start_run(run_name="evaluation-v1")
- """
-
- def __init__(self, client: AsyncRunloop) -> None:
- """Initialize AsyncBenchmarkOps.
-
- :param client: AsyncRunloop client instance
- :type client: AsyncRunloop
- """
- self._client = client
-
- async def create(self, **params: Unpack[SDKBenchmarkCreateParams]) -> AsyncBenchmark:
- """Create a new benchmark.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkCreateParams` for available parameters
- :return: The newly created benchmark
- :rtype: AsyncBenchmark
- """
- response = await self._client.benchmarks.create(**params)
- return AsyncBenchmark(self._client, response.id)
-
- def from_id(self, benchmark_id: str) -> AsyncBenchmark:
- """Get an AsyncBenchmark instance for an existing benchmark ID.
-
- :param benchmark_id: ID of the benchmark
- :type benchmark_id: str
- :return: AsyncBenchmark instance for the given ID
- :rtype: AsyncBenchmark
- """
- return AsyncBenchmark(self._client, benchmark_id)
-
- async def list(self, **params: Unpack[SDKBenchmarkListParams]) -> list[AsyncBenchmark]:
- """List all benchmarks, optionally filtered by parameters.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkListParams` for available parameters
- :return: List of benchmarks
- :rtype: list[AsyncBenchmark]
- """
- page = await self._client.benchmarks.list(**params)
- return [AsyncBenchmark(self._client, item.id) for item in page.benchmarks]
-
-
class AsyncNetworkPolicyOps:
"""High-level async manager for creating and managing network policies.
@@ -1171,7 +770,7 @@ async def create(self, name: str, value: str, **options: Unpack[LongRequestOptio
... )
>>> print(f"Created secret: {secret.name}")
- :param name: Globally unique secret name (must be a valid env var name)
+ :param name: Account-scoped secret name (must be a valid env var name)
:type name: str
:param value: Secret value to store (encrypted at rest)
:type value: str
@@ -1192,13 +791,17 @@ def from_name(self, name: str) -> AsyncSecret:
>>> info = await secret.get_info()
>>> print(f"Secret ID: {info.id}")
- :param name: The globally unique name of the secret
+ :param name: The literal account-scoped name of the secret
:type name: str
:return: An AsyncSecret instance (no API call made)
:rtype: AsyncSecret
"""
return AsyncSecret(self._client, name)
+ def from_id(self, id: str) -> AsyncSecretById:
+ """Get a lazy exact-ID handle; its operations never fall back to a name."""
+ return AsyncSecretById(self._client, id)
+
async def update(
self,
secret: "AsyncSecret | str",
@@ -1270,20 +873,12 @@ class AsyncRunloopSDK:
:ivar api: Direct access to the generated async REST API client
:vartype api: AsyncRunloop
- :ivar agent: High-level async interface for agent management.
- :vartype agent: AsyncAgentOps
:ivar axon: [Beta] High-level async interface for axon management
:vartype axon: AsyncAxonOps
- :ivar benchmark: High-level async interface for benchmark management
- :vartype benchmark: AsyncBenchmarkOps
:ivar devbox: High-level async interface for devbox management
:vartype devbox: AsyncDevboxOps
:ivar blueprint: High-level async interface for blueprint management
:vartype blueprint: AsyncBlueprintOps
- :ivar scenario: High-level async interface for scenario management
- :vartype scenario: AsyncScenarioOps
- :ivar scorer: High-level async interface for scorer management
- :vartype scorer: AsyncScorerOps
:ivar snapshot: High-level async interface for snapshot management
:vartype snapshot: AsyncSnapshotOps
:ivar storage_object: High-level async interface for storage object management
@@ -1306,16 +901,12 @@ class AsyncRunloopSDK:
"""
api: AsyncRunloop
- agent: AsyncAgentOps
axon: AsyncAxonOps
- benchmark: AsyncBenchmarkOps
devbox: AsyncDevboxOps
blueprint: AsyncBlueprintOps
gateway_config: AsyncGatewayConfigOps
mcp_config: AsyncMcpConfigOps
network_policy: AsyncNetworkPolicyOps
- scenario: AsyncScenarioOps
- scorer: AsyncScorerOps
secret: AsyncSecretOps
snapshot: AsyncSnapshotOps
storage_object: AsyncStorageObjectOps
@@ -1370,17 +961,13 @@ def __init__(
transfer_pool_shards=transfer_pool_shards,
)
- self.agent = AsyncAgentOps(self.api)
self.axon = AsyncAxonOps(self.api)
- self.benchmark = AsyncBenchmarkOps(self.api)
self.devbox = AsyncDevboxOps(self.api)
self.blueprint = AsyncBlueprintOps(self.api)
self.gateway_config = AsyncGatewayConfigOps(self.api)
self.mcp_config = AsyncMcpConfigOps(self.api)
self.network_policy = AsyncNetworkPolicyOps(self.api)
self.secret = AsyncSecretOps(self.api)
- self.scenario = AsyncScenarioOps(self.api)
- self.scorer = AsyncScorerOps(self.api)
self.snapshot = AsyncSnapshotOps(self.api)
self.storage_object = AsyncStorageObjectOps(self.api)
diff --git a/src/runloop_api_client/sdk/async_agent.py b/src/runloop_api_client/sdk/async_agent.py
deleted file mode 100644
index 907bcf588..000000000
--- a/src/runloop_api_client/sdk/async_agent.py
+++ /dev/null
@@ -1,86 +0,0 @@
-"""Agent resource class for asynchronous operations."""
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import Unpack, override
-
-from ._types import (
- BaseRequestOptions,
- LongRequestOptions,
-)
-from .._client import AsyncRunloop
-from ..types.agent_view import AgentView
-
-
-class AsyncAgent:
- """Async wrapper around agent operations.
-
- This class provides an asynchronous interface for interacting with agents,
- including retrieving agent information.
-
- Example:
- >>> agent = await runloop.agent.create_from_npm(name="my-agent", package_name="@runloop/example-agent")
- >>> info = await agent.get_info()
- >>> print(info.name)
- """
-
- def __init__(
- self,
- client: AsyncRunloop,
- agent_id: str,
- agent_view: Optional[AgentView] = None,
- ) -> None:
- """Initialize the wrapper.
-
- :param client: Generated AsyncRunloop client
- :type client: AsyncRunloop
- :param agent_id: Agent identifier returned by the API
- :type agent_id: str
- """
- self._client = client
- self._id = agent_id
- self._agent_view = agent_view
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def id(self) -> str:
- """Return the agent identifier.
-
- :return: Unique agent ID
- :rtype: str
- """
- return self._id
-
- async def get_info(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> AgentView:
- """Retrieve the latest agent information.
-
- :param options: Optional request configuration
- :return: Agent details
- :rtype: AgentView
- """
- return await self._client.agents.retrieve(
- self._id,
- **options,
- )
-
- async def delete(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> object:
- """Delete this agent. This action is irreversible.
-
- :param options: Optional request configuration
- :return: API response acknowledging deletion
- :rtype: object
- """
- return await self._client.agents.delete(
- self._id,
- **options,
- )
diff --git a/src/runloop_api_client/sdk/async_benchmark.py b/src/runloop_api_client/sdk/async_benchmark.py
deleted file mode 100644
index 0a5b3a371..000000000
--- a/src/runloop_api_client/sdk/async_benchmark.py
+++ /dev/null
@@ -1,169 +0,0 @@
-"""AsyncBenchmark resource class for asynchronous operations."""
-
-from __future__ import annotations
-
-from typing import List
-from typing_extensions import Unpack, override
-
-from ..types import BenchmarkView
-from ._types import (
- BaseRequestOptions,
- LongRequestOptions,
- SDKBenchmarkUpdateParams,
- SDKBenchmarkListRunsParams,
- SDKBenchmarkStartRunParams,
-)
-from .._types import SequenceNotStr
-from .._client import AsyncRunloop
-from .async_benchmark_run import AsyncBenchmarkRun
-
-
-class AsyncBenchmark:
- """A benchmark for evaluating agent performance across scenarios (async).
-
- Provides async methods for retrieving benchmark details, updating the benchmark,
- managing scenarios, and starting benchmark runs. Obtain instances via
- ``runloop.benchmark.from_id()`` or ``runloop.benchmark.list()``.
-
- Example:
- >>> benchmark = runloop.benchmark.from_id("bmd_xxx")
- >>> info = await benchmark.get_info()
- >>> run = await benchmark.start_run(run_name="evaluation-v1")
- >>> for scenario_id in info.scenario_ids:
- ... scenario = await runloop.scenario.from_id(scenario_id)
- ... scenario_run = await scenario.run(benchmark_run_id=run.id, run_name="evaluation-v1")
- """
-
- def __init__(self, client: AsyncRunloop, benchmark_id: str) -> None:
- """Create an AsyncBenchmark instance.
-
- :param client: AsyncRunloop client instance
- :type client: AsyncRunloop
- :param benchmark_id: Benchmark ID
- :type benchmark_id: str
- """
- self._client = client
- self._id = benchmark_id
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def id(self) -> str:
- """Return the benchmark ID.
-
- :return: Unique benchmark ID
- :rtype: str
- """
- return self._id
-
- async def get_info(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> BenchmarkView:
- """Retrieve current benchmark details.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
- :return: Current benchmark info
- :rtype: BenchmarkView
- """
- return await self._client.benchmarks.retrieve(
- self._id,
- **options,
- )
-
- async def update(
- self,
- **params: Unpack[SDKBenchmarkUpdateParams],
- ) -> BenchmarkView:
- """Update the benchmark.
-
- Only provided fields will be updated.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkUpdateParams` for available parameters
- :return: Updated benchmark info
- :rtype: BenchmarkView
- """
- return await self._client.benchmarks.update(
- self._id,
- **params,
- )
-
- async def start_run(
- self,
- **params: Unpack[SDKBenchmarkStartRunParams],
- ) -> AsyncBenchmarkRun:
- """Start a new benchmark run.
-
- Creates a new benchmark run and returns an AsyncBenchmarkRun instance for
- managing the run lifecycle.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkStartRunParams` for available parameters
- :return: AsyncBenchmarkRun instance for managing the run
- :rtype: AsyncBenchmarkRun
- """
- run_view = await self._client.benchmarks.start_run(
- benchmark_id=self._id,
- **params,
- )
- assert run_view.benchmark_id is not None, "benchmark_id should be set for runs created from a benchmark"
- return AsyncBenchmarkRun(self._client, run_view.id, run_view.benchmark_id)
-
- async def add_scenarios(
- self,
- scenario_ids: SequenceNotStr[str],
- **options: Unpack[LongRequestOptions],
- ) -> BenchmarkView:
- """Add scenarios to the benchmark.
-
- :param scenario_ids: List of scenario IDs to add
- :type scenario_ids: SequenceNotStr[str]
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Updated benchmark info
- :rtype: BenchmarkView
- """
- return await self._client.benchmarks.update_scenarios(
- self._id,
- scenarios_to_add=scenario_ids,
- **options,
- )
-
- async def remove_scenarios(
- self,
- scenario_ids: SequenceNotStr[str],
- **options: Unpack[LongRequestOptions],
- ) -> BenchmarkView:
- """Remove scenarios from the benchmark.
-
- :param scenario_ids: List of scenario IDs to remove
- :type scenario_ids: SequenceNotStr[str]
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Updated benchmark info
- :rtype: BenchmarkView
- """
- return await self._client.benchmarks.update_scenarios(
- self._id,
- scenarios_to_remove=scenario_ids,
- **options,
- )
-
- async def list_runs(
- self,
- **params: Unpack[SDKBenchmarkListRunsParams],
- ) -> List[AsyncBenchmarkRun]:
- """List all runs for this benchmark.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkListRunsParams` for available parameters
- :return: List of async benchmark runs
- :rtype: List[AsyncBenchmarkRun]
- """
- page = await self._client.benchmark_runs.list(
- benchmark_id=self._id,
- **params,
- )
- return [
- AsyncBenchmarkRun(self._client, run.id, run.benchmark_id)
- for run in page.runs
- if run.benchmark_id is not None
- ]
diff --git a/src/runloop_api_client/sdk/async_benchmark_run.py b/src/runloop_api_client/sdk/async_benchmark_run.py
deleted file mode 100644
index 58b2da40d..000000000
--- a/src/runloop_api_client/sdk/async_benchmark_run.py
+++ /dev/null
@@ -1,127 +0,0 @@
-"""AsyncBenchmarkRun resource class for asynchronous operations."""
-
-from __future__ import annotations
-
-from typing import List
-from typing_extensions import Unpack, override
-
-from ..types import BenchmarkRunView
-from ._types import BaseRequestOptions, LongRequestOptions, SDKBenchmarkRunListScenarioRunsParams
-from .._client import AsyncRunloop
-from .async_scenario_run import AsyncScenarioRun
-
-
-class AsyncBenchmarkRun:
- """A benchmark run for evaluating agent performance across scenarios (async).
-
- Provides async methods for monitoring run status, managing the run lifecycle,
- and accessing scenario run results. Obtain instances via
- ``benchmark.start_run()`` or ``benchmark.list_runs()``.
-
- Example:
- >>> benchmark = runloop.benchmark.from_id("bench-xxx")
- >>> run = await benchmark.start_run(run_name="evaluation-v1")
- >>> info = await run.get_info()
- >>> scenario_runs = await run.list_scenario_runs()
- """
-
- def __init__(self, client: AsyncRunloop, run_id: str, benchmark_id: str) -> None:
- """Create an AsyncBenchmarkRun instance.
-
- :param client: AsyncRunloop client instance
- :type client: AsyncRunloop
- :param run_id: Benchmark run ID
- :type run_id: str
- :param benchmark_id: Parent benchmark ID
- :type benchmark_id: str
- """
- self._client = client
- self._id = run_id
- self._benchmark_id = benchmark_id
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def id(self) -> str:
- """Return the benchmark run ID.
-
- :return: Unique benchmark run ID
- :rtype: str
- """
- return self._id
-
- @property
- def benchmark_id(self) -> str:
- """Return the parent benchmark ID.
-
- :return: Parent benchmark ID
- :rtype: str
- """
- return self._benchmark_id
-
- async def get_info(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> BenchmarkRunView:
- """Retrieve current benchmark run status and metadata.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
- :return: Current benchmark run state info
- :rtype: BenchmarkRunView
- """
- return await self._client.benchmark_runs.retrieve(
- self._id,
- **options,
- )
-
- async def cancel(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> BenchmarkRunView:
- """Cancel the benchmark run.
-
- Stops all running scenarios and marks the run as canceled.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Updated benchmark run state
- :rtype: BenchmarkRunView
- """
- return await self._client.benchmark_runs.cancel(
- self._id,
- **options,
- )
-
- async def complete(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> BenchmarkRunView:
- """Complete the benchmark run.
-
- Marks the run as completed. Call this after all scenarios have finished.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Completed benchmark run state
- :rtype: BenchmarkRunView
- """
- return await self._client.benchmark_runs.complete(
- self._id,
- **options,
- )
-
- async def list_scenario_runs(
- self,
- **params: Unpack[SDKBenchmarkRunListScenarioRunsParams],
- ) -> List[AsyncScenarioRun]:
- """List all scenario runs for this benchmark run.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkRunListScenarioRunsParams` for available parameters
- :return: List of async scenario run objects
- :rtype: List[AsyncScenarioRun]
- """
- page = await self._client.benchmark_runs.list_scenario_runs(
- self._id,
- **params,
- )
- return [AsyncScenarioRun(self._client, run.id, run.devbox_id) for run in page.runs]
diff --git a/src/runloop_api_client/sdk/async_devbox.py b/src/runloop_api_client/sdk/async_devbox.py
index bed785a7d..863080b63 100644
--- a/src/runloop_api_client/sdk/async_devbox.py
+++ b/src/runloop_api_client/sdk/async_devbox.py
@@ -11,12 +11,12 @@
DevboxView,
TunnelView,
DevboxExecutionDetailView,
- DevboxCreateSSHKeyResponse,
)
from ._types import (
LogCallback,
BaseRequestOptions,
LongRequestOptions,
+ DevboxCommandOptions,
PollingRequestOptions,
SDKDevboxExecuteParams,
ExecuteStreamingCallbacks,
@@ -38,7 +38,6 @@
from ..types.devboxes import ExecutionUpdateChunk
from .async_execution import AsyncExecution, _AsyncStreamingGroup
from .async_execution_result import AsyncExecutionResult
-from ..types.devbox_execute_async_params import DevboxNiceExecuteAsyncParams
from ..types.devboxes.devbox_logs_list_view import DevboxLogsListView
from ..types.devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView
@@ -168,18 +167,15 @@ async def logs(
self,
*,
execution_id: str | None = None,
- shell_name: str | None = None,
**options: Unpack[BaseRequestOptions],
) -> DevboxLogsListView:
"""Retrieve logs for the devbox.
Returns all logs from a running or completed devbox. Optionally filter
- by execution ID or shell name.
+ by execution ID.
:param execution_id: Filter logs by execution ID, defaults to None
:type execution_id: str | None, optional
- :param shell_name: Filter logs by shell name, defaults to None
- :type shell_name: str | None, optional
:param options: Optional request configuration
:return: Log entries for the devbox
:rtype: :class:`~runloop_api_client.types.devboxes.devbox_logs_list_view.DevboxLogsListView`
@@ -192,7 +188,6 @@ async def logs(
return await self._client.devboxes.logs.list(
self._id,
execution_id=execution_id if execution_id is not None else omit,
- shell_name=shell_name if shell_name is not None else omit,
**options,
)
@@ -376,39 +371,6 @@ def net(self) -> AsyncNetworkInterface:
"""
return AsyncNetworkInterface(self)
- def shell(self, shell_name: str | None = None) -> AsyncNamedShell:
- """Create a named shell instance for stateful command execution.
-
- Named shells are stateful and maintain environment variables and the current working
- directory (CWD) across commands, just like a real shell on your local computer.
- Commands executed through the same named shell instance will execute sequentially -
- the shell can only run one command at a time with automatic queuing. This ensures
- that environment changes and directory changes from one command are preserved for
- the next command.
-
- :param shell_name: The name of the persistent shell session. If not provided, a UUID will be generated automatically.
- :type shell_name: str | None, optional
- :return: An AsyncNamedShell instance for executing commands in the named shell
- :rtype: AsyncNamedShell
-
- Example:
- >>> # Create a named shell with a custom name
- >>> shell = await devbox.shell("my-session")
- >>> # Create a named shell with an auto-generated UUID name
- >>> shell2 = await devbox.shell()
- >>> # Commands execute sequentially and share state
- >>> await shell.exec("cd /app")
- >>> await shell.exec("export MY_VAR=value")
- >>> result = await shell.exec("echo $MY_VAR") # Will output 'value'
- >>> result = await shell.exec("pwd") # Will output '/app'
- """
- if shell_name is None:
- # uuid_utils is not typed
- from uuid_utils import uuid7 # type: ignore
-
- shell_name = str(uuid7())
- return AsyncNamedShell(self, shell_name)
-
# ------------------------------------------------------------------ #
# Internal helpers
# ------------------------------------------------------------------ #
@@ -519,7 +481,7 @@ async def exec(
execution: DevboxAsyncExecutionDetailView = await client.devboxes.execute_async(
devbox.id,
command=command,
- **filter_params(params, DevboxNiceExecuteAsyncParams),
+ **filter_params(params, DevboxCommandOptions),
**filter_params(params, LongRequestOptions),
)
streaming_group = devbox._start_streaming(
@@ -579,7 +541,7 @@ async def exec_async(
execution: DevboxAsyncExecutionDetailView = await client.devboxes.execute_async(
devbox.id,
command=command,
- **filter_params(params, DevboxNiceExecuteAsyncParams),
+ **filter_params(params, DevboxCommandOptions),
**filter_params(params, LongRequestOptions),
)
@@ -681,131 +643,15 @@ async def upload(
)
-class AsyncNamedShell:
- """Interface for executing commands in a persistent, stateful shell session.
-
- Named shells are stateful and maintain environment variables and the current working
- directory (CWD) across commands. Commands executed through the same named shell
- instance will execute sequentially - the shell can only run one command at a time
- with automatic queuing. This ensures that environment changes and directory changes
- from one command are preserved for the next command.
-
- Use :meth:`AsyncDevbox.shell` to create a named shell instance. If you use the same
- shell name, it will re-attach to the existing named shell, preserving its state.
-
- Example:
- >>> shell = await devbox.shell("my-session")
- >>> await shell.exec("cd /app")
- >>> await shell.exec("export MY_VAR=value")
- >>> result = await shell.exec("echo $MY_VAR") # Will output 'value'
- >>> result = await shell.exec("pwd") # Will output '/app'
- """
-
- def __init__(self, devbox: AsyncDevbox, shell_name: str) -> None:
- """Initialize the named shell.
-
- :param devbox: The async devbox instance to execute commands on
- :type devbox: AsyncDevbox
- :param shell_name: The name of the persistent shell session
- :type shell_name: str
- """
- self._devbox = devbox
- self._shell_name = shell_name
-
- async def exec(
- self,
- command: str,
- **params: Unpack[SDKDevboxExecuteParams],
- ) -> AsyncExecutionResult:
- """Execute a command in the named shell and wait for it to complete.
-
- The command will execute in the persistent shell session, maintaining environment
- variables and the current working directory from previous commands. Commands are
- queued and execute sequentially - only one command runs at a time in the named shell.
-
- Optionally provide callbacks to stream logs in real-time. When callbacks are provided,
- this method waits for both the command to complete AND all streaming data to be
- processed before returning.
-
- :param command: The command to execute
- :type command: str
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKDevboxExecuteParams` for available parameters
- :return: Wrapper with exit status and output helpers
- :rtype: AsyncExecutionResult
-
- Example:
- >>> shell = await devbox.shell("my-session")
- >>> result = await shell.exec("ls -la")
- >>> print(await result.stdout())
- >>> # With streaming callbacks
- >>> result = await shell.exec("npm install", stdout=lambda line: print(f"[LOG] {line}"))
- """
- # Ensure shell_name is set and cannot be overridden by user params
- params["shell_name"] = self._shell_name
- return await self._devbox.cmd.exec(command, **params)
-
- async def exec_async(
- self,
- command: str,
- **params: Unpack[SDKDevboxExecuteAsyncParams],
- ) -> AsyncExecution:
- """Execute a command in the named shell asynchronously without waiting for completion.
-
- The command will execute in the persistent shell session, maintaining environment
- variables and the current working directory from previous commands. Commands are
- queued and execute sequentially - only one command runs at a time in the named shell.
-
- Optionally provide callbacks to stream logs in real-time as they are produced.
- Callbacks fire in real-time as logs arrive. When you call execution.result(),
- it will wait for both the command to complete and all streaming to finish.
-
- :param command: The command to execute
- :type command: str
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKDevboxExecuteAsyncParams` for available parameters
- :return: Handle for managing the running process
- :rtype: AsyncExecution
-
- Example:
- >>> shell = await devbox.shell("my-session")
- >>> execution = await shell.exec_async("long-running-task.sh", stdout=lambda line: print(f"[LOG] {line}"))
- >>> # Do other work while command runs...
- >>> result = await execution.result()
- >>> if result.success:
- ... print("Task completed successfully!")
- """
- # Ensure shell_name is set and cannot be overridden by user params
- params["shell_name"] = self._shell_name
- return await self._devbox.cmd.exec_async(command, **params)
-
-
class AsyncNetworkInterface:
"""Interface for networking operations on a devbox.
- Accessed via devbox.net property. Provides coroutines for SSH access and tunneling.
+ Accessed via devbox.net property. Provides coroutines for tunneling.
"""
def __init__(self, devbox: AsyncDevbox) -> None:
self._devbox = devbox
- async def create_ssh_key(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> DevboxCreateSSHKeyResponse:
- """Create an SSH key for remote access to the devbox.
-
- :param options: Optional long-running request configuration
- :return: Response containing SSH connection info
- :rtype: DevboxCreateSSHKeyResponse
-
- Example:
- >>> ssh_key = await devbox.net.create_ssh_key()
- >>> print(f"SSH URL: {ssh_key.url}")
- """
- return await self._devbox._client.devboxes.create_ssh_key(
- self._devbox.id,
- **options,
- )
-
async def enable_tunnel(
self,
**params: Unpack[SDKDevboxEnableTunnelParams],
diff --git a/src/runloop_api_client/sdk/async_execution.py b/src/runloop_api_client/sdk/async_execution.py
index 073113bca..7d3a50dba 100644
--- a/src/runloop_api_client/sdk/async_execution.py
+++ b/src/runloop_api_client/sdk/async_execution.py
@@ -9,6 +9,7 @@
from ._types import BaseRequestOptions, LongRequestOptions
from .._client import AsyncRunloop
+from .._exceptions import RunloopError
from .async_execution_result import AsyncExecutionResult
from ..types.devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView
@@ -132,6 +133,38 @@ async def get_state(self, **options: Unpack[BaseRequestOptions]) -> DevboxAsyncE
**options,
)
+ async def send_std_in(self, text: str, **options: Unpack[LongRequestOptions]) -> None:
+ """Send nonempty text to an execution started with ``attach_stdin=True``.
+
+ Complete each send before the next send or ``close_std_in``. Concurrent
+ calls are not ordered. Client retries can replay input after an ambiguous
+ connection failure; delivery is not guaranteed to be exactly once.
+
+ :param text: Text to write to stdin
+ :param options: Request configuration forwarded to the generated client
+ :raises RunloopError: If the API reports unsuccessful delivery
+ """
+ response = await self._client.devboxes.executions.send_std_in(
+ self._execution_id, devbox_id=self._devbox_id, text=text, **options
+ )
+ if not response.success:
+ raise RunloopError(f"Failed to send stdin to execution {self._execution_id}")
+
+ async def close_std_in(self, **options: Unpack[LongRequestOptions]) -> None:
+ """Send EOF after all input has been delivered so the execution can finish.
+
+ Requires ``attach_stdin=True``. Wait for pending sends before closing.
+ This handle does not serialize calls or guarantee exactly-once delivery.
+
+ :param options: Request configuration forwarded to the generated client
+ :raises RunloopError: If the API reports unsuccessful delivery
+ """
+ response = await self._client.devboxes.executions.send_std_in(
+ self._execution_id, devbox_id=self._devbox_id, signal="EOF", **options
+ )
+ if not response.success:
+ raise RunloopError(f"Failed to send stdin to execution {self._execution_id}")
+
async def kill(self, **options: Unpack[LongRequestOptions]) -> None:
"""Request termination of the running execution.
diff --git a/src/runloop_api_client/sdk/async_scenario.py b/src/runloop_api_client/sdk/async_scenario.py
deleted file mode 100644
index 02ee8f7f3..000000000
--- a/src/runloop_api_client/sdk/async_scenario.py
+++ /dev/null
@@ -1,118 +0,0 @@
-"""AsyncScenario resource class for asynchronous operations."""
-
-from __future__ import annotations
-
-from typing_extensions import Unpack, override
-
-from ..types import ScenarioView
-from ._types import BaseRequestOptions, SDKScenarioRunParams, SDKScenarioUpdateParams, SDKScenarioRunAsyncParams
-from .._client import AsyncRunloop
-from .async_scenario_run import AsyncScenarioRun
-
-
-class AsyncScenario:
- """A scenario for evaluating agent performance (async).
-
- Provides async methods for retrieving scenario details, updating the scenario,
- and starting scenario runs. Obtain instances via ``runloop.scenario.from_id()``
- or ``runloop.scenario.list()``.
-
- Example:
- >>> scenario = runloop.scenario.from_id("scn-xxx")
- >>> info = await scenario.get_info()
- >>> run = await scenario.run(run_name="test-run")
- """
-
- def __init__(self, client: AsyncRunloop, scenario_id: str) -> None:
- """Create an AsyncScenario instance.
-
- :param client: AsyncRunloop client instance
- :type client: AsyncRunloop
- :param scenario_id: Scenario ID
- :type scenario_id: str
- """
- self._client = client
- self._id = scenario_id
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def id(self) -> str:
- """Return the scenario ID.
-
- :return: Unique scenario ID
- :rtype: str
- """
- return self._id
-
- async def get_info(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> ScenarioView:
- """Retrieve current scenario details.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
- :return: Current scenario info
- :rtype: ScenarioView
- """
- return await self._client.scenarios.retrieve(
- self._id,
- **options,
- )
-
- async def update(
- self,
- **params: Unpack[SDKScenarioUpdateParams],
- ) -> ScenarioView:
- """Update the scenario.
-
- Only provided fields will be updated.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioUpdateParams` for available parameters
- :return: Updated scenario info
- :rtype: ScenarioView
- """
- return await self._client.scenarios.update(
- self._id,
- **params,
- )
-
- async def run_async(
- self,
- **params: Unpack[SDKScenarioRunAsyncParams],
- ) -> AsyncScenarioRun:
- """Start a new scenario run without waiting for the devbox.
-
- Creates a new scenario run and returns immediately. The devbox may still
- be starting; call ``await_env_ready()`` on the returned AsyncScenarioRun
- to wait for it to be ready.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioRunAsyncParams` for available parameters
- :return: AsyncScenarioRun instance for managing the run
- :rtype: AsyncScenarioRun
- """
- run_view = await self._client.scenarios.start_run(
- scenario_id=self._id,
- **params,
- )
- return AsyncScenarioRun(self._client, run_view.id, run_view.devbox_id)
-
- async def run(
- self,
- **params: Unpack[SDKScenarioRunParams],
- ) -> AsyncScenarioRun:
- """Start a new scenario run and wait for the devbox to be ready.
-
- Convenience method that starts a run and waits for the devbox to be ready.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioRunParams` for available parameters
- :return: AsyncScenarioRun instance with ready devbox
- :rtype: AsyncScenarioRun
- """
- run_view = await self._client.scenarios.start_run_and_await_env_ready(
- scenario_id=self._id,
- **params,
- )
- return AsyncScenarioRun(self._client, run_view.id, run_view.devbox_id)
diff --git a/src/runloop_api_client/sdk/async_scenario_builder.py b/src/runloop_api_client/sdk/async_scenario_builder.py
deleted file mode 100644
index 37a3aa4b5..000000000
--- a/src/runloop_api_client/sdk/async_scenario_builder.py
+++ /dev/null
@@ -1,480 +0,0 @@
-"""AsyncScenarioBuilder for constructing scenarios with a fluent API."""
-
-from __future__ import annotations
-
-from typing import Dict, List, Iterable, Optional
-from typing_extensions import Self, Unpack, Literal, override
-
-from ..types import ScenarioCreateParams, ScenarioEnvironmentParam
-from ._types import ScenarioPreview, LongRequestOptions
-from .._client import AsyncRunloop
-from .async_scenario import AsyncScenario
-from .async_snapshot import AsyncSnapshot
-from .async_blueprint import AsyncBlueprint
-from ..types.scoring_function_param import (
- Scorer,
- ScoringFunctionParam,
- ScorerCustomScoringFunction,
- ScorerAstGrepScoringFunction,
- ScorerCommandScoringFunction,
- ScorerTestBasedScoringFunction,
- ScorerBashScriptScoringFunction,
- ScorerPythonScriptScoringFunction,
- ScorerTestBasedScoringFunctionTestFile,
-)
-
-
-class AsyncScenarioBuilder:
- """Async builder for constructing scenarios with a fluent API.
-
- Provides a step-by-step interface for configuring all aspects of a scenario
- before pushing it to the platform.
-
- Example:
- >>> builder = (
- ... runloop.scenario.builder("my-scenario")
- ... .from_blueprint(blueprint)
- ... .with_working_directory("/app")
- ... .with_problem_statement("Fix the bug in main.py")
- ... .add_test_command_scorer("tests", test_command="pytest")
- ... )
- >>> params = builder.build()
- >>> scenario = await runloop.scenario.create(**params) # equivalent to builder.push()
- """
-
- def __init__(self, name: str, client: AsyncRunloop) -> None:
- """Initialize the builder.
-
- :param name: Name for the scenario
- :type name: str
- :param client: AsyncRunloop client instance
- :type client: AsyncRunloop
- """
- self._client = client
- self._name = name
-
- # Environment configuration
- self._blueprint: Optional[AsyncBlueprint] = None
- self._snapshot: Optional[AsyncSnapshot] = None
- self._working_directory: Optional[str] = None
-
- # Input context
- self._problem_statement: Optional[str] = None
- self._additional_context: Optional[object] = None
-
- # Scoring
- self._scorers: List[ScoringFunctionParam] = []
-
- # Metadata and other options
- self._metadata: Dict[str, str] = {}
- self._reference_output: Optional[str] = None
- self._required_env_vars: Optional[List[str]] = None
- self._required_secrets: Optional[List[str]] = None
- self._validation_type: Optional[Literal["UNSPECIFIED", "FORWARD", "REVERSE", "EVALUATION"]] = None
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def name(self) -> str:
- """Return the scenario name.
-
- :return: Scenario name
- :rtype: str
- """
- return self._name
-
- def from_blueprint(self, blueprint: AsyncBlueprint) -> Self:
- """Set a blueprint to define the baseline environment for the scenario.
-
- :param blueprint: Blueprint to use
- :type blueprint: AsyncBlueprint
- :return: Self for method chaining
- :rtype: Self
- """
- self._blueprint = blueprint
- self._snapshot = None # Clear snapshot if blueprint is set
- return self
-
- def from_snapshot(self, snapshot: AsyncSnapshot) -> Self:
- """Set a snapshot to define the baseline environment for the scenario.
-
- :param snapshot: Snapshot to use
- :type snapshot: AsyncSnapshot
- :return: Self for method chaining
- :rtype: Self
- """
- self._snapshot = snapshot
- self._blueprint = None # Clear blueprint if snapshot is set
- return self
-
- def with_working_directory(self, directory: str) -> Self:
- """Set the working directory for the scenario.
-
- :param directory: Working directory path
- :type directory: str
- :return: Self for method chaining
- :rtype: Self
- """
- self._working_directory = directory
- return self
-
- def with_problem_statement(self, statement: str) -> Self:
- """Set the problem statement for the scenario; this will be provided as input context to the agent.
-
- :param statement: Problem statement text
- :type statement: str
- :return: Self for method chaining
- :rtype: Self
- """
- self._problem_statement = statement
- return self
-
- def with_additional_context(self, context: object) -> Self:
- """Set additional structured context for the scenario.
- This can be used to provide additional information to the agent, such as hints, examples, or other relevant information.
-
- :param context: Additional context (JSON-serializable)
- :type context: object
- :return: Self for method chaining
- :rtype: Self
- """
- self._additional_context = context
- return self
-
- def _add_scorer(self, name: str, weight: float, scorer: Scorer) -> Self:
- """Internal helper to add a scorer to the list.
-
- :raises ValueError: If weight is not positive
- """
- if weight <= 0:
- raise ValueError(f"Scorer weight must be positive, got {weight}")
- self._scorers.append({"name": name, "weight": weight, "scorer": scorer})
- return self
-
- def add_test_command_scorer(
- self,
- name: str,
- *,
- test_command: str,
- weight: float = 1.0,
- test_files: Optional[Iterable[ScorerTestBasedScoringFunctionTestFile]] = None,
- ) -> Self:
- """Add a test-based scorer that runs a test command.
-
- :param name: Name of the scoring function
- :type name: str
- :param test_command: Command to run tests (e.g., "pytest")
- :type test_command: str
- :param weight: Weight for this scorer (normalized automatically)
- :type weight: float
- :param test_files: Optional test files to create before running
- :type test_files: Optional[Iterable[ScorerTestBasedScoringFunctionTestFile]]
- :return: Self for method chaining
- :rtype: Self
- """
- scorer: ScorerTestBasedScoringFunction = {
- "type": "test_based_scorer",
- "test_command": test_command,
- }
- if test_files:
- scorer["test_files"] = test_files
- return self._add_scorer(name, weight, scorer)
-
- def add_shell_command_scorer(
- self,
- name: str,
- *,
- command: str,
- weight: float = 1.0,
- ) -> Self:
- """Add a command scorer that runs a shell command.
-
- :param name: Name of the scoring function
- :type name: str
- :param command: Shell command to execute
- :type command: str
- :param weight: Weight for this scorer (normalized automatically)
- :type weight: float
- :return: Self for method chaining
- :rtype: Self
- """
- scorer: ScorerCommandScoringFunction = {
- "type": "command_scorer",
- "command": command,
- }
- return self._add_scorer(name, weight, scorer)
-
- def add_bash_script_scorer(
- self,
- name: str,
- *,
- bash_script: str,
- weight: float = 1.0,
- ) -> Self:
- """Add a standalone bash script scorer.
-
- The script should output "score=X.X" where X.X is a float between 0.0 and 1.0, inclusive.
-
- :param name: Name of the scoring function
- :type name: str
- :param bash_script: Bash script content
- :type bash_script: str
- :param weight: Weight for this scorer (normalized automatically)
- :type weight: float
- :return: Self for method chaining
- :rtype: Self
- """
- scorer: ScorerBashScriptScoringFunction = {
- "type": "bash_script_scorer",
- "bash_script": bash_script,
- }
- return self._add_scorer(name, weight, scorer)
-
- def add_python_script_scorer(
- self,
- name: str,
- *,
- python_script: str,
- weight: float = 1.0,
- python_version_constraint: Optional[str] = None,
- requirements_contents: Optional[str] = None,
- ) -> Self:
- """Add a standalone Python script scorer.
-
- The script is run in an isolated uv environment, and the dependencies are declared in the
- `uv script header `__.
-
- The script should print the score in the range [0.0, 1.0] to stdout.
-
- :param name: Name of the scoring function
- :type name: str
- :param python_script: Python script content
- :type python_script: str
- :param weight: Weight for this scorer (normalized automatically)
- :type weight: float
- :param python_version_constraint: Python version (default "==3.12.10")
- :type python_version_constraint: Optional[str]
- :param requirements_contents: pip requirements.txt content
- :type requirements_contents: Optional[str]
- :return: Self for method chaining
- :rtype: Self
- """
- scorer: ScorerPythonScriptScoringFunction = {
- "type": "python_script_scorer",
- "python_script": python_script,
- }
- if python_version_constraint:
- scorer["python_version_constraint"] = python_version_constraint
- if requirements_contents:
- scorer["requirements_contents"] = requirements_contents
- return self._add_scorer(name, weight, scorer)
-
- def add_ast_grep_scorer(
- self,
- name: str,
- *,
- pattern: str,
- weight: float = 1.0,
- search_directory: str = ".",
- lang: Optional[str] = None,
- ) -> Self:
- """Add an AST grep scorer that matches code patterns.
-
- :param name: Name of the scoring function
- :type name: str
- :param pattern: AST pattern to match
- :type pattern: str
- :param weight: Weight for this scorer (normalized automatically)
- :type weight: float
- :param search_directory: Directory to search (default ".")
- :type search_directory: str
- :param lang: Language of the pattern (optional)
- :type lang: Optional[str]
- :return: Self for method chaining
- :rtype: Self
- """
- scorer: ScorerAstGrepScoringFunction = {
- "type": "ast_grep_scorer",
- "pattern": pattern,
- "search_directory": search_directory,
- }
- if lang:
- scorer["lang"] = lang
- return self._add_scorer(name, weight, scorer)
-
- def add_custom_scorer(
- self,
- name: str,
- *,
- custom_scorer_type: str,
- weight: float = 1.0,
- scorer_params: Optional[object] = None,
- ) -> Self:
- """Add a custom scorer registered with Runloop.
-
- :param name: Name of the scoring function
- :type name: str
- :param custom_scorer_type: Type identifier registered with Runloop
- :type custom_scorer_type: str
- :param weight: Weight for this scorer (normalized automatically)
- :type weight: float
- :param scorer_params: Additional JSON parameters for the scorer
- :type scorer_params: Optional[object]
- :return: Self for method chaining
- :rtype: Self
- """
- scorer: ScorerCustomScoringFunction = {
- "type": "custom_scorer",
- "custom_scorer_type": custom_scorer_type,
- }
- if scorer_params:
- scorer["scorer_params"] = scorer_params
- return self._add_scorer(name, weight, scorer)
-
- def with_metadata(self, metadata: Dict[str, str]) -> Self:
- """Set metadata for the scenario.
-
- :param metadata: Key-value metadata
- :type metadata: Dict[str, str]
- :return: Self for method chaining
- :rtype: Self
- """
- self._metadata = metadata
- return self
-
- def with_reference_output(self, output: str) -> Self:
- """Set the reference solution or gold patch for validation.
- After application, the scorer is expected to return a score of 1.0.
-
- :param output: Reference solution or gold patch (e.g., git diff)
- :type output: str
- :return: Self for method chaining
- :rtype: Self
- """
- self._reference_output = output
- return self
-
- def with_required_env_vars(self, env_vars: List[str]) -> Self:
- """Set required environment variables.
-
- :param env_vars: List of required environment variable names
- :type env_vars: List[str]
- :return: Self for method chaining
- :rtype: Self
- """
- self._required_env_vars = env_vars
- return self
-
- def with_required_secrets(self, secrets: List[str]) -> Self:
- """Set required secrets.
-
- :param secrets: List of required secret names
- :type secrets: List[str]
- :return: Self for method chaining
- :rtype: Self
- """
- self._required_secrets = secrets
- return self
-
- def with_validation_type(self, validation_type: Literal["UNSPECIFIED", "FORWARD", "REVERSE", "EVALUATION"]) -> Self:
- """Set the validation strategy to specify how the reference solution or gold patch is applied to the scenario.
-
- :param validation_type: Validation type
- :type validation_type: Literal["UNSPECIFIED", "FORWARD", "REVERSE", "EVALUATION"]
- :return: Self for method chaining
- :rtype: Self
- """
- self._validation_type = validation_type
- return self
-
- def _build_normalized_scorers(self) -> List[ScoringFunctionParam]:
- """Build normalized scorers list."""
- total_weight = sum(s["weight"] for s in self._scorers)
- return [{**s, "weight": s["weight"] / total_weight} for s in self._scorers]
-
- def _build_environment_params(self) -> Optional[ScenarioEnvironmentParam]:
- """Build environment parameters."""
- if not self._blueprint and not self._snapshot and not self._working_directory:
- return None
- return {
- "blueprint_id": self._blueprint.id if self._blueprint else None,
- "snapshot_id": self._snapshot.id if self._snapshot else None,
- "working_directory": self._working_directory if self._working_directory else None,
- }
-
- def build(self) -> ScenarioCreateParams:
- """Build the scenario creation parameters.
-
- Weights are automatically normalized to sum to 1.0.
-
- :raises ValueError: If required fields are missing
- :return: Parameters for scenario creation
- :rtype: ScenarioCreateParams
- """
- if not self._problem_statement:
- raise ValueError("Problem statement is required. Call with_problem_statement() first.")
-
- if not self._scorers:
- raise ValueError(
- "At least one scorer is required. "
- "Call add_test_command_scorer(), add_bash_script_scorer(), or another scorer method first."
- )
-
- return {
- "name": self._name,
- "input_context": {
- "problem_statement": self._problem_statement,
- "additional_context": self._additional_context,
- },
- "scoring_contract": {
- "scoring_function_parameters": self._build_normalized_scorers(),
- },
- "environment_parameters": self._build_environment_params(),
- "metadata": self._metadata,
- "reference_output": self._reference_output,
- "required_environment_variables": self._required_env_vars,
- "required_secret_names": self._required_secrets,
- "validation_type": self._validation_type,
- }
-
- def preview(self) -> ScenarioPreview:
- """Preview the scenario configuration without pushing to the platform.
-
- Returns the current configuration state as a ScenarioPreview object.
- Does not validate or raise errors for missing required fields.
-
- :return: Preview of the scenario configuration
- :rtype: ScenarioPreview
- """
- return ScenarioPreview.model_validate(
- {
- "name": self._name,
- "input_context": {
- "problem_statement": self._problem_statement,
- "additional_context": self._additional_context,
- },
- "scoring_contract": {
- "scoring_function_parameters": self._build_normalized_scorers(),
- },
- "environment": self._build_environment_params(),
- "metadata": self._metadata,
- "reference_output": self._reference_output,
- "required_environment_variables": self._required_env_vars,
- "required_secret_names": self._required_secrets,
- "validation_type": self._validation_type,
- }
- )
-
- async def push(self, **options: Unpack[LongRequestOptions]) -> AsyncScenario:
- """Create the scenario on the platform.
-
- :param options: Optional long-running request configuration
- :raises ValueError: If required fields are missing
- :return: Created scenario wrapper
- :rtype: AsyncScenario
- """
- params = self.build()
- scenario_view = await self._client.scenarios.create(**params, **options)
- return AsyncScenario(self._client, scenario_view.id)
diff --git a/src/runloop_api_client/sdk/async_scenario_run.py b/src/runloop_api_client/sdk/async_scenario_run.py
deleted file mode 100644
index 314de676f..000000000
--- a/src/runloop_api_client/sdk/async_scenario_run.py
+++ /dev/null
@@ -1,242 +0,0 @@
-"""AsyncScenarioRun resource class for asynchronous operations."""
-
-from __future__ import annotations
-
-import os
-from typing import Union, Optional
-from functools import cached_property
-from typing_extensions import Unpack, override
-
-from ..types import ScenarioRunView
-from ._types import BaseRequestOptions, LongRequestOptions, PollingRequestOptions
-from .._client import AsyncRunloop
-from ._helpers import filter_params
-from .async_devbox import AsyncDevbox
-from ..types.scoring_contract_result_view import ScoringContractResultView
-
-
-class AsyncScenarioRun:
- """A running scenario with devbox access (async).
-
- Provides async methods for managing the scenario run lifecycle, accessing
- the devbox, and retrieving scoring results. Obtain instances via
- ``scenario.run()`` or ``scenario.run_async()``.
-
- Example:
- >>> scenario = runloop.scenario.from_id("scn-xxx")
- >>> run = await scenario.run_async()
- >>> await run.await_env_ready()
- >>> devbox = run.devbox
- >>> # ... agent does work on the devbox ...
- >>> await run.score_and_await()
- >>> score = await run.get_score()
- """
-
- def __init__(self, client: AsyncRunloop, run_id: str, devbox_id: str) -> None:
- """Create an AsyncScenarioRun instance.
-
- :param client: AsyncRunloop client instance
- :type client: AsyncRunloop
- :param run_id: Scenario run ID
- :type run_id: str
- :param devbox_id: Devbox ID associated with this run
- :type devbox_id: str
- """
- self._client = client
- self._id = run_id
- self._devbox_id = devbox_id
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def id(self) -> str:
- """Return the scenario run ID.
-
- :return: Unique scenario run ID
- :rtype: str
- """
- return self._id
-
- @property
- def devbox_id(self) -> str:
- """Return the devbox ID associated with this run.
-
- :return: Devbox ID
- :rtype: str
- """
- return self._devbox_id
-
- @cached_property
- def devbox(self) -> AsyncDevbox:
- """The devbox instance for this scenario run.
-
- Use this to interact with the devbox environment during the scenario run.
-
- :return: AsyncDevbox instance
- :rtype: AsyncDevbox
- """
- return AsyncDevbox(self._client, self._devbox_id)
-
- async def get_info(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> ScenarioRunView:
- """Retrieve current scenario run status and metadata.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
- :return: Current scenario run state info
- :rtype: ScenarioRunView
- """
- return await self._client.scenarios.runs.retrieve(
- self._id,
- **options,
- )
-
- async def await_env_ready(
- self,
- **options: Unpack[PollingRequestOptions],
- ) -> ScenarioRunView:
- """Wait for the scenario environment (devbox) to be ready.
-
- Blocks until the devbox reaches running state.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options
- :return: Scenario run state after environment is ready
- :rtype: ScenarioRunView
- """
- await self._client.devboxes.await_running(self._devbox_id, polling_config=options.get("polling_config"))
- return await self.get_info(**filter_params(options, BaseRequestOptions))
-
- async def score(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> ScenarioRunView:
- """Submit the scenario run for scoring.
-
- This triggers the scoring process using the scenario's scoring contract.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Updated scenario run state
- :rtype: ScenarioRunView
- """
- return await self._client.scenarios.runs.score(
- self._id,
- **options,
- )
-
- async def await_scored(
- self,
- **options: Unpack[PollingRequestOptions],
- ) -> ScenarioRunView:
- """Wait for the scenario run to be scored.
-
- Blocks until scoring is complete.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options
- :return: Scored scenario run state
- :rtype: ScenarioRunView
- """
- return await self._client.scenarios.runs.await_scored(
- self._id,
- **options,
- )
-
- async def score_and_await(
- self,
- **options: Unpack[PollingRequestOptions],
- ) -> ScenarioRunView:
- """Submit for scoring and wait for completion.
-
- Convenience method that calls score() then await_scored().
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options
- :return: Scored scenario run state
- :rtype: ScenarioRunView
- """
- return await self._client.scenarios.runs.score_and_await(
- self._id,
- **options,
- )
-
- async def score_and_complete(
- self,
- **options: Unpack[PollingRequestOptions],
- ) -> ScenarioRunView:
- """Score the run, wait for scoring, then complete and shutdown.
-
- Convenience method that scores the scenario run, waits for scoring to
- finish, then completes the run and shuts down the devbox.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options
- :return: Completed scenario run state with scoring results
- :rtype: ScenarioRunView
- """
- return await self._client.scenarios.runs.score_and_complete(
- self._id,
- **options,
- )
-
- async def complete(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> ScenarioRunView:
- """Complete the scenario run and shutdown the devbox.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Final scenario run state
- :rtype: ScenarioRunView
- """
- return await self._client.scenarios.runs.complete(
- self._id,
- **options,
- )
-
- async def cancel(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> ScenarioRunView:
- """Cancel the scenario run and shutdown the devbox.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Cancelled scenario run state
- :rtype: ScenarioRunView
- """
- return await self._client.scenarios.runs.cancel(
- self._id,
- **options,
- )
-
- async def download_logs(
- self,
- file: Union[str, os.PathLike[str]],
- **options: Unpack[LongRequestOptions],
- ) -> None:
- """Download all logs for this scenario run to a zip file.
-
- Downloads a zip archive containing all logs from the scenario run's
- associated devbox.
-
- :param file: Path where the zip file will be written
- :type file: str | os.PathLike[str]
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- """
- response = await self._client.scenarios.runs.download_logs(self._id, **options)
- await response.write_to_file(file)
-
- async def get_score(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> Optional[ScoringContractResultView]:
- """Get the scoring result for this run.
-
- Returns None if the run has not been scored yet. Always makes an API
- call to retrieve the current scoring result.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
- :return: Scoring result or None if not yet scored
- :rtype: Optional[ScoringContractResultView]
- """
- info = await self.get_info(**options)
- return info.scoring_contract_result
diff --git a/src/runloop_api_client/sdk/async_scorer.py b/src/runloop_api_client/sdk/async_scorer.py
deleted file mode 100644
index a312a0bd0..000000000
--- a/src/runloop_api_client/sdk/async_scorer.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""Scorer resource class for asynchronous operations."""
-
-from __future__ import annotations
-
-from typing_extensions import Unpack, override
-
-from ._types import (
- BaseRequestOptions,
- SDKScorerUpdateParams,
-)
-from .._client import AsyncRunloop
-from ..types.scenarios import ScorerUpdateResponse, ScorerRetrieveResponse
-
-
-class AsyncScorer:
- """A custom scorer for evaluating scenario outputs (async).
-
- Scorers define bash scripts that produce a score in the range [0.0, 1.0] for scenario runs.
- Obtain instances via ``runloop.scorer.create()`` or ``runloop.scorer.from_id()``.
-
- Example:
- >>> runloop = AsyncRunloopSDK()
- >>> scorer = await runloop.scorer.create(type="my_scorer", bash_script="echo 'score=1.0'")
- """
-
- def __init__(self, client: AsyncRunloop, scorer_id: str) -> None:
- """Create an AsyncScorer instance.
-
- :param client: AsyncRunloop client instance
- :type client: AsyncRunloop
- :param scorer_id: ID of the scorer
- :type scorer_id: str
- """
- self._client = client
- self._id = scorer_id
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def id(self) -> str:
- """The scorer's unique identifier.
-
- :return: Scorer ID
- :rtype: str
- """
- return self._id
-
- async def get_info(self, **options: Unpack[BaseRequestOptions]) -> ScorerRetrieveResponse:
- """Fetch current scorer details from the API.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
- :return: Current scorer details
- :rtype: ScorerRetrieveResponse
- """
- return await self._client.scenarios.scorers.retrieve(self._id, **options)
-
- async def update(self, **params: Unpack[SDKScorerUpdateParams]) -> ScorerUpdateResponse:
- """Update the scorer's type or bash script.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerUpdateParams` for available parameters
- :return: Updated scorer details
- :rtype: ScorerUpdateResponse
- """
- return await self._client.scenarios.scorers.update(self._id, **params)
diff --git a/src/runloop_api_client/sdk/async_secret.py b/src/runloop_api_client/sdk/async_secret.py
index 189212c42..ca347601b 100644
--- a/src/runloop_api_client/sdk/async_secret.py
+++ b/src/runloop_api_client/sdk/async_secret.py
@@ -13,8 +13,10 @@ class AsyncSecret:
"""Asynchronous wrapper around a secret resource.
Secrets are encrypted key-value pairs that can be securely stored and injected
- into Devboxes as environment variables. Secrets are identified by their globally
- unique name.
+ into Devboxes as environment variables. This wrapper always operates by its account-scoped
+ name, even if it contains an ID from an earlier response. Reads and updates
+ select the greatest ID at lookup; deletion removes all selected matches,
+ nonatomically. Use ``sdk.secret.from_id(id)`` for exact row operations.
Example:
>>> runloop = AsyncRunloopSDK()
@@ -36,7 +38,7 @@ def __init__(
:param client: Generated AsyncRunloop client
:type client: AsyncRunloop
- :param name: The globally unique name of the secret
+ :param name: The literal account-scoped name of the secret
:type name: str
:param id: The secret ID (optional, may not be known until get_info is called)
:type id: str | None
@@ -53,7 +55,7 @@ def __repr__(self) -> str:
def id(self) -> str | None:
"""Return the secret ID.
- :return: Secret ID, or None if not yet fetched from API
+ :return: Captured response ID, or None. Fetching metadata does not refresh this property.
:rtype: str | None
"""
return self._id
@@ -62,7 +64,7 @@ def id(self) -> str | None:
def name(self) -> str:
"""Return the secret name.
- :return: Globally unique secret name
+ :return: Literal account-scoped secret name
:rtype: str
"""
return self._name
@@ -93,7 +95,9 @@ async def update(
value: str,
**options: Unpack[LongRequestOptions],
) -> SecretView:
- """Update this secret's value.
+ """Update the greatest-ID row matching this name at lookup.
+
+ This wrapper remains name-bound and its captured ID is unchanged.
Example:
>>> updated = await secret.update("new-secret-value")
@@ -115,7 +119,9 @@ async def delete(
self,
**options: Unpack[LongRequestOptions],
) -> SecretView:
- """Delete this secret. This action is irreversible.
+ """Delete every selected row matching this name, nonatomically.
+
+ A concurrent create can survive. This action is irreversible.
Example:
>>> await secret.delete()
diff --git a/src/runloop_api_client/sdk/async_secret_by_id.py b/src/runloop_api_client/sdk/async_secret_by_id.py
new file mode 100644
index 000000000..92449f96b
--- /dev/null
+++ b/src/runloop_api_client/sdk/async_secret_by_id.py
@@ -0,0 +1,44 @@
+"""Exact-ID Secret operations, separate from legacy name selectors."""
+
+from __future__ import annotations
+
+from typing_extensions import Unpack, override
+
+from ._types import BaseRequestOptions, LongRequestOptions
+from .._client import AsyncRunloop
+from ..types.secret_view import SecretView
+
+
+class AsyncSecretById:
+ """An exact Secret row, obtained with ``sdk.secret.from_id(id)``.
+
+ Construction makes no request. The ID never changes, but another writer can
+ change the value. Requests retain the generated client's retry policy and are
+ not exactly-once operations. Use ``get_info()`` for the name and metadata;
+ values are never returned.
+ """
+
+ def __init__(self, client: AsyncRunloop, id: str) -> None:
+ self._client = client
+ self._id = id
+
+ @override
+ def __repr__(self) -> str:
+ return f""
+
+ @property
+ def id(self) -> str:
+ """The exact row ID, not a name or an immutable value-version identifier."""
+ return self._id
+
+ async def get_info(self, **options: Unpack[BaseRequestOptions]) -> SecretView:
+ """Retrieve this row's metadata. Missing IDs never fall back to names."""
+ return await self._client.secrets.retrieve_by_id(self._id, **options)
+
+ async def update(self, value: str, **options: Unpack[LongRequestOptions]) -> SecretView:
+ """Replace this row's value, without changing running devbox environments or issued tokens."""
+ return await self._client.secrets.update_by_id(self._id, value=value, **options)
+
+ async def delete(self, **options: Unpack[LongRequestOptions]) -> SecretView:
+ """Delete only this row. Name readers may then see an older same-name row."""
+ return await self._client.secrets.delete_by_id(self._id, **options)
diff --git a/src/runloop_api_client/sdk/async_storage_object.py b/src/runloop_api_client/sdk/async_storage_object.py
index ca81ce1e3..8d81ff97e 100644
--- a/src/runloop_api_client/sdk/async_storage_object.py
+++ b/src/runloop_api_client/sdk/async_storage_object.py
@@ -2,9 +2,11 @@
from __future__ import annotations
-from typing import Iterable
+from typing import Mapping, Iterable
from typing_extensions import Unpack, override
+import httpx
+
from ._types import BaseRequestOptions, LongRequestOptions, SDKObjectDownloadParams
from .._client import AsyncRunloop
from ..types.object_view import ObjectView
@@ -15,7 +17,13 @@
class AsyncStorageObject:
"""Async wrapper around storage object operations, including uploads and downloads."""
- def __init__(self, client: AsyncRunloop, object_id: str, upload_url: str | None) -> None:
+ def __init__(
+ self,
+ client: AsyncRunloop,
+ object_id: str,
+ upload_url: str | None,
+ upload_headers: Mapping[str, str] | None = None,
+ ) -> None:
"""Initialize the wrapper.
:param client: Generated AsyncRunloop client
@@ -24,10 +32,12 @@ def __init__(self, client: AsyncRunloop, object_id: str, upload_url: str | None)
:type object_id: str
:param upload_url: Optional pre-signed upload URL if the object is still open, defaults to None
:type upload_url: str | None, optional
+ :param upload_headers: Required signed-upload headers returned with the URL; copied on construction
"""
self._client = client
self._id = object_id
self._upload_url = upload_url
+ self._upload_headers = dict(upload_headers or {})
@override
def __repr__(self) -> str:
@@ -81,6 +91,7 @@ async def complete(
**options,
)
self._upload_url = None
+ self._upload_headers = {}
return result
async def get_download_url(
@@ -151,16 +162,30 @@ async def delete(
async def upload_content(self, content: str | bytes | Iterable[bytes]) -> None:
"""Upload content to the object's pre-signed URL.
+ The URL and required headers are sensitive upload credentials. Requests use
+ the configured HTTP transport, but not its default headers, cookies, or auth.
+ Custom transport implementations and event hooks must preserve this isolation.
+ Uploads are not retried and redirects are not followed.
+
:param content: Bytes payload, text payload, or an iterable streaming bytes
:type content: str | bytes | Iterable[bytes]
:return: None
:rtype: None
:raises RuntimeError: If no upload URL is available
- :raises httpx.HTTPStatusError: Propagated from the underlying ``httpx`` client when the upload fails
+ :raises httpx.HTTPStatusError: If storage rejects the upload (message excludes signed credentials)
+ :raises httpx.RequestError: If the upload cannot be sent
"""
url = self._ensure_upload_url()
- response = await self._client._client.put(url, content=content)
- response.raise_for_status()
+ # Construct directly so HTTPX does not merge API-client defaults into storage requests.
+ request = httpx.Request("PUT", url, content=content, headers=self._upload_headers)
+ try:
+ response = await self._client._client.send(request, auth=None, follow_redirects=False)
+ except httpx.RequestError:
+ raise httpx.RequestError("Storage upload failed during transport", request=request) from None
+ if not response.is_success:
+ raise httpx.HTTPStatusError(
+ f"Storage upload failed (HTTP {response.status_code})", request=request, response=response
+ )
def as_build_context(self) -> BuildContext:
"""Return this object in the shape expected for a Blueprint build context.
diff --git a/src/runloop_api_client/sdk/benchmark.py b/src/runloop_api_client/sdk/benchmark.py
deleted file mode 100644
index aee49e543..000000000
--- a/src/runloop_api_client/sdk/benchmark.py
+++ /dev/null
@@ -1,167 +0,0 @@
-"""Benchmark resource class for synchronous operations."""
-
-from __future__ import annotations
-
-from typing import List
-from typing_extensions import Unpack, override
-
-from ..types import BenchmarkView
-from ._types import (
- BaseRequestOptions,
- LongRequestOptions,
- SDKBenchmarkUpdateParams,
- SDKBenchmarkListRunsParams,
- SDKBenchmarkStartRunParams,
-)
-from .._types import SequenceNotStr
-from .._client import Runloop
-from .benchmark_run import BenchmarkRun
-
-
-class Benchmark:
- """A benchmark for evaluating agent performance across scenarios.
-
- Provides methods for retrieving benchmark details, updating the benchmark,
- managing scenarios, and starting benchmark runs. Obtain instances via
- ``runloop.benchmark.from_id()`` or ``runloop.benchmark.list()``.
-
- Example:
- >>> benchmark = runloop.benchmark.from_id("bmd_xxx")
- >>> info = benchmark.get_info()
- >>> run = benchmark.start_run(run_name="evaluation-v1")
- >>> for scenario_id in info.scenario_ids:
- ... scenario = runloop.scenario.from_id(scenario_id)
- ... scenario_run = scenario.run(benchmark_run_id=run.id, run_name="evaluation-v1")
- """
-
- def __init__(self, client: Runloop, benchmark_id: str) -> None:
- """Create a Benchmark instance.
-
- :param client: Runloop client instance
- :type client: Runloop
- :param benchmark_id: Benchmark ID
- :type benchmark_id: str
- """
- self._client = client
- self._id = benchmark_id
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def id(self) -> str:
- """Return the benchmark ID.
-
- :return: Unique benchmark ID
- :rtype: str
- """
- return self._id
-
- def get_info(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> BenchmarkView:
- """Retrieve current benchmark details.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
- :return: Current benchmark info
- :rtype: BenchmarkView
- """
- return self._client.benchmarks.retrieve(
- self._id,
- **options,
- )
-
- def update(
- self,
- **params: Unpack[SDKBenchmarkUpdateParams],
- ) -> BenchmarkView:
- """Update the benchmark.
-
- Only provided fields will be updated.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkUpdateParams` for available parameters
- :return: Updated benchmark info
- :rtype: BenchmarkView
- """
- return self._client.benchmarks.update(
- self._id,
- **params,
- )
-
- def start_run(
- self,
- **params: Unpack[SDKBenchmarkStartRunParams],
- ) -> BenchmarkRun:
- """Start a new benchmark run.
-
- Creates a new benchmark run and returns a BenchmarkRun instance for
- managing the run lifecycle.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkStartRunParams` for available parameters
- :return: BenchmarkRun instance for managing the run
- :rtype: BenchmarkRun
- """
- run_view = self._client.benchmarks.start_run(
- benchmark_id=self._id,
- **params,
- )
- assert run_view.benchmark_id is not None, "benchmark_id should be set for runs created from a benchmark"
- return BenchmarkRun(self._client, run_view.id, run_view.benchmark_id)
-
- def add_scenarios(
- self,
- scenario_ids: SequenceNotStr[str],
- **options: Unpack[LongRequestOptions],
- ) -> BenchmarkView:
- """Add scenarios to the benchmark.
-
- :param scenario_ids: List of scenario IDs to add
- :type scenario_ids: SequenceNotStr[str]
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Updated benchmark info
- :rtype: BenchmarkView
- """
- return self._client.benchmarks.update_scenarios(
- self._id,
- scenarios_to_add=scenario_ids,
- **options,
- )
-
- def remove_scenarios(
- self,
- scenario_ids: SequenceNotStr[str],
- **options: Unpack[LongRequestOptions],
- ) -> BenchmarkView:
- """Remove scenarios from the benchmark.
-
- :param scenario_ids: List of scenario IDs to remove
- :type scenario_ids: SequenceNotStr[str]
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Updated benchmark info
- :rtype: BenchmarkView
- """
- return self._client.benchmarks.update_scenarios(
- self._id,
- scenarios_to_remove=scenario_ids,
- **options,
- )
-
- def list_runs(
- self,
- **params: Unpack[SDKBenchmarkListRunsParams],
- ) -> List[BenchmarkRun]:
- """List all runs for this benchmark.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkListRunsParams` for available parameters
- :return: List of benchmark runs
- :rtype: List[BenchmarkRun]
- """
- page = self._client.benchmark_runs.list(
- benchmark_id=self._id,
- **params,
- )
- return [
- BenchmarkRun(self._client, run.id, run.benchmark_id) for run in page.runs if run.benchmark_id is not None
- ]
diff --git a/src/runloop_api_client/sdk/benchmark_run.py b/src/runloop_api_client/sdk/benchmark_run.py
deleted file mode 100644
index 080e90148..000000000
--- a/src/runloop_api_client/sdk/benchmark_run.py
+++ /dev/null
@@ -1,127 +0,0 @@
-"""BenchmarkRun resource class for synchronous operations."""
-
-from __future__ import annotations
-
-from typing import List
-from typing_extensions import Unpack, override
-
-from ..types import BenchmarkRunView
-from ._types import BaseRequestOptions, LongRequestOptions, SDKBenchmarkRunListScenarioRunsParams
-from .._client import Runloop
-from .scenario_run import ScenarioRun
-
-
-class BenchmarkRun:
- """A benchmark run for evaluating agent performance across scenarios.
-
- Provides methods for monitoring run status, managing the run lifecycle,
- and accessing scenario run results. Obtain instances via
- ``benchmark.start_run()`` or ``benchmark.list_runs()``.
-
- Example:
- >>> benchmark = runloop.benchmark.from_id("bench-xxx")
- >>> run = benchmark.start_run(run_name="evaluation-v1")
- >>> info = run.get_info()
- >>> scenario_runs = run.list_scenario_runs()
- """
-
- def __init__(self, client: Runloop, run_id: str, benchmark_id: str) -> None:
- """Create a BenchmarkRun instance.
-
- :param client: Runloop client instance
- :type client: Runloop
- :param run_id: Benchmark run ID
- :type run_id: str
- :param benchmark_id: Parent benchmark ID
- :type benchmark_id: str
- """
- self._client = client
- self._id = run_id
- self._benchmark_id = benchmark_id
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def id(self) -> str:
- """Return the benchmark run ID.
-
- :return: Unique benchmark run ID
- :rtype: str
- """
- return self._id
-
- @property
- def benchmark_id(self) -> str:
- """Return the parent benchmark ID.
-
- :return: Parent benchmark ID
- :rtype: str
- """
- return self._benchmark_id
-
- def get_info(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> BenchmarkRunView:
- """Retrieve current benchmark run status and metadata.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
- :return: Current benchmark run state info
- :rtype: BenchmarkRunView
- """
- return self._client.benchmark_runs.retrieve(
- self._id,
- **options,
- )
-
- def cancel(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> BenchmarkRunView:
- """Cancel the benchmark run.
-
- Stops all running scenarios and marks the run as canceled.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Updated benchmark run state
- :rtype: BenchmarkRunView
- """
- return self._client.benchmark_runs.cancel(
- self._id,
- **options,
- )
-
- def complete(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> BenchmarkRunView:
- """Complete the benchmark run.
-
- Marks the run as completed. Call this after all scenarios have finished.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Completed benchmark run state
- :rtype: BenchmarkRunView
- """
- return self._client.benchmark_runs.complete(
- self._id,
- **options,
- )
-
- def list_scenario_runs(
- self,
- **params: Unpack[SDKBenchmarkRunListScenarioRunsParams],
- ) -> List[ScenarioRun]:
- """List all scenario runs for this benchmark run.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkRunListScenarioRunsParams` for available parameters
- :return: List of scenario run objects
- :rtype: List[ScenarioRun]
- """
- page = self._client.benchmark_runs.list_scenario_runs(
- self._id,
- **params,
- )
- return [ScenarioRun(self._client, run.id, run.devbox_id) for run in page.runs]
diff --git a/src/runloop_api_client/sdk/devbox.py b/src/runloop_api_client/sdk/devbox.py
index c97ea2682..88c529de2 100644
--- a/src/runloop_api_client/sdk/devbox.py
+++ b/src/runloop_api_client/sdk/devbox.py
@@ -11,12 +11,12 @@
DevboxView,
TunnelView,
DevboxExecutionDetailView,
- DevboxCreateSSHKeyResponse,
)
from ._types import (
LogCallback,
BaseRequestOptions,
LongRequestOptions,
+ DevboxCommandOptions,
PollingRequestOptions,
SDKDevboxExecuteParams,
ExecuteStreamingCallbacks,
@@ -39,7 +39,6 @@
from ..lib.polling import PollingConfig
from ..types.devboxes import ExecutionUpdateChunk
from .execution_result import ExecutionResult
-from ..types.devbox_execute_async_params import DevboxNiceExecuteAsyncParams
from ..types.devboxes.devbox_logs_list_view import DevboxLogsListView
from ..types.devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView
@@ -167,18 +166,15 @@ def logs(
self,
*,
execution_id: str | None = None,
- shell_name: str | None = None,
**options: Unpack[BaseRequestOptions],
) -> DevboxLogsListView:
"""Retrieve logs for the devbox.
Returns all logs from a running or completed devbox. Optionally filter
- by execution ID or shell name.
+ by execution ID.
:param execution_id: Filter logs by execution ID, defaults to None
:type execution_id: str | None, optional
- :param shell_name: Filter logs by shell name, defaults to None
- :type shell_name: str | None, optional
:param options: Optional request configuration
:return: Log entries for the devbox
:rtype: :class:`~runloop_api_client.types.devboxes.devbox_logs_list_view.DevboxLogsListView`
@@ -191,7 +187,6 @@ def logs(
return self._client.devboxes.logs.list(
self._id,
execution_id=execution_id if execution_id is not None else omit,
- shell_name=shell_name if shell_name is not None else omit,
**options,
)
@@ -375,39 +370,6 @@ def net(self) -> NetworkInterface:
"""
return NetworkInterface(self)
- def shell(self, shell_name: str | None = None) -> NamedShell:
- """Create a named shell instance for stateful command execution.
-
- Named shells are stateful and maintain environment variables and the current working
- directory (CWD) across commands, just like a real shell on your local computer.
- Commands executed through the same named shell instance will execute sequentially -
- the shell can only run one command at a time with automatic queuing. This ensures
- that environment changes and directory changes from one command are preserved for
- the next command.
-
- :param shell_name: The name of the persistent shell session. If not provided, a UUID will be generated automatically.
- :type shell_name: str | None, optional
- :return: A NamedShell instance for executing commands in the named shell
- :rtype: NamedShell
-
- Example:
- >>> # Create a named shell with a custom name
- >>> shell = devbox.shell("my-session")
- >>> # Create a named shell with an auto-generated UUID name
- >>> shell2 = devbox.shell()
- >>> # Commands execute sequentially and share state
- >>> shell.exec("cd /app")
- >>> shell.exec("export MY_VAR=value")
- >>> result = shell.exec("echo $MY_VAR") # Will output 'value'
- >>> result = shell.exec("pwd") # Will output '/app'
- """
- if shell_name is None:
- # uuid_utils is not typed
- from uuid_utils import uuid7 # type: ignore
-
- shell_name = str(uuid7())
- return NamedShell(self, shell_name)
-
# --------------------------------------------------------------------- #
# Internal helpers
# --------------------------------------------------------------------- #
@@ -534,7 +496,7 @@ def exec(
execution: DevboxAsyncExecutionDetailView = client.devboxes.execute_async(
devbox.id,
command=command,
- **filter_params(params, DevboxNiceExecuteAsyncParams),
+ **filter_params(params, DevboxCommandOptions),
**filter_params(params, LongRequestOptions),
)
streaming_group = devbox._start_streaming(
@@ -582,7 +544,7 @@ def exec_async(
execution: DevboxAsyncExecutionDetailView = client.devboxes.execute_async(
devbox.id,
command=command,
- **filter_params(params, DevboxNiceExecuteAsyncParams),
+ **filter_params(params, DevboxCommandOptions),
**filter_params(params, LongRequestOptions),
)
@@ -684,131 +646,15 @@ def upload(
)
-class NamedShell:
- """Interface for executing commands in a persistent, stateful shell session.
-
- Named shells are stateful and maintain environment variables and the current working
- directory (CWD) across commands. Commands executed through the same named shell
- instance will execute sequentially - the shell can only run one command at a time
- with automatic queuing. This ensures that environment changes and directory changes
- from one command are preserved for the next command.
-
- Use :meth:`Devbox.shell` to create a named shell instance. If you use the same shell
- name, it will re-attach to the existing named shell, preserving its state.
-
- Example:
- >>> shell = devbox.shell("my-session")
- >>> shell.exec("cd /app")
- >>> shell.exec("export MY_VAR=value")
- >>> result = shell.exec("echo $MY_VAR") # Will output 'value'
- >>> result = shell.exec("pwd") # Will output '/app'
- """
-
- def __init__(self, devbox: Devbox, shell_name: str) -> None:
- """Initialize the named shell.
-
- :param devbox: The devbox instance to execute commands on
- :type devbox: Devbox
- :param shell_name: The name of the persistent shell session
- :type shell_name: str
- """
- self._devbox = devbox
- self._shell_name = shell_name
-
- def exec(
- self,
- command: str,
- **params: Unpack[SDKDevboxExecuteParams],
- ) -> ExecutionResult:
- """Execute a command in the named shell and wait for it to complete.
-
- The command will execute in the persistent shell session, maintaining environment
- variables and the current working directory from previous commands. Commands are
- queued and execute sequentially - only one command runs at a time in the named shell.
-
- Optionally provide callbacks to stream logs in real-time. When callbacks are provided,
- this method waits for both the command to complete AND all streaming data to be
- processed before returning.
-
- :param command: The command to execute
- :type command: str
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKDevboxExecuteParams` for available parameters
- :return: Wrapper with exit status and output helpers
- :rtype: ExecutionResult
-
- Example:
- >>> shell = devbox.shell("my-session")
- >>> result = shell.exec("ls -la")
- >>> print(result.stdout())
- >>> # With streaming callbacks
- >>> result = shell.exec("npm install", stdout=lambda line: print(f"[LOG] {line}"))
- """
- # Ensure shell_name is set and cannot be overridden by user params
- params["shell_name"] = self._shell_name
- return self._devbox.cmd.exec(command, **params)
-
- def exec_async(
- self,
- command: str,
- **params: Unpack[SDKDevboxExecuteAsyncParams],
- ) -> Execution:
- """Execute a command in the named shell asynchronously without waiting for completion.
-
- The command will execute in the persistent shell session, maintaining environment
- variables and the current working directory from previous commands. Commands are
- queued and execute sequentially - only one command runs at a time in the named shell.
-
- Optionally provide callbacks to stream logs in real-time as they are produced.
- Callbacks fire in real-time as logs arrive. When you call execution.result(),
- it will wait for both the command to complete and all streaming to finish.
-
- :param command: The command to execute
- :type command: str
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKDevboxExecuteAsyncParams` for available parameters
- :return: Handle for managing the running process
- :rtype: Execution
-
- Example:
- >>> shell = devbox.shell("my-session")
- >>> execution = shell.exec_async("long-running-task.sh", stdout=lambda line: print(f"[LOG] {line}"))
- >>> # Do other work while command runs...
- >>> result = execution.result()
- >>> if result.success:
- ... print("Task completed successfully!")
- """
- # Ensure shell_name is set and cannot be overridden by user params
- params["shell_name"] = self._shell_name
- return self._devbox.cmd.exec_async(command, **params)
-
-
class NetworkInterface:
"""Interface for network operations on a devbox.
- Accessed via devbox.net property. Provides methods for SSH access and tunneling.
+ Accessed via devbox.net property. Provides methods for tunneling.
"""
def __init__(self, devbox: Devbox) -> None:
self._devbox = devbox
- def create_ssh_key(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> DevboxCreateSSHKeyResponse:
- """Create an SSH key for remote access to the devbox.
-
- :param options: Optional long-running request configuration
- :return: Response containing SSH connection info
- :rtype: :class:`~runloop_api_client.types.devbox_create_ssh_key_response.DevboxCreateSSHKeyResponse`
-
- Example:
- >>> ssh_key = devbox.net.create_ssh_key()
- >>> print(f"SSH URL: {ssh_key.url}")
- """
- return self._devbox._client.devboxes.create_ssh_key(
- self._devbox.id,
- **options,
- )
-
def enable_tunnel(
self,
**params: Unpack[SDKDevboxEnableTunnelParams],
diff --git a/src/runloop_api_client/sdk/execution.py b/src/runloop_api_client/sdk/execution.py
index b37952df4..180ef44df 100644
--- a/src/runloop_api_client/sdk/execution.py
+++ b/src/runloop_api_client/sdk/execution.py
@@ -9,6 +9,7 @@
from ._types import BaseRequestOptions, LongRequestOptions
from .._client import Runloop
+from .._exceptions import RunloopError
from .execution_result import ExecutionResult
from ..types.devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView
@@ -120,6 +121,38 @@ def get_state(self, **options: Unpack[BaseRequestOptions]) -> DevboxAsyncExecuti
**options,
)
+ def send_std_in(self, text: str, **options: Unpack[LongRequestOptions]) -> None:
+ """Send nonempty text to an execution started with ``attach_stdin=True``.
+
+ Complete each send before the next send or ``close_std_in``. Concurrent
+ calls are not ordered. Client retries can replay input after an ambiguous
+ connection failure; delivery is not guaranteed to be exactly once.
+
+ :param text: Text to write to stdin
+ :param options: Request configuration forwarded to the generated client
+ :raises RunloopError: If the API reports unsuccessful delivery
+ """
+ response = self._client.devboxes.executions.send_std_in(
+ self._execution_id, devbox_id=self._devbox_id, text=text, **options
+ )
+ if not response.success:
+ raise RunloopError(f"Failed to send stdin to execution {self._execution_id}")
+
+ def close_std_in(self, **options: Unpack[LongRequestOptions]) -> None:
+ """Send EOF after all input has been delivered so the execution can finish.
+
+ Requires ``attach_stdin=True``. Wait for pending sends before closing.
+ This handle does not serialize calls or guarantee exactly-once delivery.
+
+ :param options: Request configuration forwarded to the generated client
+ :raises RunloopError: If the API reports unsuccessful delivery
+ """
+ response = self._client.devboxes.executions.send_std_in(
+ self._execution_id, devbox_id=self._devbox_id, signal="EOF", **options
+ )
+ if not response.success:
+ raise RunloopError(f"Failed to send stdin to execution {self._execution_id}")
+
def kill(self, **options: Unpack[LongRequestOptions]) -> None:
"""Request termination of the running execution.
diff --git a/src/runloop_api_client/sdk/scenario.py b/src/runloop_api_client/sdk/scenario.py
deleted file mode 100644
index 346118b91..000000000
--- a/src/runloop_api_client/sdk/scenario.py
+++ /dev/null
@@ -1,118 +0,0 @@
-"""Scenario resource class for synchronous operations."""
-
-from __future__ import annotations
-
-from typing_extensions import Unpack, override
-
-from ..types import ScenarioView
-from ._types import BaseRequestOptions, SDKScenarioRunParams, SDKScenarioUpdateParams, SDKScenarioRunAsyncParams
-from .._client import Runloop
-from .scenario_run import ScenarioRun
-
-
-class Scenario:
- """A scenario for evaluating agent performance.
-
- Provides methods for retrieving scenario details, updating the scenario,
- and starting scenario runs. Obtain instances via ``runloop.scenario.from_id()``
- or ``runloop.scenario.list()``.
-
- Example:
- >>> scenario = runloop.scenario.from_id("scn-xxx")
- >>> info = scenario.get_info()
- >>> run = scenario.run(run_name="test-run")
- """
-
- def __init__(self, client: Runloop, scenario_id: str) -> None:
- """Create a Scenario instance.
-
- :param client: Runloop client instance
- :type client: Runloop
- :param scenario_id: Scenario ID
- :type scenario_id: str
- """
- self._client = client
- self._id = scenario_id
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def id(self) -> str:
- """Return the scenario ID.
-
- :return: Unique scenario ID
- :rtype: str
- """
- return self._id
-
- def get_info(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> ScenarioView:
- """Retrieve current scenario details.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
- :return: Current scenario info
- :rtype: ScenarioView
- """
- return self._client.scenarios.retrieve(
- self._id,
- **options,
- )
-
- def update(
- self,
- **params: Unpack[SDKScenarioUpdateParams],
- ) -> ScenarioView:
- """Update the scenario.
-
- Only provided fields will be updated.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioUpdateParams` for available parameters
- :return: Updated scenario info
- :rtype: ScenarioView
- """
- return self._client.scenarios.update(
- self._id,
- **params,
- )
-
- def run_async(
- self,
- **params: Unpack[SDKScenarioRunAsyncParams],
- ) -> ScenarioRun:
- """Start a new scenario run without waiting for the devbox.
-
- Creates a new scenario run and returns immediately. The devbox may still
- be starting; call ``await_env_ready()`` on the returned ScenarioRun to
- wait for it to be ready.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioRunAsyncParams` for available parameters
- :return: ScenarioRun instance for managing the run
- :rtype: ScenarioRun
- """
- run_view = self._client.scenarios.start_run(
- scenario_id=self._id,
- **params,
- )
- return ScenarioRun(self._client, run_view.id, run_view.devbox_id)
-
- def run(
- self,
- **params: Unpack[SDKScenarioRunParams],
- ) -> ScenarioRun:
- """Start a new scenario run and wait for the devbox to be ready.
-
- Convenience method that starts a run and waits for the devbox to be ready.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioRunParams` for available parameters
- :return: ScenarioRun instance with ready devbox
- :rtype: ScenarioRun
- """
- run_view = self._client.scenarios.start_run_and_await_env_ready(
- scenario_id=self._id,
- **params,
- )
- return ScenarioRun(self._client, run_view.id, run_view.devbox_id)
diff --git a/src/runloop_api_client/sdk/scenario_builder.py b/src/runloop_api_client/sdk/scenario_builder.py
deleted file mode 100644
index e2fc15de4..000000000
--- a/src/runloop_api_client/sdk/scenario_builder.py
+++ /dev/null
@@ -1,480 +0,0 @@
-"""ScenarioBuilder for constructing scenarios with a fluent API."""
-
-from __future__ import annotations
-
-from typing import Dict, List, Iterable, Optional
-from typing_extensions import Self, Unpack, Literal, override
-
-from ..types import ScenarioCreateParams, ScenarioEnvironmentParam
-from ._types import ScenarioPreview, LongRequestOptions
-from .._client import Runloop
-from .scenario import Scenario
-from .snapshot import Snapshot
-from .blueprint import Blueprint
-from ..types.scoring_function_param import (
- Scorer,
- ScoringFunctionParam,
- ScorerCustomScoringFunction,
- ScorerAstGrepScoringFunction,
- ScorerCommandScoringFunction,
- ScorerTestBasedScoringFunction,
- ScorerBashScriptScoringFunction,
- ScorerPythonScriptScoringFunction,
- ScorerTestBasedScoringFunctionTestFile,
-)
-
-
-class ScenarioBuilder:
- """Builder for constructing scenarios with a fluent API.
-
- Provides a step-by-step interface for configuring all aspects of a scenario
- before pushing it to the platform.
-
- Example:
- >>> builder = (
- ... runloop.scenario.builder("my-scenario")
- ... .from_blueprint(blueprint)
- ... .with_working_directory("/app")
- ... .with_problem_statement("Fix the bug in main.py")
- ... .add_test_command_scorer("tests", test_command="pytest")
- ... )
- >>> params = builder.build()
- >>> scenario = runloop.scenario.create(**params) # equivalent to builder.push()
- """
-
- def __init__(self, name: str, client: Runloop) -> None:
- """Initialize the builder.
-
- :param name: Name for the scenario
- :type name: str
- :param client: Runloop client instance
- :type client: Runloop
- """
- self._client = client
- self._name = name
-
- # Environment configuration
- self._blueprint: Optional[Blueprint] = None
- self._snapshot: Optional[Snapshot] = None
- self._working_directory: Optional[str] = None
-
- # Input context
- self._problem_statement: Optional[str] = None
- self._additional_context: Optional[object] = None
-
- # Scoring
- self._scorers: List[ScoringFunctionParam] = []
-
- # Metadata and other options
- self._metadata: Dict[str, str] = {}
- self._reference_output: Optional[str] = None
- self._required_env_vars: Optional[List[str]] = None
- self._required_secrets: Optional[List[str]] = None
- self._validation_type: Optional[Literal["UNSPECIFIED", "FORWARD", "REVERSE", "EVALUATION"]] = None
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def name(self) -> str:
- """Return the scenario name.
-
- :return: Scenario name
- :rtype: str
- """
- return self._name
-
- def from_blueprint(self, blueprint: Blueprint) -> Self:
- """Set a blueprint to define the baseline environment for the scenario.
-
- :param blueprint: Blueprint to use
- :type blueprint: Blueprint
- :return: Self for method chaining
- :rtype: Self
- """
- self._blueprint = blueprint
- self._snapshot = None # Clear snapshot if blueprint is set
- return self
-
- def from_snapshot(self, snapshot: Snapshot) -> Self:
- """Set a snapshot to define the baseline environment for the scenario.
-
- :param snapshot: Snapshot to use
- :type snapshot: Snapshot
- :return: Self for method chaining
- :rtype: Self
- """
- self._snapshot = snapshot
- self._blueprint = None # Clear blueprint if snapshot is set
- return self
-
- def with_working_directory(self, directory: str) -> Self:
- """Set the working directory for the scenario.
-
- :param directory: Working directory path
- :type directory: str
- :return: Self for method chaining
- :rtype: Self
- """
- self._working_directory = directory
- return self
-
- def with_problem_statement(self, statement: str) -> Self:
- """Set the problem statement for the scenario; this will be provided as input context to the agent.
-
- :param statement: Problem statement text
- :type statement: str
- :return: Self for method chaining
- :rtype: Self
- """
- self._problem_statement = statement
- return self
-
- def with_additional_context(self, context: object) -> Self:
- """Set additional structured context for the scenario.
- This can be used to provide additional information to the agent, such as hints, examples, or other relevant information.
-
- :param context: Additional context (JSON-serializable)
- :type context: object
- :return: Self for method chaining
- :rtype: Self
- """
- self._additional_context = context
- return self
-
- def _add_scorer(self, name: str, weight: float, scorer: Scorer) -> Self:
- """Internal helper to add a scorer to the list.
-
- :raises ValueError: If weight is not positive
- """
- if weight <= 0:
- raise ValueError(f"Scorer weight must be positive, got {weight}")
- self._scorers.append({"name": name, "weight": weight, "scorer": scorer})
- return self
-
- def add_test_command_scorer(
- self,
- name: str,
- *,
- test_command: str,
- weight: float = 1.0,
- test_files: Optional[Iterable[ScorerTestBasedScoringFunctionTestFile]] = None,
- ) -> Self:
- """Add a test-based scorer that runs a test command.
-
- :param name: Name of the scoring function
- :type name: str
- :param test_command: Command to run tests (e.g., "pytest")
- :type test_command: str
- :param weight: Weight for this scorer (normalized automatically)
- :type weight: float
- :param test_files: Optional test files to create before running
- :type test_files: Optional[Iterable[ScorerTestBasedScoringFunctionTestFile]]
- :return: Self for method chaining
- :rtype: Self
- """
- scorer: ScorerTestBasedScoringFunction = {
- "type": "test_based_scorer",
- "test_command": test_command,
- }
- if test_files:
- scorer["test_files"] = test_files
- return self._add_scorer(name, weight, scorer)
-
- def add_shell_command_scorer(
- self,
- name: str,
- *,
- command: str,
- weight: float = 1.0,
- ) -> Self:
- """Add a command scorer that runs a shell command.
-
- :param name: Name of the scoring function
- :type name: str
- :param command: Shell command to execute
- :type command: str
- :param weight: Weight for this scorer (normalized automatically)
- :type weight: float
- :return: Self for method chaining
- :rtype: Self
- """
- scorer: ScorerCommandScoringFunction = {
- "type": "command_scorer",
- "command": command,
- }
- return self._add_scorer(name, weight, scorer)
-
- def add_bash_script_scorer(
- self,
- name: str,
- *,
- bash_script: str,
- weight: float = 1.0,
- ) -> Self:
- """Add a standalone bash script scorer.
-
- The script should output "score=X.X" where X.X is a float between 0.0 and 1.0, inclusive.
-
- :param name: Name of the scoring function
- :type name: str
- :param bash_script: Bash script content
- :type bash_script: str
- :param weight: Weight for this scorer (normalized automatically)
- :type weight: float
- :return: Self for method chaining
- :rtype: Self
- """
- scorer: ScorerBashScriptScoringFunction = {
- "type": "bash_script_scorer",
- "bash_script": bash_script,
- }
- return self._add_scorer(name, weight, scorer)
-
- def add_python_script_scorer(
- self,
- name: str,
- *,
- python_script: str,
- weight: float = 1.0,
- python_version_constraint: Optional[str] = None,
- requirements_contents: Optional[str] = None,
- ) -> Self:
- """Add a standalone Python script scorer.
-
- The script is run in an isolated uv environment, and the dependencies are declared in the
- `uv script header `__.
-
- The script should print the score in the range [0.0, 1.0] to stdout.
-
- :param name: Name of the scoring function
- :type name: str
- :param python_script: Python script content
- :type python_script: str
- :param weight: Weight for this scorer (normalized automatically)
- :type weight: float
- :param python_version_constraint: Python version (default "==3.12.10")
- :type python_version_constraint: Optional[str]
- :param requirements_contents: pip requirements.txt content
- :type requirements_contents: Optional[str]
- :return: Self for method chaining
- :rtype: Self
- """
- scorer: ScorerPythonScriptScoringFunction = {
- "type": "python_script_scorer",
- "python_script": python_script,
- }
- if python_version_constraint:
- scorer["python_version_constraint"] = python_version_constraint
- if requirements_contents:
- scorer["requirements_contents"] = requirements_contents
- return self._add_scorer(name, weight, scorer)
-
- def add_ast_grep_scorer(
- self,
- name: str,
- *,
- pattern: str,
- weight: float = 1.0,
- search_directory: str = ".",
- lang: Optional[str] = None,
- ) -> Self:
- """Add an AST grep scorer that matches code patterns.
-
- :param name: Name of the scoring function
- :type name: str
- :param pattern: AST pattern to match
- :type pattern: str
- :param weight: Weight for this scorer (normalized automatically)
- :type weight: float
- :param search_directory: Directory to search (default ".")
- :type search_directory: str
- :param lang: Language of the pattern (optional)
- :type lang: Optional[str]
- :return: Self for method chaining
- :rtype: Self
- """
- scorer: ScorerAstGrepScoringFunction = {
- "type": "ast_grep_scorer",
- "pattern": pattern,
- "search_directory": search_directory,
- }
- if lang:
- scorer["lang"] = lang
- return self._add_scorer(name, weight, scorer)
-
- def add_custom_scorer(
- self,
- name: str,
- *,
- custom_scorer_type: str,
- weight: float = 1.0,
- scorer_params: Optional[object] = None,
- ) -> Self:
- """Add a custom scorer registered with Runloop.
-
- :param name: Name of the scoring function
- :type name: str
- :param custom_scorer_type: Type identifier registered with Runloop
- :type custom_scorer_type: str
- :param weight: Weight for this scorer (normalized automatically)
- :type weight: float
- :param scorer_params: Additional JSON parameters for the scorer
- :type scorer_params: Optional[object]
- :return: Self for method chaining
- :rtype: Self
- """
- scorer: ScorerCustomScoringFunction = {
- "type": "custom_scorer",
- "custom_scorer_type": custom_scorer_type,
- }
- if scorer_params:
- scorer["scorer_params"] = scorer_params
- return self._add_scorer(name, weight, scorer)
-
- def with_metadata(self, metadata: Dict[str, str]) -> Self:
- """Set metadata for the scenario.
-
- :param metadata: Key-value metadata
- :type metadata: Dict[str, str]
- :return: Self for method chaining
- :rtype: Self
- """
- self._metadata = metadata
- return self
-
- def with_reference_output(self, output: str) -> Self:
- """Set the reference solution or gold patch for validation.
- After application, the scorer is expected to return a score of 1.0.
-
- :param output: Reference solution or gold patch (e.g., git diff)
- :type output: str
- :return: Self for method chaining
- :rtype: Self
- """
- self._reference_output = output
- return self
-
- def with_required_env_vars(self, env_vars: List[str]) -> Self:
- """Set required environment variables.
-
- :param env_vars: List of required environment variable names
- :type env_vars: List[str]
- :return: Self for method chaining
- :rtype: Self
- """
- self._required_env_vars = env_vars
- return self
-
- def with_required_secrets(self, secrets: List[str]) -> Self:
- """Set required secrets.
-
- :param secrets: List of required secret names
- :type secrets: List[str]
- :return: Self for method chaining
- :rtype: Self
- """
- self._required_secrets = secrets
- return self
-
- def with_validation_type(self, validation_type: Literal["UNSPECIFIED", "FORWARD", "REVERSE", "EVALUATION"]) -> Self:
- """Set the validation strategy to specify how the reference solution or gold patch is applied to the scenario.
-
- :param validation_type: Validation type
- :type validation_type: Literal["UNSPECIFIED", "FORWARD", "REVERSE", "EVALUATION"]
- :return: Self for method chaining
- :rtype: Self
- """
- self._validation_type = validation_type
- return self
-
- def _build_normalized_scorers(self) -> List[ScoringFunctionParam]:
- """Build normalized scorers list."""
- total_weight = sum(s["weight"] for s in self._scorers)
- return [{**s, "weight": s["weight"] / total_weight} for s in self._scorers]
-
- def _build_environment_params(self) -> Optional[ScenarioEnvironmentParam]:
- """Build environment parameters"""
- if not self._blueprint and not self._snapshot and not self._working_directory:
- return None
- return {
- "blueprint_id": self._blueprint.id if self._blueprint else None,
- "snapshot_id": self._snapshot.id if self._snapshot else None,
- "working_directory": self._working_directory if self._working_directory else None,
- }
-
- def build(self) -> ScenarioCreateParams:
- """Build the scenario creation parameters.
-
- Weights are automatically normalized to sum to 1.0.
-
- :raises ValueError: If required fields are missing
- :return: Parameters for scenario creation
- :rtype: ScenarioCreateParams
- """
- if not self._problem_statement:
- raise ValueError("Problem statement is required. Call with_problem_statement() first.")
-
- if not self._scorers:
- raise ValueError(
- "At least one scorer is required. "
- "Call add_test_command_scorer(), add_bash_script_scorer(), or another scorer method first."
- )
-
- return {
- "name": self._name,
- "input_context": {
- "problem_statement": self._problem_statement,
- "additional_context": self._additional_context,
- },
- "scoring_contract": {
- "scoring_function_parameters": self._build_normalized_scorers(),
- },
- "environment_parameters": self._build_environment_params(),
- "metadata": self._metadata,
- "reference_output": self._reference_output,
- "required_environment_variables": self._required_env_vars,
- "required_secret_names": self._required_secrets,
- "validation_type": self._validation_type,
- }
-
- def preview(self) -> ScenarioPreview:
- """Preview the scenario configuration without pushing to the platform.
-
- Returns the current configuration state as a ScenarioPreview object.
- Does not validate or raise errors for missing required fields.
-
- :return: Preview of the scenario configuration
- :rtype: ScenarioPreview
- """
- return ScenarioPreview.model_validate(
- {
- "name": self._name,
- "input_context": {
- "problem_statement": self._problem_statement,
- "additional_context": self._additional_context,
- },
- "scoring_contract": {
- "scoring_function_parameters": self._build_normalized_scorers(),
- },
- "environment": self._build_environment_params(),
- "metadata": self._metadata,
- "reference_output": self._reference_output,
- "required_environment_variables": self._required_env_vars,
- "required_secret_names": self._required_secrets,
- "validation_type": self._validation_type,
- }
- )
-
- def push(self, **options: Unpack[LongRequestOptions]) -> Scenario:
- """Create the scenario on the platform.
-
- :param options: Optional long-running request configuration
- :raises ValueError: If required fields are missing
- :return: Created scenario wrapper
- :rtype: Scenario
- """
- params = self.build()
- scenario_view = self._client.scenarios.create(**params, **options)
- return Scenario(self._client, scenario_view.id)
diff --git a/src/runloop_api_client/sdk/scenario_run.py b/src/runloop_api_client/sdk/scenario_run.py
deleted file mode 100644
index ede44b105..000000000
--- a/src/runloop_api_client/sdk/scenario_run.py
+++ /dev/null
@@ -1,242 +0,0 @@
-"""ScenarioRun resource class for synchronous operations."""
-
-from __future__ import annotations
-
-import os
-from typing import Union, Optional
-from functools import cached_property
-from typing_extensions import Unpack, override
-
-from ..types import ScenarioRunView
-from ._types import BaseRequestOptions, LongRequestOptions, PollingRequestOptions
-from .devbox import Devbox
-from .._client import Runloop
-from ._helpers import filter_params
-from ..types.scoring_contract_result_view import ScoringContractResultView
-
-
-class ScenarioRun:
- """A running scenario with devbox access.
-
- Provides methods for managing the scenario run lifecycle, accessing
- the devbox, and retrieving scoring results. Obtain instances via
- ``scenario.run()`` or ``scenario.run_async()``.
-
- Example:
- >>> scenario = runloop.scenario.from_id("scn-xxx")
- >>> run = scenario.run_async()
- >>> run.await_env_ready()
- >>> devbox = run.devbox
- >>> # ... agent does work on the devbox ...
- >>> run.score_and_await()
- >>> score = run.get_score()
- """
-
- def __init__(self, client: Runloop, run_id: str, devbox_id: str) -> None:
- """Create a ScenarioRun instance.
-
- :param client: Runloop client instance
- :type client: Runloop
- :param run_id: Scenario run ID
- :type run_id: str
- :param devbox_id: Devbox ID associated with this run
- :type devbox_id: str
- """
- self._client = client
- self._id = run_id
- self._devbox_id = devbox_id
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def id(self) -> str:
- """Return the scenario run ID.
-
- :return: Unique scenario run ID
- :rtype: str
- """
- return self._id
-
- @property
- def devbox_id(self) -> str:
- """Return the devbox ID associated with this run.
-
- :return: Devbox ID
- :rtype: str
- """
- return self._devbox_id
-
- @cached_property
- def devbox(self) -> Devbox:
- """The devbox instance for this scenario run.
-
- Use this to interact with the devbox environment during the scenario run.
-
- :return: Devbox instance
- :rtype: Devbox
- """
- return Devbox(self._client, self._devbox_id)
-
- def get_info(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> ScenarioRunView:
- """Retrieve current scenario run status and metadata.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
- :return: Current scenario run state info
- :rtype: ScenarioRunView
- """
- return self._client.scenarios.runs.retrieve(
- self._id,
- **options,
- )
-
- def await_env_ready(
- self,
- **options: Unpack[PollingRequestOptions],
- ) -> ScenarioRunView:
- """Wait for the scenario environment (devbox) to be ready.
-
- Blocks until the devbox reaches running state.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options
- :return: Scenario run state after environment is ready
- :rtype: ScenarioRunView
- """
- self._client.devboxes.await_running(self._devbox_id, polling_config=options.get("polling_config"))
- return self.get_info(**filter_params(options, BaseRequestOptions))
-
- def score(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> ScenarioRunView:
- """Submit the scenario run for scoring.
-
- This triggers the scoring process using the scenario's scoring contract.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Updated scenario run state
- :rtype: ScenarioRunView
- """
- return self._client.scenarios.runs.score(
- self._id,
- **options,
- )
-
- def await_scored(
- self,
- **options: Unpack[PollingRequestOptions],
- ) -> ScenarioRunView:
- """Wait for the scenario run to be scored.
-
- Blocks until scoring is complete.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options
- :return: Scored scenario run state
- :rtype: ScenarioRunView
- """
- return self._client.scenarios.runs.await_scored(
- self._id,
- **options,
- )
-
- def score_and_await(
- self,
- **options: Unpack[PollingRequestOptions],
- ) -> ScenarioRunView:
- """Submit for scoring and wait for completion.
-
- Convenience method that calls score() then await_scored().
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options
- :return: Scored scenario run state
- :rtype: ScenarioRunView
- """
- return self._client.scenarios.runs.score_and_await(
- self._id,
- **options,
- )
-
- def score_and_complete(
- self,
- **options: Unpack[PollingRequestOptions],
- ) -> ScenarioRunView:
- """Score the run, wait for scoring, then complete and shutdown.
-
- Convenience method that scores the scenario run, waits for scoring to
- finish, then completes the run and shuts down the devbox.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options
- :return: Completed scenario run state with scoring results
- :rtype: ScenarioRunView
- """
- return self._client.scenarios.runs.score_and_complete(
- self._id,
- **options,
- )
-
- def complete(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> ScenarioRunView:
- """Complete the scenario run and shutdown the devbox.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Final scenario run state
- :rtype: ScenarioRunView
- """
- return self._client.scenarios.runs.complete(
- self._id,
- **options,
- )
-
- def cancel(
- self,
- **options: Unpack[LongRequestOptions],
- ) -> ScenarioRunView:
- """Cancel the scenario run and shutdown the devbox.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- :return: Cancelled scenario run state
- :rtype: ScenarioRunView
- """
- return self._client.scenarios.runs.cancel(
- self._id,
- **options,
- )
-
- def download_logs(
- self,
- file: Union[str, os.PathLike[str]],
- **options: Unpack[LongRequestOptions],
- ) -> None:
- """Download all logs for this scenario run to a zip file.
-
- Downloads a zip archive containing all logs from the scenario run's
- associated devbox.
-
- :param file: Path where the zip file will be written
- :type file: str | os.PathLike[str]
- :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
- """
- response = self._client.scenarios.runs.download_logs(self._id, **options)
- response.write_to_file(file)
-
- def get_score(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> Optional[ScoringContractResultView]:
- """Get the scoring result for this run.
-
- Returns None if the run has not been scored yet. Always makes an API
- call to retrieve the current scoring result.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
- :return: Scoring result or None if not yet scored
- :rtype: Optional[ScoringContractResultView]
- """
- info = self.get_info(**options)
- return info.scoring_contract_result
diff --git a/src/runloop_api_client/sdk/scorer.py b/src/runloop_api_client/sdk/scorer.py
deleted file mode 100644
index ccc575fa7..000000000
--- a/src/runloop_api_client/sdk/scorer.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""Scorer resource class for synchronous operations."""
-
-from __future__ import annotations
-
-from typing_extensions import Unpack, override
-
-from ._types import (
- BaseRequestOptions,
- SDKScorerUpdateParams,
-)
-from .._client import Runloop
-from ..types.scenarios import ScorerUpdateResponse, ScorerRetrieveResponse
-
-
-class Scorer:
- """A custom scorer for evaluating scenario outputs.
-
- Scorers define bash scripts that produce a score in the range [0.0, 1.0] for scenario runs.
- Obtain instances via ``runloop.scorer.create()`` or ``runloop.scorer.from_id()``.
-
- Example:
- >>> runloop = RunloopSDK()
- >>> scorer = runloop.scorer.create(type="my_scorer", bash_script="echo 'score=1.0'")
- """
-
- def __init__(self, client: Runloop, scorer_id: str) -> None:
- """Create a Scorer instance.
-
- :param client: Runloop client instance
- :type client: Runloop
- :param scorer_id: ID of the scorer
- :type scorer_id: str
- """
- self._client = client
- self._id = scorer_id
-
- @override
- def __repr__(self) -> str:
- return f""
-
- @property
- def id(self) -> str:
- """The scorer's unique identifier.
-
- :return: Scorer ID
- :rtype: str
- """
- return self._id
-
- def get_info(self, **options: Unpack[BaseRequestOptions]) -> ScorerRetrieveResponse:
- """Fetch current scorer details from the API.
-
- :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
- :return: Current scorer details
- :rtype: ScorerRetrieveResponse
- """
- return self._client.scenarios.scorers.retrieve(self._id, **options)
-
- def update(self, **params: Unpack[SDKScorerUpdateParams]) -> ScorerUpdateResponse:
- """Update the scorer's type or bash script.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerUpdateParams` for available parameters
- :return: Updated scorer details
- :rtype: ScorerUpdateResponse
- """
- return self._client.scenarios.scorers.update(self._id, **params)
diff --git a/src/runloop_api_client/sdk/secret.py b/src/runloop_api_client/sdk/secret.py
index 8fdfa8c0d..e414bd389 100644
--- a/src/runloop_api_client/sdk/secret.py
+++ b/src/runloop_api_client/sdk/secret.py
@@ -13,8 +13,10 @@ class Secret:
"""Synchronous wrapper around a secret resource.
Secrets are encrypted key-value pairs that can be securely stored and injected
- into Devboxes as environment variables. Secrets are identified by their globally
- unique name.
+ into Devboxes as environment variables. This wrapper always operates by its account-scoped
+ name, even if it contains an ID from an earlier response. Reads and updates
+ select the greatest ID at lookup; deletion removes all selected matches,
+ nonatomically. Use ``sdk.secret.from_id(id)`` for exact row operations.
Example:
>>> runloop = RunloopSDK()
@@ -36,9 +38,9 @@ def __init__(
:param client: Generated Runloop client
:type client: Runloop
- :param name: The globally unique name of the secret
+ :param name: The literal account-scoped name of the secret
:type name: str
- :param id: The secret ID (optional, may not be known until getInfo is called)
+ :param id: An optional ID captured from the response that constructed this wrapper
:type id: str | None
"""
self._client = client
@@ -53,7 +55,7 @@ def __repr__(self) -> str:
def id(self) -> str | None:
"""Return the secret ID.
- :return: Secret ID, or None if not yet fetched from API
+ :return: Captured response ID, or None. Fetching metadata does not refresh this property.
:rtype: str | None
"""
return self._id
@@ -62,7 +64,7 @@ def id(self) -> str | None:
def name(self) -> str:
"""Return the secret name.
- :return: Globally unique secret name
+ :return: Literal account-scoped secret name
:rtype: str
"""
return self._name
@@ -93,7 +95,9 @@ def update(
value: str,
**options: Unpack[LongRequestOptions],
) -> SecretView:
- """Update this secret's value.
+ """Update the greatest-ID row matching this name at lookup.
+
+ This wrapper remains name-bound and its captured ID is unchanged.
Example:
>>> updated = secret.update("new-secret-value")
@@ -115,7 +119,9 @@ def delete(
self,
**options: Unpack[LongRequestOptions],
) -> SecretView:
- """Delete this secret. This action is irreversible.
+ """Delete every selected row matching this name, nonatomically.
+
+ A concurrent create can survive. This action is irreversible.
Example:
>>> secret.delete()
diff --git a/src/runloop_api_client/sdk/secret_by_id.py b/src/runloop_api_client/sdk/secret_by_id.py
new file mode 100644
index 000000000..163866b0e
--- /dev/null
+++ b/src/runloop_api_client/sdk/secret_by_id.py
@@ -0,0 +1,44 @@
+"""Exact-ID Secret operations, separate from legacy name selectors."""
+
+from __future__ import annotations
+
+from typing_extensions import Unpack, override
+
+from ._types import BaseRequestOptions, LongRequestOptions
+from .._client import Runloop
+from ..types.secret_view import SecretView
+
+
+class SecretById:
+ """An exact Secret row, obtained with ``sdk.secret.from_id(id)``.
+
+ Construction makes no request. The ID never changes, but another writer can
+ change the value. Requests retain the generated client's retry policy and are
+ not exactly-once operations. Use ``get_info()`` for the name and metadata;
+ values are never returned.
+ """
+
+ def __init__(self, client: Runloop, id: str) -> None:
+ self._client = client
+ self._id = id
+
+ @override
+ def __repr__(self) -> str:
+ return f""
+
+ @property
+ def id(self) -> str:
+ """The exact row ID, not a name or an immutable value-version identifier."""
+ return self._id
+
+ def get_info(self, **options: Unpack[BaseRequestOptions]) -> SecretView:
+ """Retrieve this row's metadata. Missing IDs never fall back to names."""
+ return self._client.secrets.retrieve_by_id(self._id, **options)
+
+ def update(self, value: str, **options: Unpack[LongRequestOptions]) -> SecretView:
+ """Replace this row's value, without changing running devbox environments or issued tokens."""
+ return self._client.secrets.update_by_id(self._id, value=value, **options)
+
+ def delete(self, **options: Unpack[LongRequestOptions]) -> SecretView:
+ """Delete only this row. Name readers may then see an older same-name row."""
+ return self._client.secrets.delete_by_id(self._id, **options)
diff --git a/src/runloop_api_client/sdk/storage_object.py b/src/runloop_api_client/sdk/storage_object.py
index 859fb3a63..d7a1bca73 100644
--- a/src/runloop_api_client/sdk/storage_object.py
+++ b/src/runloop_api_client/sdk/storage_object.py
@@ -2,9 +2,11 @@
from __future__ import annotations
-from typing import Iterable
+from typing import Mapping, Iterable
from typing_extensions import Unpack, override
+import httpx
+
from ._types import BaseRequestOptions, LongRequestOptions, SDKObjectDownloadParams
from .._client import Runloop
from ..types.object_view import ObjectView
@@ -15,7 +17,13 @@
class StorageObject:
"""Wrapper around storage object operations, including uploads and downloads."""
- def __init__(self, client: Runloop, object_id: str, upload_url: str | None) -> None:
+ def __init__(
+ self,
+ client: Runloop,
+ object_id: str,
+ upload_url: str | None,
+ upload_headers: Mapping[str, str] | None = None,
+ ) -> None:
"""Initialize the wrapper.
:param client: Generated Runloop client
@@ -24,10 +32,12 @@ def __init__(self, client: Runloop, object_id: str, upload_url: str | None) -> N
:type object_id: str
:param upload_url: Pre-signed upload URL, if the object is in draft state, defaults to None
:type upload_url: str | None, optional
+ :param upload_headers: Required signed-upload headers returned with the URL; copied on construction
"""
self._client = client
self._id = object_id
self._upload_url = upload_url
+ self._upload_headers = dict(upload_headers or {})
@override
def __repr__(self) -> str:
@@ -81,6 +91,7 @@ def complete(
**options,
)
self._upload_url = None
+ self._upload_headers = {}
return result
def get_download_url(
@@ -151,16 +162,30 @@ def delete(
def upload_content(self, content: str | bytes | Iterable[bytes]) -> None:
"""Upload content to the object's pre-signed URL.
+ The URL and required headers are sensitive upload credentials. Requests use
+ the configured HTTP transport, but not its default headers, cookies, or auth.
+ Custom transport implementations and event hooks must preserve this isolation.
+ Uploads are not retried and redirects are not followed.
+
:param content: Bytes payload, text payload, or an iterable streaming bytes
:type content: str | bytes | Iterable[bytes]
:return: None
:rtype: None
:raises RuntimeError: If no upload URL is available
- :raises httpx.HTTPStatusError: Propagated from the underlying ``httpx`` client when the upload fails
+ :raises httpx.HTTPStatusError: If storage rejects the upload (message excludes signed credentials)
+ :raises httpx.RequestError: If the upload cannot be sent
"""
url = self._ensure_upload_url()
- response = self._client._client.put(url, content=content)
- response.raise_for_status()
+ # Construct directly so HTTPX does not merge API-client defaults into storage requests.
+ request = httpx.Request("PUT", url, content=content, headers=self._upload_headers)
+ try:
+ response = self._client._client.send(request, auth=None, follow_redirects=False)
+ except httpx.RequestError:
+ raise httpx.RequestError("Storage upload failed during transport", request=request) from None
+ if not response.is_success:
+ raise httpx.HTTPStatusError(
+ f"Storage upload failed (HTTP {response.status_code})", request=request, response=response
+ )
def as_build_context(self) -> BuildContext:
"""Return this object in the shape expected for a Blueprint build context.
diff --git a/src/runloop_api_client/sdk/sync.py b/src/runloop_api_client/sdk/sync.py
index ce10d1457..c3265cbca 100644
--- a/src/runloop_api_client/sdk/sync.py
+++ b/src/runloop_api_client/sdk/sync.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Dict, List, Mapping, Optional
+from typing import Dict, Mapping, Optional
from pathlib import Path
from datetime import timedelta
from typing_extensions import Unpack
@@ -10,26 +10,17 @@
import httpx
from .axon import Axon
-from .agent import Agent
from ._types import (
SDKAxonListParams,
BaseRequestOptions,
LongRequestOptions,
- SDKAgentListParams,
SDKAxonCreateParams,
SDKDevboxListParams,
SDKObjectListParams,
- SDKScorerListParams,
- SDKAgentCreateParams,
SDKDevboxCreateParams,
SDKObjectCreateParams,
- SDKScenarioListParams,
- SDKScorerCreateParams,
- SDKBenchmarkListParams,
SDKBlueprintListParams,
SDKMcpConfigListParams,
- SDKAgentListPublicParams,
- SDKBenchmarkCreateParams,
SDKBlueprintCreateParams,
SDKMcpConfigCreateParams,
SDKDiskSnapshotListParams,
@@ -40,26 +31,21 @@
SDKDevboxCreateFromImageParams,
)
from .devbox import Devbox
-from .scorer import Scorer
from .secret import Secret
from .._types import Timeout, NotGiven, not_given
from .._client import DEFAULT_MAX_RETRIES, Runloop
from ._helpers import detect_content_type
-from .scenario import Scenario
from .snapshot import Snapshot
-from .benchmark import Benchmark
from .blueprint import Blueprint
from .mcp_config import McpConfig
from .._constants import DEFAULT_API_POOL_SHARDS, DEFAULT_TRANSFER_POOL_SHARDS, DEFAULT_BACKGROUND_POOL_SHARDS
+from .secret_by_id import SecretById
from .gateway_config import GatewayConfig
from .network_policy import NetworkPolicy
from .storage_object import StorageObject
-from .scenario_builder import ScenarioBuilder
from ..types.secret_view import SecretView
from ..lib.context_loader import TarFilter, build_directory_tar
from ..types.object_create_params import ContentType
-from ..types.agent_devbox_counts_view import AgentDevboxCountsView
-from ..types.shared_params.agent_source import Git, Npm, Pip, Object
class DevboxOps:
@@ -344,7 +330,7 @@ def create(
:rtype: StorageObject
"""
obj = self._client.objects.create(**params)
- return StorageObject(self._client, obj.id, upload_url=obj.upload_url)
+ return StorageObject(self._client, obj.id, upload_url=obj.upload_url, upload_headers=obj.upload_headers)
def from_id(self, object_id: str) -> StorageObject:
"""Return a storage object wrapper by identifier.
@@ -544,7 +530,7 @@ def from_id(self, axon_id: str) -> Axon:
return Axon(self._client, axon_id)
def list(self, **params: Unpack[SDKAxonListParams]) -> list[Axon]:
- """[Beta] List all active axons.
+ """[Beta] List all axons, including deleted axons.
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAxonListParams` for available parameters
:return: Collection of axon wrappers
@@ -554,423 +540,6 @@ def list(self, **params: Unpack[SDKAxonListParams]) -> list[Axon]:
return [Axon(self._client, axon.id) for axon in result.axons]
-class ScorerOps:
- """Create and manage custom scorers. Access via ``runloop.scorer``.
-
- Example:
- >>> runloop = RunloopSDK()
- >>> scorer = runloop.scorer.create(type="my_scorer", bash_script="echo 'score=1.0'")
- >>> all_scorers = runloop.scorer.list()
- """
-
- def __init__(self, client: Runloop) -> None:
- """Initialize ScorerOps.
-
- :param client: Runloop client instance
- :type client: Runloop
- """
- self._client = client
-
- def create(self, **params: Unpack[SDKScorerCreateParams]) -> Scorer:
- """Create a new scorer with the given type and bash script.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerCreateParams` for available parameters
- :return: The newly created scorer
- :rtype: Scorer
- """
- response = self._client.scenarios.scorers.create(**params)
- return Scorer(self._client, response.id)
-
- def from_id(self, scorer_id: str) -> Scorer:
- """Get a Scorer instance for an existing scorer ID.
-
- :param scorer_id: ID of the scorer
- :type scorer_id: str
- :return: Scorer instance for the given ID
- :rtype: Scorer
- """
- return Scorer(self._client, scorer_id)
-
- def list(self, **params: Unpack[SDKScorerListParams]) -> list[Scorer]:
- """List all scorers, optionally filtered by parameters.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerListParams` for available parameters
- :return: List of scorers
- :rtype: list[Scorer]
- """
- page = self._client.scenarios.scorers.list(**params)
- return [Scorer(self._client, item.id) for item in page.scorers]
-
-
-class AgentOps:
- """High-level manager for creating and managing agents.
-
- Accessed via ``runloop.agent`` from :class:`RunloopSDK`, provides methods to
- create, retrieve, and list agents from various sources (npm, pip, git, object storage).
-
- Example:
- >>> runloop = RunloopSDK()
- >>> # Create agent from NPM package
- >>> agent = runloop.agent.create_from_npm(name="my-agent", package_name="@runloop/example-agent")
- >>> # Create agent from Git repository
- >>> agent = runloop.agent.create_from_git(
- ... name="git-agent", repository="https://github.com/user/agent-repo", ref="main"
- ... )
- >>> # List all agents
- >>> agents = runloop.agent.list(limit=10)
- """
-
- def __init__(self, client: Runloop) -> None:
- """Initialize the manager.
-
- :param client: Generated Runloop client to wrap
- :type client: Runloop
- """
- self._client = client
-
- def create(
- self,
- **params: Unpack[SDKAgentCreateParams],
- ) -> Agent:
- """Create a new agent.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for available parameters
- :return: Wrapper bound to the newly created agent
- :rtype: Agent
- """
- agent_view = self._client.agents.create(
- **params,
- )
- return Agent(self._client, agent_view.id, agent_view)
-
- def create_from_npm(
- self,
- *,
- package_name: str,
- registry_url: Optional[str] = None,
- agent_setup: Optional[list[str]] = None,
- **params: Unpack[SDKAgentCreateParams],
- ) -> Agent:
- """Create an agent from an NPM package.
-
- Example:
- >>> agent = runloop.agent.create_from_npm(
- ... name="my-npm-agent", package_name="@runloop/example-agent", version="1.0.0"
- ... )
-
- :param package_name: NPM package name
- :type package_name: str
- :param registry_url: NPM registry URL, defaults to None
- :type registry_url: Optional[str], optional
- :param agent_setup: Setup commands to run after installation, defaults to None
- :type agent_setup: Optional[list[str]], optional
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
- :return: Wrapper bound to the newly created agent
- :rtype: Agent
- :raises ValueError: If 'source' is provided in params
- """
- if "source" in params:
- raise ValueError(
- "Cannot specify 'source' when using create_from_npm(); source is automatically set to npm configuration"
- )
-
- npm_config: Npm = {"package_name": package_name}
- if registry_url is not None:
- npm_config["registry_url"] = registry_url
- if agent_setup is not None:
- npm_config["agent_setup"] = agent_setup
-
- params["source"] = {"type": "npm", "npm": npm_config}
- return self.create(**params)
-
- def create_from_pip(
- self,
- *,
- package_name: str,
- registry_url: Optional[str] = None,
- agent_setup: Optional[list[str]] = None,
- **params: Unpack[SDKAgentCreateParams],
- ) -> Agent:
- """Create an agent from a Pip package.
-
- Example:
- >>> agent = runloop.agent.create_from_pip(
- ... name="my-pip-agent", package_name="runloop-example-agent", version="1.0.0"
- ... )
-
- :param package_name: Pip package name
- :type package_name: str
- :param registry_url: Pip registry URL, defaults to None
- :type registry_url: Optional[str], optional
- :param agent_setup: Setup commands to run after installation, defaults to None
- :type agent_setup: Optional[list[str]], optional
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
- :return: Wrapper bound to the newly created agent
- :rtype: Agent
- :raises ValueError: If 'source' is provided in params
- """
- if "source" in params:
- raise ValueError(
- "Cannot specify 'source' when using create_from_pip(); source is automatically set to pip configuration"
- )
-
- pip_config: Pip = {"package_name": package_name}
- if registry_url is not None:
- pip_config["registry_url"] = registry_url
- if agent_setup is not None:
- pip_config["agent_setup"] = agent_setup
-
- params["source"] = {"type": "pip", "pip": pip_config}
- return self.create(**params)
-
- def create_from_git(
- self,
- *,
- repository: str,
- ref: Optional[str] = None,
- agent_setup: Optional[list[str]] = None,
- **params: Unpack[SDKAgentCreateParams],
- ) -> Agent:
- """Create an agent from a Git repository.
-
- Example:
- >>> agent = runloop.agent.create_from_git(
- ... name="my-git-agent",
- ... repository="https://github.com/user/agent-repo",
- ... ref="main",
- ... agent_setup=["npm install", "npm run build"],
- ... version="1.0.0",
- ... )
-
- :param repository: Git repository URL
- :type repository: str
- :param ref: Optional Git ref (branch/tag/commit), defaults to main/HEAD
- :type ref: Optional[str], optional
- :param agent_setup: Setup commands to run after cloning, defaults to None
- :type agent_setup: Optional[list[str]], optional
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
- :return: Wrapper bound to the newly created agent
- :rtype: Agent
- :raises ValueError: If 'source' is provided in params
- """
- if "source" in params:
- raise ValueError(
- "Cannot specify 'source' when using create_from_git(); source is automatically set to git configuration"
- )
-
- git_config: Git = {"repository": repository}
- if ref is not None:
- git_config["ref"] = ref
- if agent_setup is not None:
- git_config["agent_setup"] = agent_setup
-
- params["source"] = {"type": "git", "git": git_config}
- return self.create(**params)
-
- def create_from_object(
- self,
- *,
- object_id: str,
- agent_setup: Optional[list[str]] = None,
- **params: Unpack[SDKAgentCreateParams],
- ) -> Agent:
- """Create an agent from a storage object.
-
- Example:
- >>> # First upload agent code as an object
- >>> obj = runloop.storage_object.upload_from_dir("./my-agent")
- >>> # Then create agent from the object
- >>> agent = runloop.agent.create_from_object(
- ... name="my-object-agent",
- ... object_id=obj.id,
- ... agent_setup=["chmod +x setup.sh", "./setup.sh"],
- ... version="1.0.0",
- ... )
-
- :param object_id: Storage object ID
- :type object_id: str
- :param agent_setup: Setup commands to run after unpacking, defaults to None
- :type agent_setup: Optional[list[str]], optional
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentCreateParams` for additional parameters (excluding 'source')
- :return: Wrapper bound to the newly created agent
- :rtype: Agent
- :raises ValueError: If 'source' is provided in params
- """
- if "source" in params:
- raise ValueError(
- "Cannot specify 'source' when using create_from_object(); source is automatically set to object configuration"
- )
-
- object_config: Object = {"object_id": object_id}
- if agent_setup is not None:
- object_config["agent_setup"] = agent_setup
-
- params["source"] = {"type": "object", "object": object_config}
- return self.create(**params)
-
- def from_id(self, agent_id: str) -> Agent:
- """Attach to an existing agent by ID.
-
- :param agent_id: Existing agent ID
- :type agent_id: str
- :return: Wrapper bound to the requested agent
- :rtype: Agent
- """
- return Agent(self._client, agent_id)
-
- def list(
- self,
- **params: Unpack[SDKAgentListParams],
- ) -> list[Agent]:
- """List agents accessible to the caller.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentListParams` for available parameters
- :return: Collection of agent wrappers
- :rtype: list[Agent]
- """
- page = self._client.agents.list(
- **params,
- )
- return [Agent(self._client, item.id, item) for item in page.agents]
-
- def list_public(
- self,
- **params: Unpack[SDKAgentListPublicParams],
- ) -> List[Agent]:
- """List public agents.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentListPublicParams` for available parameters
- :return: Collection of public agent wrappers
- :rtype: list[Agent]
- """
- page = self._client.agents.list_public(
- **params,
- )
- return [Agent(self._client, item.id, item) for item in page.agents]
-
- def devbox_counts(
- self,
- **options: Unpack[BaseRequestOptions],
- ) -> AgentDevboxCountsView:
- """Get devbox counts grouped by agent name.
-
- :param options: Optional request configuration
- :return: Devbox counts per agent name and total
- :rtype: AgentDevboxCountsView
- """
- return self._client.agents.devbox_counts(
- **options,
- )
-
-
-class ScenarioOps:
- """Manage scenarios. Access via ``runloop.scenario``.
-
- Example:
- >>> runloop = RunloopSDK()
- >>> scenario = runloop.scenario.from_id("scn-xxx")
- >>> run = scenario.run()
- >>> scenarios = runloop.scenario.list()
-
- Example using builder:
- >>> builder = (
- ... runloop.scenario.builder("my-scenario")
- ... .from_blueprint(blueprint)
- ... .with_problem_statement("Fix the bug")
- ... .add_test_command_scorer("tests", test_command="pytest")
- ... )
- >>> params = builder.build()
- >>> scenario = runloop.scenario.create(**params) # equivalent to builder.push()
- """
-
- def __init__(self, client: Runloop) -> None:
- """Initialize ScenarioOps.
-
- :param client: Runloop client instance
- :type client: Runloop
- """
- self._client = client
-
- def builder(self, name: str) -> ScenarioBuilder:
- """Create a new scenario builder.
-
- :param name: Name for the scenario
- :type name: str
- :return: A new ScenarioBuilder instance
- :rtype: ScenarioBuilder
- """
- return ScenarioBuilder(name, self._client)
-
- def from_id(self, scenario_id: str) -> Scenario:
- """Get a Scenario instance for an existing scenario ID.
-
- :param scenario_id: ID of the scenario
- :type scenario_id: str
- :return: Scenario instance for the given ID
- :rtype: Scenario
- """
- return Scenario(self._client, scenario_id)
-
- def list(self, **params: Unpack[SDKScenarioListParams]) -> list[Scenario]:
- """List all scenarios, optionally filtered by parameters.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioListParams` for available parameters
- :return: List of scenarios
- :rtype: list[Scenario]
- """
- page = self._client.scenarios.list(**params)
- return [Scenario(self._client, item.id) for item in page.scenarios]
-
-
-class BenchmarkOps:
- """Manage benchmarks. Access via ``runloop.benchmark``.
-
- Example:
- >>> runloop = RunloopSDK()
- >>> benchmarks = runloop.benchmark.list()
- >>> benchmark = runloop.benchmark.from_id("bmd_xxx")
- >>> run = benchmark.start_run(run_name="evaluation-v1")
- """
-
- def __init__(self, client: Runloop) -> None:
- """Initialize BenchmarkOps.
-
- :param client: Runloop client instance
- :type client: Runloop
- """
- self._client = client
-
- def create(self, **params: Unpack[SDKBenchmarkCreateParams]) -> Benchmark:
- """Create a new benchmark.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkCreateParams` for available parameters
- :return: The newly created benchmark
- :rtype: Benchmark
- """
- response = self._client.benchmarks.create(**params)
- return Benchmark(self._client, response.id)
-
- def from_id(self, benchmark_id: str) -> Benchmark:
- """Get a Benchmark instance for an existing benchmark ID.
-
- :param benchmark_id: ID of the benchmark
- :type benchmark_id: str
- :return: Benchmark instance for the given ID
- :rtype: Benchmark
- """
- return Benchmark(self._client, benchmark_id)
-
- def list(self, **params: Unpack[SDKBenchmarkListParams]) -> list[Benchmark]:
- """List all benchmarks, optionally filtered by parameters.
-
- :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkListParams` for available parameters
- :return: List of benchmarks
- :rtype: list[Benchmark]
- """
- page = self._client.benchmarks.list(**params)
- return [Benchmark(self._client, item.id) for item in page.benchmarks]
-
-
class NetworkPolicyOps:
"""High-level manager for creating and managing network policies.
@@ -1196,7 +765,7 @@ def create(self, name: str, value: str, **options: Unpack[LongRequestOptions]) -
... )
>>> print(f"Created secret: {secret.name}")
- :param name: Globally unique secret name (must be a valid env var name)
+ :param name: Account-scoped secret name (must be a valid env var name)
:type name: str
:param value: Secret value to store (encrypted at rest)
:type value: str
@@ -1217,13 +786,17 @@ def from_name(self, name: str) -> Secret:
>>> info = secret.get_info()
>>> print(f"Secret ID: {info.id}")
- :param name: The globally unique name of the secret
+ :param name: The literal account-scoped name of the secret
:type name: str
:return: A Secret instance (no API call made)
:rtype: Secret
"""
return Secret(self._client, name)
+ def from_id(self, id: str) -> SecretById:
+ """Get a lazy exact-ID handle; its operations never fall back to a name."""
+ return SecretById(self._client, id)
+
def update(
self,
secret: "Secret | str",
@@ -1295,20 +868,12 @@ class RunloopSDK:
:ivar api: Direct access to the generated REST API client
:vartype api: Runloop
- :ivar agent: High-level interface for agent management.
- :vartype agent: AgentOps
:ivar axon: [Beta] High-level interface for axon management
:vartype axon: AxonOps
- :ivar benchmark: High-level interface for benchmark management
- :vartype benchmark: BenchmarkOps
:ivar devbox: High-level interface for devbox management
:vartype devbox: DevboxOps
:ivar blueprint: High-level interface for blueprint management
:vartype blueprint: BlueprintOps
- :ivar scenario: High-level interface for scenario management
- :vartype scenario: ScenarioOps
- :ivar scorer: High-level interface for scorer management
- :vartype scorer: ScorerOps
:ivar snapshot: High-level interface for snapshot management
:vartype snapshot: SnapshotOps
:ivar storage_object: High-level interface for storage object management
@@ -1331,16 +896,12 @@ class RunloopSDK:
"""
api: Runloop
- agent: AgentOps
axon: AxonOps
- benchmark: BenchmarkOps
devbox: DevboxOps
blueprint: BlueprintOps
gateway_config: GatewayConfigOps
mcp_config: McpConfigOps
network_policy: NetworkPolicyOps
- scenario: ScenarioOps
- scorer: ScorerOps
secret: SecretOps
snapshot: SnapshotOps
storage_object: StorageObjectOps
@@ -1395,17 +956,13 @@ def __init__(
transfer_pool_shards=transfer_pool_shards,
)
- self.agent = AgentOps(self.api)
self.axon = AxonOps(self.api)
- self.benchmark = BenchmarkOps(self.api)
self.devbox = DevboxOps(self.api)
self.blueprint = BlueprintOps(self.api)
self.gateway_config = GatewayConfigOps(self.api)
self.mcp_config = McpConfigOps(self.api)
self.network_policy = NetworkPolicyOps(self.api)
self.secret = SecretOps(self.api)
- self.scenario = ScenarioOps(self.api)
- self.scorer = ScorerOps(self.api)
self.snapshot = SnapshotOps(self.api)
self.storage_object = StorageObjectOps(self.api)
diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py
index d3ad8c627..2e37bc2a6 100644
--- a/src/runloop_api_client/types/__init__.py
+++ b/src/runloop_api_client/types/__init__.py
@@ -5,9 +5,6 @@
from .shared import (
Mount as Mount,
AfterIdle as AfterIdle,
- AgentMount as AgentMount,
- RunProfile as RunProfile,
- AgentSource as AgentSource,
BrokerMount as BrokerMount,
ObjectMount as ObjectMount,
CustomHeader as CustomHeader,
@@ -20,20 +17,15 @@
)
from .axon_view import AxonView as AxonView
from .port_rule import PortRule as PortRule
-from .agent_view import AgentView as AgentView
from .devbox_view import DevboxView as DevboxView
from .object_view import ObjectView as ObjectView
from .secret_view import SecretView as SecretView
from .tunnel_view import TunnelView as TunnelView
from .account_view import AccountView as AccountView
from .allowed_cidr import AllowedCidr as AllowedCidr
-from .input_context import InputContext as InputContext
-from .scenario_view import ScenarioView as ScenarioView
from .axon_list_view import AxonListView as AxonListView
-from .benchmark_view import BenchmarkView as BenchmarkView
from .blueprint_view import BlueprintView as BlueprintView
from .mcp_token_view import McpTokenView as McpTokenView
-from .agent_list_view import AgentListView as AgentListView
from .axon_event_view import AxonEventView as AxonEventView
from .mcp_config_view import McpConfigView as McpConfigView
from .port_rule_param import PortRuleParam as PortRuleParam
@@ -43,27 +35,20 @@
from .object_list_view import ObjectListView as ObjectListView
from .pty_connect_view import PtyConnectView as PtyConnectView
from .scope_entry_view import ScopeEntryView as ScopeEntryView
-from .scoring_contract import ScoringContract as ScoringContract
-from .scoring_function import ScoringFunction as ScoringFunction
from .secret_list_view import SecretListView as SecretListView
-from .agent_list_params import AgentListParams as AgentListParams
-from .scenario_run_view import ScenarioRunView as ScenarioRunView
from .allowed_cidr_param import AllowedCidrParam as AllowedCidrParam
from .axon_create_params import AxonCreateParams as AxonCreateParams
-from .benchmark_job_view import BenchmarkJobView as BenchmarkJobView
-from .benchmark_run_view import BenchmarkRunView as BenchmarkRunView
+from .axon_update_params import AxonUpdateParams as AxonUpdateParams
from .devbox_list_params import DevboxListParams as DevboxListParams
from .gateway_token_view import GatewayTokenView as GatewayTokenView
from .object_list_params import ObjectListParams as ObjectListParams
from .pty_connect_params import PtyConnectParams as PtyConnectParams
from .pty_control_params import PtyControlParams as PtyControlParams
from .secret_list_params import SecretListParams as SecretListParams
-from .agent_create_params import AgentCreateParams as AgentCreateParams
from .axon_publish_params import AxonPublishParams as AxonPublishParams
from .blueprint_build_log import BlueprintBuildLog as BlueprintBuildLog
from .blueprint_list_view import BlueprintListView as BlueprintListView
from .gateway_config_view import GatewayConfigView as GatewayConfigView
-from .input_context_param import InputContextParam as InputContextParam
from .network_policy_view import NetworkPolicyView as NetworkPolicyView
from .publish_result_view import PublishResultView as PublishResultView
from .api_key_created_view import APIKeyCreatedView as APIKeyCreatedView
@@ -73,31 +58,17 @@
from .devbox_update_params import DevboxUpdateParams as DevboxUpdateParams
from .mcp_config_list_view import McpConfigListView as McpConfigListView
from .object_create_params import ObjectCreateParams as ObjectCreateParams
-from .scenario_environment import ScenarioEnvironment as ScenarioEnvironment
-from .scenario_list_params import ScenarioListParams as ScenarioListParams
from .secret_create_params import SecretCreateParams as SecretCreateParams
from .secret_update_params import SecretUpdateParams as SecretUpdateParams
-from .benchmark_list_params import BenchmarkListParams as BenchmarkListParams
from .blueprint_list_params import BlueprintListParams as BlueprintListParams
from .devbox_execute_params import DevboxExecuteParams as DevboxExecuteParams
from .blueprint_preview_view import BlueprintPreviewView as BlueprintPreviewView
from .devbox_shutdown_params import DevboxShutdownParams as DevboxShutdownParams
from .mcp_config_list_params import McpConfigListParams as McpConfigListParams
from .object_download_params import ObjectDownloadParams as ObjectDownloadParams
-from .scenario_create_params import ScenarioCreateParams as ScenarioCreateParams
-from .scenario_run_list_view import ScenarioRunListView as ScenarioRunListView
-from .scenario_update_params import ScenarioUpdateParams as ScenarioUpdateParams
from .scope_entry_view_param import ScopeEntryViewParam as ScopeEntryViewParam
-from .scoring_contract_param import ScoringContractParam as ScoringContractParam
-from .scoring_function_param import ScoringFunctionParam as ScoringFunctionParam
-from .benchmark_create_params import BenchmarkCreateParams as BenchmarkCreateParams
-from .benchmark_job_list_view import BenchmarkJobListView as BenchmarkJobListView
-from .benchmark_run_list_view import BenchmarkRunListView as BenchmarkRunListView
-from .benchmark_update_params import BenchmarkUpdateParams as BenchmarkUpdateParams
from .blueprint_create_params import BlueprintCreateParams as BlueprintCreateParams
from .pty_control_result_view import PtyControlResultView as PtyControlResultView
-from .agent_devbox_counts_view import AgentDevboxCountsView as AgentDevboxCountsView
-from .agent_list_public_params import AgentListPublicParams as AgentListPublicParams
from .blueprint_preview_params import BlueprintPreviewParams as BlueprintPreviewParams
from .gateway_config_list_view import GatewayConfigListView as GatewayConfigListView
from .mcp_config_create_params import McpConfigCreateParams as McpConfigCreateParams
@@ -105,31 +76,22 @@
from .network_policy_list_view import NetworkPolicyListView as NetworkPolicyListView
from .object_download_url_view import ObjectDownloadURLView as ObjectDownloadURLView
from .axon_subscribe_sse_params import AxonSubscribeSseParams as AxonSubscribeSseParams
-from .benchmark_job_list_params import BenchmarkJobListParams as BenchmarkJobListParams
-from .benchmark_run_list_params import BenchmarkRunListParams as BenchmarkRunListParams
from .devbox_send_std_in_result import DevboxSendStdInResult as DevboxSendStdInResult
from .devbox_snapshot_list_view import DevboxSnapshotListView as DevboxSnapshotListView
from .devbox_upload_file_params import DevboxUploadFileParams as DevboxUploadFileParams
from .object_list_public_params import ObjectListPublicParams as ObjectListPublicParams
-from .scenario_start_run_params import ScenarioStartRunParams as ScenarioStartRunParams
-from .benchmark_start_run_params import BenchmarkStartRunParams as BenchmarkStartRunParams
from .blueprint_build_parameters import BlueprintBuildParameters as BlueprintBuildParameters
from .devbox_eviction_event_view import DevboxEvictionEventView as DevboxEvictionEventView
from .devbox_execute_sync_params import DevboxExecuteSyncParams as DevboxExecuteSyncParams
from .devbox_resource_usage_view import DevboxResourceUsageView as DevboxResourceUsageView
from .gateway_config_list_params import GatewayConfigListParams as GatewayConfigListParams
-from .input_context_update_param import InputContextUpdateParam as InputContextUpdateParam
from .network_policy_list_params import NetworkPolicyListParams as NetworkPolicyListParams
-from .scenario_environment_param import ScenarioEnvironmentParam as ScenarioEnvironmentParam
-from .benchmark_job_create_params import BenchmarkJobCreateParams as BenchmarkJobCreateParams
+from .secret_update_by_id_params import SecretUpdateByIDParams as SecretUpdateByIDParams
from .devbox_download_file_params import DevboxDownloadFileParams as DevboxDownloadFileParams
from .devbox_enable_tunnel_params import DevboxEnableTunnelParams as DevboxEnableTunnelParams
from .devbox_execute_async_params import DevboxExecuteAsyncParams as DevboxExecuteAsyncParams
from .devbox_snapshot_disk_params import DevboxSnapshotDiskParams as DevboxSnapshotDiskParams
from .restricted_key_created_view import RestrictedKeyCreatedView as RestrictedKeyCreatedView
-from .scenario_list_public_params import ScenarioListPublicParams as ScenarioListPublicParams
-from .benchmark_definitions_params import BenchmarkDefinitionsParams as BenchmarkDefinitionsParams
-from .benchmark_list_public_params import BenchmarkListPublicParams as BenchmarkListPublicParams
from .blueprint_list_public_params import BlueprintListPublicParams as BlueprintListPublicParams
from .devbox_execution_detail_view import DevboxExecutionDetailView as DevboxExecutionDetailView
from .gateway_config_create_params import GatewayConfigCreateParams as GatewayConfigCreateParams
@@ -137,22 +99,13 @@
from .network_policy_create_params import NetworkPolicyCreateParams as NetworkPolicyCreateParams
from .network_policy_update_params import NetworkPolicyUpdateParams as NetworkPolicyUpdateParams
from .restricted_key_create_params import RestrictedKeyCreateParams as RestrictedKeyCreateParams
-from .scoring_contract_result_view import ScoringContractResultView as ScoringContractResultView
-from .scoring_function_result_view import ScoringFunctionResultView as ScoringFunctionResultView
-from .scenario_definition_list_view import ScenarioDefinitionListView as ScenarioDefinitionListView
-from .scoring_contract_update_param import ScoringContractUpdateParam as ScoringContractUpdateParam
from .blueprint_build_logs_list_view import BlueprintBuildLogsListView as BlueprintBuildLogsListView
from .devbox_create_mcp_token_params import DevboxCreateMcpTokenParams as DevboxCreateMcpTokenParams
-from .devbox_create_ssh_key_response import DevboxCreateSSHKeyResponse as DevboxCreateSSHKeyResponse
from .devbox_wait_for_command_params import DevboxWaitForCommandParams as DevboxWaitForCommandParams
from .devbox_read_file_contents_params import DevboxReadFileContentsParams as DevboxReadFileContentsParams
-from .benchmark_update_scenarios_params import BenchmarkUpdateScenariosParams as BenchmarkUpdateScenariosParams
from .devbox_list_disk_snapshots_params import DevboxListDiskSnapshotsParams as DevboxListDiskSnapshotsParams
from .devbox_snapshot_disk_async_params import DevboxSnapshotDiskAsyncParams as DevboxSnapshotDiskAsyncParams
from .devbox_write_file_contents_params import DevboxWriteFileContentsParams as DevboxWriteFileContentsParams
from .devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView as DevboxAsyncExecutionDetailView
from .devbox_create_gateway_token_params import DevboxCreateGatewayTokenParams as DevboxCreateGatewayTokenParams
from .devbox_read_file_contents_response import DevboxReadFileContentsResponse as DevboxReadFileContentsResponse
-from .benchmark_run_list_scenario_runs_params import (
- BenchmarkRunListScenarioRunsParams as BenchmarkRunListScenarioRunsParams,
-)
diff --git a/src/runloop_api_client/types/account_view.py b/src/runloop_api_client/types/account_view.py
index 0507674c1..36dce6530 100644
--- a/src/runloop_api_client/types/account_view.py
+++ b/src/runloop_api_client/types/account_view.py
@@ -5,20 +5,7 @@
from .._models import BaseModel
-__all__ = ["AccountView", "Billing", "BillingAws", "BillingStripe"]
-
-
-class BillingAws(BaseModel):
- """AWS Marketplace billing information."""
-
- customer_identifier: Optional[str] = None
- """The AWS account ID used for Marketplace billing (12-digit)."""
-
- license_arn: Optional[str] = None
- """The AWS Marketplace license ARN."""
-
- subscription_status: Optional[str] = None
- """The AWS Marketplace subscription status."""
+__all__ = ["AccountView", "Billing", "BillingStripe"]
class BillingStripe(BaseModel):
@@ -34,12 +21,9 @@ class BillingStripe(BaseModel):
class Billing(BaseModel):
"""The account billing information."""
- account_billing_type: Literal["STRIPE", "AWS_MARKETPLACE", "STRIPE_PROJECTS", "UNRECOGNIZED"]
+ account_billing_type: Literal["STRIPE", "AWS_MARKETPLACE", "STRIPE_PROJECTS", "NONE", "UNRECOGNIZED"]
"""The account billing type."""
- aws: Optional[BillingAws] = None
- """AWS Marketplace billing information."""
-
stripe: Optional[BillingStripe] = None
"""Stripe billing information."""
@@ -84,14 +68,13 @@ class AccountView(BaseModel):
]
"""The account tier."""
- account_billing_type: Optional[Literal["STRIPE", "AWS_MARKETPLACE", "STRIPE_PROJECTS", "UNRECOGNIZED"]] = None
+ account_billing_type: Optional[Literal["STRIPE", "AWS_MARKETPLACE", "STRIPE_PROJECTS", "NONE", "UNRECOGNIZED"]] = (
+ None
+ )
"""Deprecated: use billing.account_billing_type."""
active_subscription: Optional[str] = None
"""Deprecated: use billing.stripe.active_subscription."""
- external_billing_account_id: Optional[str] = None
- """Deprecated: use billing.aws.customer_identifier."""
-
stripe_customer_id: Optional[str] = None
"""Deprecated: use billing.stripe.customer_id."""
diff --git a/src/runloop_api_client/types/agent_create_params.py b/src/runloop_api_client/types/agent_create_params.py
deleted file mode 100644
index ad97cc4f4..000000000
--- a/src/runloop_api_client/types/agent_create_params.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import Required, TypedDict
-
-from .shared_params.agent_source import AgentSource
-
-__all__ = ["AgentCreateParams"]
-
-
-class AgentCreateParams(TypedDict, total=False):
- name: Required[str]
- """The name of the Agent."""
-
- source: Optional[AgentSource]
- """Agent source configuration."""
-
- version: Optional[str]
- """Optional version identifier for the Agent.
-
- For npm/pip sources this is typically a semver string (e.g. '2.0.65'). For git
- sources it can be a branch or tag. Semantics are user-defined for object
- sources.
- """
diff --git a/src/runloop_api_client/types/agent_devbox_counts_view.py b/src/runloop_api_client/types/agent_devbox_counts_view.py
deleted file mode 100644
index af9006d1e..000000000
--- a/src/runloop_api_client/types/agent_devbox_counts_view.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Dict
-
-from .._models import BaseModel
-
-__all__ = ["AgentDevboxCountsView"]
-
-
-class AgentDevboxCountsView(BaseModel):
- """Devbox counts grouped by agent name.
-
- Used to efficiently fetch devbox counts for multiple agents in a single request.
- """
-
- counts: Dict[str, int]
- """Map of agent name to devbox count.
-
- Each key is an agent name, and the value is the count of devboxes associated
- with that agent.
- """
-
- total_count: int
- """Total count of devboxes across all agents in the result."""
diff --git a/src/runloop_api_client/types/agent_list_params.py b/src/runloop_api_client/types/agent_list_params.py
deleted file mode 100644
index 5c3857fb5..000000000
--- a/src/runloop_api_client/types/agent_list_params.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import TypedDict
-
-__all__ = ["AgentListParams"]
-
-
-class AgentListParams(TypedDict, total=False):
- include_total_count: bool
- """If true (default), includes total_count in the response.
-
- Set to false to skip the count query for better performance on large datasets.
- """
-
- is_public: bool
- """Filter agents by public visibility."""
-
- limit: int
- """The limit of items to return. Default is 20. Max is 5000."""
-
- name: str
- """Filter agents by name (partial match supported)."""
-
- search: str
- """Search by agent ID or name."""
-
- starting_after: str
- """Load the next page of data starting after the item with the given ID."""
-
- version: str
- """Filter by version. Use 'latest' to get the most recently created agent."""
diff --git a/src/runloop_api_client/types/agent_list_public_params.py b/src/runloop_api_client/types/agent_list_public_params.py
deleted file mode 100644
index a0e53d503..000000000
--- a/src/runloop_api_client/types/agent_list_public_params.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import TypedDict
-
-__all__ = ["AgentListPublicParams"]
-
-
-class AgentListPublicParams(TypedDict, total=False):
- include_total_count: bool
- """If true (default), includes total_count in the response.
-
- Set to false to skip the count query for better performance on large datasets.
- """
-
- limit: int
- """The limit of items to return. Default is 20. Max is 5000."""
-
- name: str
- """Filter agents by name (partial match supported)."""
-
- search: str
- """Search by agent ID or name."""
-
- starting_after: str
- """Load the next page of data starting after the item with the given ID."""
-
- version: str
- """Filter by version. Use 'latest' to get the most recently created agent."""
diff --git a/src/runloop_api_client/types/agent_list_view.py b/src/runloop_api_client/types/agent_list_view.py
deleted file mode 100644
index f3dfbd09f..000000000
--- a/src/runloop_api_client/types/agent_list_view.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List, Optional
-
-from .._models import BaseModel
-from .agent_view import AgentView
-
-__all__ = ["AgentListView"]
-
-
-class AgentListView(BaseModel):
- """A paginated list of Agents."""
-
- agents: List[AgentView]
- """The list of Agents."""
-
- has_more: bool
- """Whether there are more Agents to fetch."""
-
- total_count: Optional[int] = None
- """The total count of Agents."""
diff --git a/src/runloop_api_client/types/agent_view.py b/src/runloop_api_client/types/agent_view.py
deleted file mode 100644
index 6139fd244..000000000
--- a/src/runloop_api_client/types/agent_view.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Optional
-
-from .._models import BaseModel
-from .shared.agent_source import AgentSource
-
-__all__ = ["AgentView"]
-
-
-class AgentView(BaseModel):
- """An Agent represents a registered AI agent entity."""
-
- id: str
- """The unique identifier of the Agent."""
-
- create_time_ms: int
- """The creation time of the Agent (Unix timestamp milliseconds)."""
-
- is_public: bool
- """Whether the Agent is publicly accessible."""
-
- name: str
- """The name of the Agent."""
-
- source: Optional[AgentSource] = None
- """Agent source configuration."""
-
- version: Optional[str] = None
- """Optional version identifier for the Agent.
-
- For npm/pip sources this is typically a semver string (e.g. '2.0.65'). For git
- sources it can be a branch or tag. Omitted for object sources or when not
- provided.
- """
diff --git a/src/runloop_api_client/types/axon_create_params.py b/src/runloop_api_client/types/axon_create_params.py
index 0906db0ae..d7bf49c8d 100644
--- a/src/runloop_api_client/types/axon_create_params.py
+++ b/src/runloop_api_client/types/axon_create_params.py
@@ -2,12 +2,15 @@
from __future__ import annotations
-from typing import Optional
+from typing import Dict, Optional
from typing_extensions import TypedDict
__all__ = ["AxonCreateParams"]
class AxonCreateParams(TypedDict, total=False):
+ metadata: Optional[Dict[str, str]]
+ """User defined metadata to attach to the axon for organization."""
+
name: Optional[str]
"""(Optional) Name for the axon."""
diff --git a/src/runloop_api_client/types/axon_list_params.py b/src/runloop_api_client/types/axon_list_params.py
index 7ae12827c..6efb722a9 100644
--- a/src/runloop_api_client/types/axon_list_params.py
+++ b/src/runloop_api_client/types/axon_list_params.py
@@ -2,7 +2,9 @@
from __future__ import annotations
-from typing_extensions import TypedDict
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
__all__ = ["AxonListParams"]
@@ -20,6 +22,15 @@ class AxonListParams(TypedDict, total=False):
limit: int
"""The limit of items to return. Default is 20. Max is 5000."""
+ metadata_key: Annotated[str, PropertyInfo(alias="metadata[key]")]
+ """Filter axons by metadata key-value pair.
+
+ Can be used multiple times for different keys.
+ """
+
+ metadata_key_in: Annotated[str, PropertyInfo(alias="metadata[key][in]")]
+ """Filter axons by metadata key with multiple possible values (OR condition)."""
+
name: str
"""Filter by axon name (prefix match supported)."""
diff --git a/src/runloop_api_client/types/axon_update_params.py b/src/runloop_api_client/types/axon_update_params.py
new file mode 100644
index 000000000..2763cb831
--- /dev/null
+++ b/src/runloop_api_client/types/axon_update_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+from typing_extensions import TypedDict
+
+__all__ = ["AxonUpdateParams"]
+
+
+class AxonUpdateParams(TypedDict, total=False):
+ metadata: Optional[Dict[str, str]]
+ """User defined metadata to replace the axon metadata.
+
+ Omit or set to null to leave unchanged, or set to an empty map to clear it.
+ """
diff --git a/src/runloop_api_client/types/axon_view.py b/src/runloop_api_client/types/axon_view.py
index 61d1bd243..863747538 100644
--- a/src/runloop_api_client/types/axon_view.py
+++ b/src/runloop_api_client/types/axon_view.py
@@ -1,6 +1,6 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing import Dict, Optional
from .._models import BaseModel
@@ -14,5 +14,11 @@ class AxonView(BaseModel):
created_at_ms: int
"""Creation time in milliseconds since epoch."""
+ metadata: Dict[str, str]
+ """The user defined axon metadata."""
+
+ deleted_at_ms: Optional[int] = None
+ """Deletion time in milliseconds since epoch; null while the axon is active."""
+
name: Optional[str] = None
"""The name of the axon."""
diff --git a/src/runloop_api_client/types/benchmark_create_params.py b/src/runloop_api_client/types/benchmark_create_params.py
deleted file mode 100644
index 36f7b95a9..000000000
--- a/src/runloop_api_client/types/benchmark_create_params.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Dict, Optional
-from typing_extensions import Required, TypedDict
-
-from .._types import SequenceNotStr
-
-__all__ = ["BenchmarkCreateParams"]
-
-
-class BenchmarkCreateParams(TypedDict, total=False):
- name: Required[str]
- """The unique name of the Benchmark."""
-
- attribution: Optional[str]
- """Attribution information for the benchmark."""
-
- description: Optional[str]
- """Detailed description of the benchmark."""
-
- metadata: Optional[Dict[str, str]]
- """User defined metadata to attach to the benchmark."""
-
- required_environment_variables: Optional[SequenceNotStr[str]]
- """Environment variables required to run the benchmark.
-
- If any required variables are not supplied, the benchmark will fail to start.
- """
-
- required_secret_names: SequenceNotStr[str]
- """
- Secrets required to run the benchmark with (environment variable name will be
- mapped to the your user secret by name). If any of these secrets are not
- provided or the mapping is incorrect, the benchmark will fail to start.
- """
-
- scenario_ids: Optional[SequenceNotStr[str]]
- """The Scenario IDs that make up the Benchmark."""
diff --git a/src/runloop_api_client/types/benchmark_definitions_params.py b/src/runloop_api_client/types/benchmark_definitions_params.py
deleted file mode 100644
index 97caff125..000000000
--- a/src/runloop_api_client/types/benchmark_definitions_params.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import TypedDict
-
-__all__ = ["BenchmarkDefinitionsParams"]
-
-
-class BenchmarkDefinitionsParams(TypedDict, total=False):
- limit: int
- """The limit of items to return. Default is 20. Max is 5000."""
-
- starting_after: str
- """Load the next page of data starting after the item with the given ID."""
diff --git a/src/runloop_api_client/types/benchmark_job_create_params.py b/src/runloop_api_client/types/benchmark_job_create_params.py
deleted file mode 100644
index f9be11555..000000000
--- a/src/runloop_api_client/types/benchmark_job_create_params.py
+++ /dev/null
@@ -1,208 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Dict, Union, Iterable, Optional
-from typing_extensions import Literal, Required, TypeAlias, TypedDict
-
-from .._types import SequenceNotStr
-
-__all__ = [
- "BenchmarkJobCreateParams",
- "Spec",
- "SpecHarborJobSpec",
- "SpecBenchmarkDefinitionJobSpec",
- "SpecBenchmarkDefinitionJobSpecAgentConfig",
- "SpecBenchmarkDefinitionJobSpecAgentConfigAgentEnvironment",
- "SpecBenchmarkDefinitionJobSpecOrchestratorConfig",
- "SpecScenarioDefinitionJobSpec",
- "SpecScenarioDefinitionJobSpecAgentConfig",
- "SpecScenarioDefinitionJobSpecAgentConfigAgentEnvironment",
- "SpecScenarioDefinitionJobSpecOrchestratorConfig",
-]
-
-
-class BenchmarkJobCreateParams(TypedDict, total=False):
- name: Optional[str]
- """The name of the BenchmarkJob.
-
- If not provided, name will be generated based on target dataset.
- """
-
- spec: Optional[Spec]
- """The job specification. Exactly one spec type must be set."""
-
-
-class SpecHarborJobSpec(TypedDict, total=False):
- """Harbor-based job specification with inline YAML configuration."""
-
- inline_yaml: Required[str]
- """The Harbor job configuration as inline YAML content."""
-
- type: Required[Literal["harbor"]]
-
-
-class SpecBenchmarkDefinitionJobSpecAgentConfigAgentEnvironment(TypedDict, total=False):
- """Environment configuration for an agent in a benchmark job"""
-
- environment_variables: Optional[Dict[str, str]]
- """Environment variables to set when launching the agent."""
-
- secrets: Optional[Dict[str, str]]
- """Secrets to inject as environment variables when launching the agent.
-
- Map of environment variable names to secret IDs.
- """
-
-
-class SpecBenchmarkDefinitionJobSpecAgentConfig(TypedDict, total=False):
- """Configuration for an agent in a benchmark job"""
-
- name: Required[str]
- """Name of the agent"""
-
- type: Required[Literal["job_agent"]]
-
- agent_environment: Optional[SpecBenchmarkDefinitionJobSpecAgentConfigAgentEnvironment]
- """Environment configuration for an agent in a benchmark job"""
-
- agent_id: Optional[str]
- """ID of the agent to use (optional if agent exists by name)"""
-
- kwargs: Optional[Dict[str, str]]
- """Additional kwargs for agent configuration"""
-
- model_name: Optional[str]
- """Model name override for this agent"""
-
- timeout_seconds: Optional[float]
- """Timeout in seconds for this agent"""
-
-
-class SpecBenchmarkDefinitionJobSpecOrchestratorConfig(TypedDict, total=False):
- """Orchestrator configuration for benchmark job execution"""
-
- n_attempts: Optional[int]
- """Number of retry attempts on failure (default: 0).
-
- This is the retry policy for failed scenarios. Default is 0.
- """
-
- n_concurrent_trials: Optional[int]
- """Number of concurrent trials to run (default: 1).
-
- Controls parallelism for scenario execution. Default is 1.
- """
-
- quiet: Optional[bool]
- """Suppress verbose output (default: false)"""
-
- timeout_multiplier: Optional[float]
- """Timeout multiplier for retries (default: 1.0).
-
- Each retry will multiply the timeout by this factor.
- """
-
-
-class SpecBenchmarkDefinitionJobSpec(TypedDict, total=False):
- """Specifies a benchmark definition with runtime configuration.
-
- The benchmark definition's scenarios will be executed using the provided agent and orchestrator configurations.
- """
-
- agent_configs: Required[Iterable[SpecBenchmarkDefinitionJobSpecAgentConfig]]
- """Agent configurations to use for this run. Must specify at least one agent."""
-
- benchmark_id: Required[str]
- """ID of the benchmark definition to run.
-
- The scenarios from this benchmark will be executed.
- """
-
- type: Required[Literal["benchmark"]]
-
- orchestrator_config: Optional[SpecBenchmarkDefinitionJobSpecOrchestratorConfig]
- """Orchestrator configuration for benchmark job execution"""
-
-
-class SpecScenarioDefinitionJobSpecAgentConfigAgentEnvironment(TypedDict, total=False):
- """Environment configuration for an agent in a benchmark job"""
-
- environment_variables: Optional[Dict[str, str]]
- """Environment variables to set when launching the agent."""
-
- secrets: Optional[Dict[str, str]]
- """Secrets to inject as environment variables when launching the agent.
-
- Map of environment variable names to secret IDs.
- """
-
-
-class SpecScenarioDefinitionJobSpecAgentConfig(TypedDict, total=False):
- """Configuration for an agent in a benchmark job"""
-
- name: Required[str]
- """Name of the agent"""
-
- type: Required[Literal["job_agent"]]
-
- agent_environment: Optional[SpecScenarioDefinitionJobSpecAgentConfigAgentEnvironment]
- """Environment configuration for an agent in a benchmark job"""
-
- agent_id: Optional[str]
- """ID of the agent to use (optional if agent exists by name)"""
-
- kwargs: Optional[Dict[str, str]]
- """Additional kwargs for agent configuration"""
-
- model_name: Optional[str]
- """Model name override for this agent"""
-
- timeout_seconds: Optional[float]
- """Timeout in seconds for this agent"""
-
-
-class SpecScenarioDefinitionJobSpecOrchestratorConfig(TypedDict, total=False):
- """Orchestrator configuration for benchmark job execution"""
-
- n_attempts: Optional[int]
- """Number of retry attempts on failure (default: 0).
-
- This is the retry policy for failed scenarios. Default is 0.
- """
-
- n_concurrent_trials: Optional[int]
- """Number of concurrent trials to run (default: 1).
-
- Controls parallelism for scenario execution. Default is 1.
- """
-
- quiet: Optional[bool]
- """Suppress verbose output (default: false)"""
-
- timeout_multiplier: Optional[float]
- """Timeout multiplier for retries (default: 1.0).
-
- Each retry will multiply the timeout by this factor.
- """
-
-
-class SpecScenarioDefinitionJobSpec(TypedDict, total=False):
- """Specifies a set of scenarios with runtime configuration.
-
- The scenarios will be executed using the provided agent and orchestrator configurations.
- """
-
- agent_configs: Required[Iterable[SpecScenarioDefinitionJobSpecAgentConfig]]
- """Agent configurations to use for this run. Must specify at least one agent."""
-
- scenario_ids: Required[SequenceNotStr[str]]
- """List of scenario IDs to execute"""
-
- type: Required[Literal["scenarios"]]
-
- orchestrator_config: Optional[SpecScenarioDefinitionJobSpecOrchestratorConfig]
- """Orchestrator configuration for benchmark job execution"""
-
-
-Spec: TypeAlias = Union[SpecHarborJobSpec, SpecBenchmarkDefinitionJobSpec, SpecScenarioDefinitionJobSpec]
diff --git a/src/runloop_api_client/types/benchmark_job_list_params.py b/src/runloop_api_client/types/benchmark_job_list_params.py
deleted file mode 100644
index fd68abd44..000000000
--- a/src/runloop_api_client/types/benchmark_job_list_params.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import TypedDict
-
-__all__ = ["BenchmarkJobListParams"]
-
-
-class BenchmarkJobListParams(TypedDict, total=False):
- include_total_count: bool
- """If true (default), includes total_count in the response.
-
- Set to false to skip the count query for better performance on large datasets.
- """
-
- limit: int
- """The limit of items to return. Default is 20. Max is 5000."""
-
- name: str
- """Filter by name"""
-
- search: str
- """Search by benchmark job ID or name."""
-
- starting_after: str
- """Load the next page of data starting after the item with the given ID."""
diff --git a/src/runloop_api_client/types/benchmark_job_list_view.py b/src/runloop_api_client/types/benchmark_job_list_view.py
deleted file mode 100644
index f4f2458c3..000000000
--- a/src/runloop_api_client/types/benchmark_job_list_view.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List, Optional
-
-from .._models import BaseModel
-from .benchmark_job_view import BenchmarkJobView
-
-__all__ = ["BenchmarkJobListView"]
-
-
-class BenchmarkJobListView(BaseModel):
- has_more: bool
-
- jobs: List[BenchmarkJobView]
- """List of BenchmarkJobs matching filter."""
-
- total_count: Optional[int] = None
diff --git a/src/runloop_api_client/types/benchmark_job_view.py b/src/runloop_api_client/types/benchmark_job_view.py
deleted file mode 100644
index ac1694d5d..000000000
--- a/src/runloop_api_client/types/benchmark_job_view.py
+++ /dev/null
@@ -1,335 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Dict, List, Union, Optional
-from typing_extensions import Literal, Annotated, TypeAlias
-
-from pydantic import Field as FieldInfo
-
-from .._utils import PropertyInfo
-from .._models import BaseModel
-
-__all__ = [
- "BenchmarkJobView",
- "BenchmarkOutcome",
- "BenchmarkOutcomeScenarioOutcome",
- "BenchmarkOutcomeScenarioOutcomeFailureReason",
- "InProgressRun",
- "InProgressRunAgentConfig",
- "InProgressRunAgentConfigExternalAPIAgentConfig",
- "InProgressRunAgentConfigJobAgentConfig",
- "InProgressRunAgentConfigJobAgentConfigAgentEnvironment",
- "JobSource",
- "JobSourceHarborJobSource",
- "JobSourceBenchmarkDefJobSource",
- "JobSourceScenariosJobSource",
- "JobSpec",
- "JobSpecAgentConfig",
- "JobSpecAgentConfigAgentEnvironment",
- "JobSpecOrchestratorConfig",
-]
-
-
-class BenchmarkOutcomeScenarioOutcomeFailureReason(BaseModel):
- """Information about why a scenario execution failed"""
-
- exception_message: str
- """The exception message providing context"""
-
- exception_type: str
- """The exception class name (e.g., 'TimeoutException', 'AgentTimeoutError')"""
-
-
-class BenchmarkOutcomeScenarioOutcome(BaseModel):
- """
- Outcome data for a single scenario execution, including its final state and scoring results.
- """
-
- scenario_definition_id: str
- """The ID of the scenario definition that was executed."""
-
- scenario_name: str
- """The name of the scenario."""
-
- state: Literal["COMPLETED", "FAILED", "TIMEOUT", "CANCELED"]
- """The final state of the scenario execution."""
-
- duration_ms: Optional[int] = None
- """Duration of the scenario execution in milliseconds."""
-
- failure_reason: Optional[BenchmarkOutcomeScenarioOutcomeFailureReason] = None
- """Information about why a scenario execution failed"""
-
- scenario_run_id: Optional[str] = None
- """The ID of the scenario run.
-
- May be absent if the scenario failed during setup before a run was created.
- """
-
- score: Optional[float] = None
- """The score achieved for this scenario (0.0 to 1.0).
-
- Only present if state is COMPLETED.
- """
-
-
-class BenchmarkOutcome(BaseModel):
- """
- Outcome data for a single benchmark run within a benchmark job, representing results for one agent configuration.
- """
-
- agent_name: str
- """The name of the agent configuration used."""
-
- benchmark_run_id: str
- """The ID of the benchmark run."""
-
- n_completed: int
- """Number of scenarios that completed successfully."""
-
- n_failed: int
- """Number of scenarios that failed."""
-
- n_timeout: int
- """Number of scenarios that timed out."""
-
- scenario_outcomes: List[BenchmarkOutcomeScenarioOutcome]
- """Detailed outcomes for each scenario in this benchmark run."""
-
- average_score: Optional[float] = None
- """Average score across all completed scenarios (0.0 to 1.0)."""
-
- duration_ms: Optional[int] = None
- """Total duration of the benchmark run in milliseconds."""
-
- api_model_name: Optional[str] = FieldInfo(alias="model_name", default=None)
- """The model name used by the agent."""
-
-
-class InProgressRunAgentConfigExternalAPIAgentConfig(BaseModel):
- """Configuration for externally-driven benchmark runs via API"""
-
- type: Literal["external_api"]
-
- info: Optional[str] = None
- """Placeholder for future external agent metadata"""
-
-
-class InProgressRunAgentConfigJobAgentConfigAgentEnvironment(BaseModel):
- """Environment configuration for an agent in a benchmark job"""
-
- environment_variables: Optional[Dict[str, str]] = None
- """Environment variables to set when launching the agent."""
-
- secrets: Optional[Dict[str, str]] = None
- """Secrets to inject as environment variables when launching the agent.
-
- Map of environment variable names to secret IDs.
- """
-
-
-class InProgressRunAgentConfigJobAgentConfig(BaseModel):
- """Configuration for an agent in a benchmark job"""
-
- name: str
- """Name of the agent"""
-
- type: Literal["job_agent"]
-
- agent_environment: Optional[InProgressRunAgentConfigJobAgentConfigAgentEnvironment] = None
- """Environment configuration for an agent in a benchmark job"""
-
- agent_id: Optional[str] = None
- """ID of the agent to use (optional if agent exists by name)"""
-
- kwargs: Optional[Dict[str, str]] = None
- """Additional kwargs for agent configuration"""
-
- api_model_name: Optional[str] = FieldInfo(alias="model_name", default=None)
- """Model name override for this agent"""
-
- timeout_seconds: Optional[float] = None
- """Timeout in seconds for this agent"""
-
-
-InProgressRunAgentConfig: TypeAlias = Annotated[
- Union[InProgressRunAgentConfigExternalAPIAgentConfig, InProgressRunAgentConfigJobAgentConfig, None],
- PropertyInfo(discriminator="type"),
-]
-
-
-class InProgressRun(BaseModel):
- """
- A lightweight view of a benchmark run currently in progress, showing basic execution details without full outcome data.
- """
-
- benchmark_run_id: str
- """The ID of the benchmark run."""
-
- start_time_ms: int
- """Start time (Unix milliseconds)."""
-
- state: Literal["running", "canceled", "completed", "failed"]
- """The current state of the run."""
-
- agent_config: Optional[InProgressRunAgentConfig] = None
- """Agent configuration used for this run.
-
- Specifies whether the run was driven by an external API agent or a job-defined
- agent.
- """
-
- duration_ms: Optional[int] = None
- """Duration so far in milliseconds."""
-
-
-class JobSourceHarborJobSource(BaseModel):
- """Harbor job source with inline YAML configuration"""
-
- inline_yaml: str
- """The Harbor job configuration as inline YAML content"""
-
- type: Literal["harbor"]
-
-
-class JobSourceBenchmarkDefJobSource(BaseModel):
- """Benchmark definition job source"""
-
- benchmark_id: str
- """The ID of the benchmark definition"""
-
- type: Literal["benchmark"]
-
- benchmark_name: Optional[str] = None
- """Optional user-provided name for the benchmark definition"""
-
-
-class JobSourceScenariosJobSource(BaseModel):
- """Scenarios job source with a list of scenario definition IDs"""
-
- scenario_ids: List[str]
- """List of scenario definition IDs to execute"""
-
- type: Literal["scenarios"]
-
-
-JobSource: TypeAlias = Annotated[
- Union[JobSourceHarborJobSource, JobSourceBenchmarkDefJobSource, JobSourceScenariosJobSource, None],
- PropertyInfo(discriminator="type"),
-]
-
-
-class JobSpecAgentConfigAgentEnvironment(BaseModel):
- """Environment configuration for an agent in a benchmark job"""
-
- environment_variables: Optional[Dict[str, str]] = None
- """Environment variables to set when launching the agent."""
-
- secrets: Optional[Dict[str, str]] = None
- """Secrets to inject as environment variables when launching the agent.
-
- Map of environment variable names to secret IDs.
- """
-
-
-class JobSpecAgentConfig(BaseModel):
- """Configuration for an agent in a benchmark job"""
-
- name: str
- """Name of the agent"""
-
- type: Literal["job_agent"]
-
- agent_environment: Optional[JobSpecAgentConfigAgentEnvironment] = None
- """Environment configuration for an agent in a benchmark job"""
-
- agent_id: Optional[str] = None
- """ID of the agent to use (optional if agent exists by name)"""
-
- kwargs: Optional[Dict[str, str]] = None
- """Additional kwargs for agent configuration"""
-
- api_model_name: Optional[str] = FieldInfo(alias="model_name", default=None)
- """Model name override for this agent"""
-
- timeout_seconds: Optional[float] = None
- """Timeout in seconds for this agent"""
-
-
-class JobSpecOrchestratorConfig(BaseModel):
- """Orchestrator configuration for benchmark job execution"""
-
- n_attempts: Optional[int] = None
- """Number of retry attempts on failure (default: 0).
-
- This is the retry policy for failed scenarios. Default is 0.
- """
-
- n_concurrent_trials: Optional[int] = None
- """Number of concurrent trials to run (default: 1).
-
- Controls parallelism for scenario execution. Default is 1.
- """
-
- quiet: Optional[bool] = None
- """Suppress verbose output (default: false)"""
-
- timeout_multiplier: Optional[float] = None
- """Timeout multiplier for retries (default: 1.0).
-
- Each retry will multiply the timeout by this factor.
- """
-
-
-class JobSpec(BaseModel):
- """Job specification describing scenarios and execution configuration"""
-
- agent_configs: List[JobSpecAgentConfig]
- """Agent configurations for this job"""
-
- scenario_ids: List[str]
- """List of scenario IDs to execute"""
-
- orchestrator_config: Optional[JobSpecOrchestratorConfig] = None
- """Orchestrator configuration for benchmark job execution"""
-
-
-class BenchmarkJobView(BaseModel):
- """
- A BenchmarkJobView represents a benchmark job that runs a set of scenarios entirely on runloop.
- """
-
- id: str
- """The ID of the BenchmarkJob."""
-
- create_time_ms: int
- """Timestamp when job was created (Unix milliseconds)."""
-
- name: str
- """The unique name of the BenchmarkJob."""
-
- state: Literal["initializing", "queued", "running", "completed", "failed", "cancelled", "timeout"]
- """The current state of the benchmark job."""
-
- benchmark_outcomes: Optional[List[BenchmarkOutcome]] = None
- """Detailed outcome data for each benchmark run created by this job.
-
- Includes per-agent results and scenario-level details.
- """
-
- failure_reason: Optional[str] = None
- """Failure reason if job failed."""
-
- in_progress_runs: Optional[List[InProgressRun]] = None
- """Benchmark runs currently in progress for this job.
-
- Shows runs that have not yet completed.
- """
-
- job_source: Optional[JobSource] = None
- """The source configuration that was used to create this job.
-
- Either Harbor YAML or benchmark definition reference.
- """
-
- job_spec: Optional[JobSpec] = None
- """Job specification describing scenarios and execution configuration"""
diff --git a/src/runloop_api_client/types/benchmark_list_params.py b/src/runloop_api_client/types/benchmark_list_params.py
deleted file mode 100644
index 650c93ca3..000000000
--- a/src/runloop_api_client/types/benchmark_list_params.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import TypedDict
-
-__all__ = ["BenchmarkListParams"]
-
-
-class BenchmarkListParams(TypedDict, total=False):
- include_total_count: bool
- """If true (default), includes total_count in the response.
-
- Set to false to skip the count query for better performance on large datasets.
- """
-
- limit: int
- """The limit of items to return. Default is 20. Max is 5000."""
-
- name: str
- """Filter by name"""
-
- search: str
- """Search by benchmark ID or name."""
-
- starting_after: str
- """Load the next page of data starting after the item with the given ID."""
diff --git a/src/runloop_api_client/types/benchmark_list_public_params.py b/src/runloop_api_client/types/benchmark_list_public_params.py
deleted file mode 100644
index de7426686..000000000
--- a/src/runloop_api_client/types/benchmark_list_public_params.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import TypedDict
-
-__all__ = ["BenchmarkListPublicParams"]
-
-
-class BenchmarkListPublicParams(TypedDict, total=False):
- include_total_count: bool
- """If true (default), includes total_count in the response.
-
- Set to false to skip the count query for better performance on large datasets.
- """
-
- limit: int
- """The limit of items to return. Default is 20. Max is 5000."""
-
- name: str
- """Filter by name"""
-
- search: str
- """Search by benchmark ID or name."""
-
- starting_after: str
- """Load the next page of data starting after the item with the given ID."""
diff --git a/src/runloop_api_client/types/benchmark_run_list_params.py b/src/runloop_api_client/types/benchmark_run_list_params.py
deleted file mode 100644
index 697d9db1f..000000000
--- a/src/runloop_api_client/types/benchmark_run_list_params.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import TypedDict
-
-__all__ = ["BenchmarkRunListParams"]
-
-
-class BenchmarkRunListParams(TypedDict, total=False):
- benchmark_id: str
- """The Benchmark ID to filter by."""
-
- include_total_count: bool
- """If true (default), includes total_count in the response.
-
- Set to false to skip the count query for better performance on large datasets.
- """
-
- limit: int
- """The limit of items to return. Default is 20. Max is 5000."""
-
- name: str
- """Filter by name"""
-
- search: str
- """Search by benchmark run ID or name."""
-
- starting_after: str
- """Load the next page of data starting after the item with the given ID."""
-
- state: str
- """Filter by state"""
diff --git a/src/runloop_api_client/types/benchmark_run_list_scenario_runs_params.py b/src/runloop_api_client/types/benchmark_run_list_scenario_runs_params.py
deleted file mode 100644
index ac626e6a8..000000000
--- a/src/runloop_api_client/types/benchmark_run_list_scenario_runs_params.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import Literal, TypedDict
-
-__all__ = ["BenchmarkRunListScenarioRunsParams"]
-
-
-class BenchmarkRunListScenarioRunsParams(TypedDict, total=False):
- include_total_count: bool
- """If true (default), includes total_count in the response.
-
- Set to false to skip the count query for better performance on large datasets.
- """
-
- limit: int
- """The limit of items to return. Default is 20. Max is 5000."""
-
- search: str
- """Search by scenario run ID or name."""
-
- starting_after: str
- """Load the next page of data starting after the item with the given ID."""
-
- state: Literal["running", "scoring", "scored", "completed", "canceled", "timeout", "failed"]
- """Filter by Scenario Run state"""
diff --git a/src/runloop_api_client/types/benchmark_run_list_view.py b/src/runloop_api_client/types/benchmark_run_list_view.py
deleted file mode 100644
index efee2176d..000000000
--- a/src/runloop_api_client/types/benchmark_run_list_view.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List, Optional
-
-from .._models import BaseModel
-from .benchmark_run_view import BenchmarkRunView
-
-__all__ = ["BenchmarkRunListView"]
-
-
-class BenchmarkRunListView(BaseModel):
- has_more: bool
-
- runs: List[BenchmarkRunView]
- """List of BenchmarkRuns matching filter."""
-
- total_count: Optional[int] = None
diff --git a/src/runloop_api_client/types/benchmark_run_view.py b/src/runloop_api_client/types/benchmark_run_view.py
deleted file mode 100644
index 5ea3ce15b..000000000
--- a/src/runloop_api_client/types/benchmark_run_view.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Dict, Optional
-from typing_extensions import Literal
-
-from .._models import BaseModel
-
-__all__ = ["BenchmarkRunView"]
-
-
-class BenchmarkRunView(BaseModel):
- """
- A BenchmarkRunView represents a run of a complete set of Scenarios, organized under a Benchmark or created by a BenchmarkJob.
- """
-
- id: str
- """The ID of the BenchmarkRun."""
-
- metadata: Dict[str, str]
- """User defined metadata to attach to the benchmark run for organization."""
-
- start_time_ms: int
- """The time the benchmark run execution started (Unix timestamp milliseconds)."""
-
- state: Literal["running", "canceled", "completed", "failed"]
- """The state of the BenchmarkRun."""
-
- benchmark_id: Optional[str] = None
- """The ID of the Benchmark definition.
-
- Present if run was created from a benchmark definition.
- """
-
- duration_ms: Optional[int] = None
- """The duration for the BenchmarkRun to complete."""
-
- environment_variables: Optional[Dict[str, str]] = None
- """Environment variables used to run the benchmark."""
-
- name: Optional[str] = None
- """The name of the BenchmarkRun."""
-
- purpose: Optional[str] = None
- """Purpose of the run."""
-
- score: Optional[float] = None
- """The final score across the BenchmarkRun, present once completed.
-
- Calculated as sum of scenario scores / number of scenario runs.
- """
-
- secrets_provided: Optional[Dict[str, str]] = None
- """User secrets used to run the benchmark.
-
- Example: {"DB_PASS": "DATABASE_PASSWORD"} would set the environment variable
- 'DB_PASS' on all scenario devboxes to the value of the secret
- 'DATABASE_PASSWORD'.
- """
diff --git a/src/runloop_api_client/types/benchmark_start_run_params.py b/src/runloop_api_client/types/benchmark_start_run_params.py
deleted file mode 100644
index edd65ca7c..000000000
--- a/src/runloop_api_client/types/benchmark_start_run_params.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Dict, Optional
-from typing_extensions import Required, Annotated, TypedDict
-
-from .._utils import PropertyInfo
-from .shared_params.run_profile import RunProfile
-
-__all__ = ["BenchmarkStartRunParams"]
-
-
-# Split into separate params so that OO SDK start_run params can omit the benchmark_id
-# Neither of these params are exposed to the user, only the derived SDKBenchmarkStartRunParams
-class BenchmarkSelfStartRunParams(TypedDict, total=False):
- metadata: Optional[Dict[str, str]]
- """User defined metadata to attach to the benchmark run for organization."""
-
- run_name: Optional[str]
- """Display name of the run."""
-
- run_profile: Annotated[Optional[RunProfile], PropertyInfo(alias="runProfile")]
- """Runtime configuration to use for this benchmark run"""
-
-
-class BenchmarkStartRunParams(BenchmarkSelfStartRunParams, total=False):
- benchmark_id: Required[str]
- """ID of the Benchmark to run."""
diff --git a/src/runloop_api_client/types/benchmark_update_params.py b/src/runloop_api_client/types/benchmark_update_params.py
deleted file mode 100644
index ce9e8fb0c..000000000
--- a/src/runloop_api_client/types/benchmark_update_params.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Dict, Optional
-from typing_extensions import TypedDict
-
-from .._types import SequenceNotStr
-
-__all__ = ["BenchmarkUpdateParams"]
-
-
-class BenchmarkUpdateParams(TypedDict, total=False):
- attribution: Optional[str]
- """Attribution information for the benchmark. Pass in empty string to clear."""
-
- description: Optional[str]
- """Detailed description of the benchmark. Pass in empty string to clear."""
-
- metadata: Optional[Dict[str, str]]
- """User defined metadata to attach to the benchmark. Pass in empty map to clear."""
-
- name: Optional[str]
- """The unique name of the Benchmark. Cannot be blank."""
-
- required_environment_variables: Optional[SequenceNotStr[str]]
- """Environment variables required to run the benchmark.
-
- If any required variables are not supplied, the benchmark will fail to start.
- Pass in empty list to clear.
- """
-
- required_secret_names: Optional[SequenceNotStr[str]]
- """
- Secrets required to run the benchmark with (environment variable name will be
- mapped to the your user secret by name). If any of these secrets are not
- provided or the mapping is incorrect, the benchmark will fail to start. Pass in
- empty list to clear.
- """
-
- scenario_ids: Optional[SequenceNotStr[str]]
- """The Scenario IDs that make up the Benchmark. Pass in empty list to clear."""
diff --git a/src/runloop_api_client/types/benchmark_update_scenarios_params.py b/src/runloop_api_client/types/benchmark_update_scenarios_params.py
deleted file mode 100644
index 2aca2b0d4..000000000
--- a/src/runloop_api_client/types/benchmark_update_scenarios_params.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import TypedDict
-
-from .._types import SequenceNotStr
-
-__all__ = ["BenchmarkUpdateScenariosParams"]
-
-
-class BenchmarkUpdateScenariosParams(TypedDict, total=False):
- scenarios_to_add: Optional[SequenceNotStr[str]]
- """Scenario IDs to add to the Benchmark."""
-
- scenarios_to_remove: Optional[SequenceNotStr[str]]
- """Scenario IDs to remove from the Benchmark."""
diff --git a/src/runloop_api_client/types/benchmark_view.py b/src/runloop_api_client/types/benchmark_view.py
deleted file mode 100644
index 1eb52fb6e..000000000
--- a/src/runloop_api_client/types/benchmark_view.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Dict, List, Optional
-from typing_extensions import Literal
-
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
-
-__all__ = ["BenchmarkView"]
-
-
-class BenchmarkView(BaseModel):
- """
- A BenchmarkDefinitionView represents a grouped set of Scenarios that together form a Benchmark.
- """
-
- id: str
- """The ID of the Benchmark."""
-
- metadata: Dict[str, str]
- """User defined metadata to attach to the benchmark for organization."""
-
- name: str
- """The name of the Benchmark."""
-
- scenario_ids: List[str] = FieldInfo(alias="scenarioIds")
- """List of Scenario IDs that make up the benchmark."""
-
- status: Literal["active", "archived"]
- """Whether the benchmark is active or archived.
-
- Archived benchmarks are excluded from listings and cannot be run.
- """
-
- attribution: Optional[str] = None
- """Attribution information for the benchmark."""
-
- description: Optional[str] = None
- """Detailed description of the benchmark."""
-
- is_public: Optional[bool] = None
- """Whether this benchmark is public."""
-
- required_environment_variables: Optional[List[str]] = None
- """Required environment variables used to run the benchmark.
-
- If any required environment variables are missing, the benchmark will fail to
- start.
- """
-
- required_secret_names: Optional[List[str]] = None
- """Required secrets used to run the benchmark.
-
- If any required secrets are missing, the benchmark will fail to start.
- """
diff --git a/src/runloop_api_client/types/devbox_async_execution_detail_view.py b/src/runloop_api_client/types/devbox_async_execution_detail_view.py
index 5239e4ed2..5ce648936 100755
--- a/src/runloop_api_client/types/devbox_async_execution_detail_view.py
+++ b/src/runloop_api_client/types/devbox_async_execution_detail_view.py
@@ -24,9 +24,6 @@ class DevboxAsyncExecutionDetailView(BaseModel):
This field will remain unset until the execution has completed.
"""
- shell_name: Optional[str] = None
- """Shell name."""
-
stderr: Optional[str] = None
"""Standard error generated by command.
diff --git a/src/runloop_api_client/types/devbox_create_params.py b/src/runloop_api_client/types/devbox_create_params.py
index 765961846..269001c54 100644
--- a/src/runloop_api_client/types/devbox_create_params.py
+++ b/src/runloop_api_client/types/devbox_create_params.py
@@ -117,7 +117,11 @@ class Gateways(TypedDict, total=False):
"""
gateway: Required[str]
- """The gateway config to use. Can be a gateway config ID (gwc_xxx) or name."""
+ """The gateway config to use.
+
+ Can be a gateway config ID (gwc_xxx) or name. A name binds the most recently
+ created config with that name.
+ """
secret: Required[str]
"""The secret containing the credential. Can be a secret ID or name."""
@@ -129,7 +133,11 @@ class Mcp(TypedDict, total=False):
"""
mcp_config: Required[str]
- """The MCP config to use. Can be an MCP config ID (mcp_xxx) or name."""
+ """The MCP config to use.
+
+ Can be an MCP config ID (mcp_xxx) or name. A name binds the most recently
+ created config with that name.
+ """
secret: Required[str]
"""The secret containing the MCP server credential. Can be a secret ID or name."""
@@ -142,7 +150,11 @@ class Tunnel(TypedDict, total=False):
"""
auth_mode: Optional[Literal["open", "authenticated"]]
- """Authentication mode for the tunnel. Defaults to 'public' if not specified."""
+ """Authentication mode for the tunnel.
+
+ Defaults to open if omitted: the hostname is a public capability and does not
+ require a tunnel bearer token.
+ """
http_keep_alive: Optional[bool]
"""
diff --git a/src/runloop_api_client/types/devbox_create_ssh_key_response.py b/src/runloop_api_client/types/devbox_create_ssh_key_response.py
deleted file mode 100755
index 90032165d..000000000
--- a/src/runloop_api_client/types/devbox_create_ssh_key_response.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from .._models import BaseModel
-
-__all__ = ["DevboxCreateSSHKeyResponse"]
-
-
-class DevboxCreateSSHKeyResponse(BaseModel):
- id: str
- """The ID of the Devbox."""
-
- ssh_private_key: str
- """The ssh private key, in PEM format."""
-
- ssh_user: str
- """The Linux user to use for SSH connections to this Devbox."""
-
- url: str
- """The host url of the Devbox that can be used for SSH."""
diff --git a/src/runloop_api_client/types/devbox_enable_tunnel_params.py b/src/runloop_api_client/types/devbox_enable_tunnel_params.py
index 68c5023d8..778ea58d4 100644
--- a/src/runloop_api_client/types/devbox_enable_tunnel_params.py
+++ b/src/runloop_api_client/types/devbox_enable_tunnel_params.py
@@ -10,7 +10,11 @@
class DevboxEnableTunnelParams(TypedDict, total=False):
auth_mode: Optional[Literal["open", "authenticated"]]
- """Authentication mode for the tunnel. Defaults to 'public' if not specified."""
+ """Authentication mode for the tunnel.
+
+ Defaults to open if omitted: the hostname is a public capability and does not
+ require a tunnel bearer token.
+ """
http_keep_alive: Optional[bool]
"""
diff --git a/src/runloop_api_client/types/devbox_execute_async_params.py b/src/runloop_api_client/types/devbox_execute_async_params.py
index 466939809..dcb9cc926 100644
--- a/src/runloop_api_client/types/devbox_execute_async_params.py
+++ b/src/runloop_api_client/types/devbox_execute_async_params.py
@@ -8,26 +8,15 @@
__all__ = ["DevboxExecuteAsyncParams"]
-class DevboxNiceExecuteAsyncParams(TypedDict, total=False):
- attach_stdin: Optional[bool]
- """Whether to attach stdin streaming for async commands.
-
- Not valid for execute_sync endpoint. Defaults to false if not specified.
- """
-
- shell_name: Optional[str]
- """The name of the persistent shell to create or use if already created.
+class DevboxExecuteAsyncParams(TypedDict, total=False):
+ command: Required[str]
+ """The command to execute via the Devbox shell.
- When using a persistent shell, the command will run from the directory at the
- end of the previous command and environment variables will be preserved.
+ Commands are run from the user home directory in a fresh login shell.
"""
+ attach_stdin: Optional[bool]
+ """Whether to attach stdin streaming for async commands.
-class DevboxExecuteAsyncParams(DevboxNiceExecuteAsyncParams, total=False):
- command: Required[str]
- """The command to execute via the Devbox shell.
-
- By default, commands are run from the user home directory unless shell_name is
- specified. If shell_name is specified the command is run from the directory
- based on the recent state of the persistent shell.
+ Not valid for execute_sync endpoint. Defaults to false if not specified.
"""
diff --git a/src/runloop_api_client/types/devbox_execute_params.py b/src/runloop_api_client/types/devbox_execute_params.py
index 69e373e5c..b248ba64e 100644
--- a/src/runloop_api_client/types/devbox_execute_params.py
+++ b/src/runloop_api_client/types/devbox_execute_params.py
@@ -12,9 +12,7 @@ class DevboxExecuteParams(TypedDict, total=False):
command: Required[str]
"""The command to execute via the Devbox shell.
- By default, commands are run from the user home directory unless shell_name is
- specified. If shell_name is specified the command is run from the directory
- based on the recent state of the persistent shell.
+ Commands are run from the user home directory in a fresh login shell.
"""
command_id: Required[str]
@@ -28,10 +26,3 @@ class DevboxExecuteParams(TypedDict, total=False):
Defaults to 25 seconds. Operation is not killed.
"""
-
- shell_name: Optional[str]
- """The name of the persistent shell to create or use if already created.
-
- When using a persistent shell, the command will run from the directory at the
- end of the previous command and environment variables will be preserved.
- """
diff --git a/src/runloop_api_client/types/devbox_execute_sync_params.py b/src/runloop_api_client/types/devbox_execute_sync_params.py
index c911a151c..d701f551b 100644
--- a/src/runloop_api_client/types/devbox_execute_sync_params.py
+++ b/src/runloop_api_client/types/devbox_execute_sync_params.py
@@ -12,9 +12,7 @@ class DevboxExecuteSyncParams(TypedDict, total=False):
command: Required[str]
"""The command to execute via the Devbox shell.
- By default, commands are run from the user home directory unless shell_name is
- specified. If shell_name is specified the command is run from the directory
- based on the recent state of the persistent shell.
+ Commands are run from the user home directory in a fresh login shell.
"""
attach_stdin: Optional[bool]
@@ -22,10 +20,3 @@ class DevboxExecuteSyncParams(TypedDict, total=False):
Not valid for execute_sync endpoint. Defaults to false if not specified.
"""
-
- shell_name: Optional[str]
- """The name of the persistent shell to create or use if already created.
-
- When using a persistent shell, the command will run from the directory at the
- end of the previous command and environment variables will be preserved.
- """
diff --git a/src/runloop_api_client/types/devbox_execution_detail_view.py b/src/runloop_api_client/types/devbox_execution_detail_view.py
index 8c56bf86b..f76c2ca85 100644
--- a/src/runloop_api_client/types/devbox_execution_detail_view.py
+++ b/src/runloop_api_client/types/devbox_execution_detail_view.py
@@ -1,7 +1,5 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
-
from .._models import BaseModel
__all__ = ["DevboxExecutionDetailView"]
@@ -19,6 +17,3 @@ class DevboxExecutionDetailView(BaseModel):
stdout: str
"""Standard out generated by command."""
-
- shell_name: Optional[str] = None
- """Shell name."""
diff --git a/src/runloop_api_client/types/devbox_view.py b/src/runloop_api_client/types/devbox_view.py
index 504d1fe2e..9f2fbd936 100644
--- a/src/runloop_api_client/types/devbox_view.py
+++ b/src/runloop_api_client/types/devbox_view.py
@@ -134,7 +134,7 @@ class DevboxView(BaseModel):
initiator_id: Optional[str] = None
"""The ID of the initiator that created the Devbox."""
- initiator_type: Optional[Literal["unknown", "api", "scenario", "scoring_validation", "reflex"]] = None
+ initiator_type: Optional[Literal["unknown", "api", "reflex"]] = None
"""The type of initiator that created the Devbox."""
mcp_specs: Optional[Dict[str, McpSpecs]] = None
diff --git a/src/runloop_api_client/types/devboxes/devbox_logs_list_view.py b/src/runloop_api_client/types/devboxes/devbox_logs_list_view.py
index fc3a9b6e8..4b1b069b1 100644
--- a/src/runloop_api_client/types/devboxes/devbox_logs_list_view.py
+++ b/src/runloop_api_client/types/devboxes/devbox_logs_list_view.py
@@ -30,9 +30,6 @@ class Log(BaseModel):
message: Optional[str] = None
"""Log line message."""
- shell_name: Optional[str] = None
- """The Shell name the cmd executed in."""
-
class DevboxLogsListView(BaseModel):
logs: List[Log]
diff --git a/src/runloop_api_client/types/devboxes/execution_execute_async_params.py b/src/runloop_api_client/types/devboxes/execution_execute_async_params.py
index e0ae30e53..b7f2825db 100644
--- a/src/runloop_api_client/types/devboxes/execution_execute_async_params.py
+++ b/src/runloop_api_client/types/devboxes/execution_execute_async_params.py
@@ -12,9 +12,7 @@ class ExecutionExecuteAsyncParams(TypedDict, total=False):
command: Required[str]
"""The command to execute via the Devbox shell.
- By default, commands are run from the user home directory unless shell_name is
- specified. If shell_name is specified the command is run from the directory
- based on the recent state of the persistent shell.
+ Commands are run from the user home directory in a fresh login shell.
"""
attach_stdin: Optional[bool]
@@ -22,10 +20,3 @@ class ExecutionExecuteAsyncParams(TypedDict, total=False):
Not valid for execute_sync endpoint. Defaults to false if not specified.
"""
-
- shell_name: Optional[str]
- """The name of the persistent shell to create or use if already created.
-
- When using a persistent shell, the command will run from the directory at the
- end of the previous command and environment variables will be preserved.
- """
diff --git a/src/runloop_api_client/types/devboxes/execution_execute_sync_params.py b/src/runloop_api_client/types/devboxes/execution_execute_sync_params.py
index e0875a1c1..0b8c1cb99 100755
--- a/src/runloop_api_client/types/devboxes/execution_execute_sync_params.py
+++ b/src/runloop_api_client/types/devboxes/execution_execute_sync_params.py
@@ -12,9 +12,7 @@ class ExecutionExecuteSyncParams(TypedDict, total=False):
command: Required[str]
"""The command to execute via the Devbox shell.
- By default, commands are run from the user home directory unless shell_name is
- specified. If shell_name is specified the command is run from the directory
- based on the recent state of the persistent shell.
+ Commands are run from the user home directory in a fresh login shell.
"""
attach_stdin: Optional[bool]
@@ -22,10 +20,3 @@ class ExecutionExecuteSyncParams(TypedDict, total=False):
Not valid for execute_sync endpoint. Defaults to false if not specified.
"""
-
- shell_name: Optional[str]
- """The name of the persistent shell to create or use if already created.
-
- When using a persistent shell, the command will run from the directory at the
- end of the previous command and environment variables will be preserved.
- """
diff --git a/src/runloop_api_client/types/devboxes/log_list_params.py b/src/runloop_api_client/types/devboxes/log_list_params.py
index be52499b2..2a2e4d933 100644
--- a/src/runloop_api_client/types/devboxes/log_list_params.py
+++ b/src/runloop_api_client/types/devboxes/log_list_params.py
@@ -10,6 +10,3 @@
class LogListParams(TypedDict, total=False):
execution_id: str
"""ID of execution to filter logs by."""
-
- shell_name: str
- """Shell Name to filter logs by."""
diff --git a/src/runloop_api_client/types/gateway_config_create_params.py b/src/runloop_api_client/types/gateway_config_create_params.py
index 653a62940..0f9a4e6c6 100644
--- a/src/runloop_api_client/types/gateway_config_create_params.py
+++ b/src/runloop_api_client/types/gateway_config_create_params.py
@@ -24,7 +24,7 @@ class GatewayConfigCreateParams(TypedDict, total=False):
name: Required[str]
"""The human-readable name for the GatewayConfig.
- Must be unique within your account.
+ Names are labels and may be reused.
"""
custom_headers: Optional[Iterable[CustomHeader]]
diff --git a/src/runloop_api_client/types/gateway_config_update_params.py b/src/runloop_api_client/types/gateway_config_update_params.py
index 64078933e..c40b9e2fc 100644
--- a/src/runloop_api_client/types/gateway_config_update_params.py
+++ b/src/runloop_api_client/types/gateway_config_update_params.py
@@ -32,4 +32,4 @@ class GatewayConfigUpdateParams(TypedDict, total=False):
"""New target endpoint URL (e.g., 'https://api.anthropic.com')."""
name: Optional[str]
- """New name for the GatewayConfig. Must be unique within your account."""
+ """New name for the GatewayConfig. Names are labels and may be reused."""
diff --git a/src/runloop_api_client/types/gateway_config_view.py b/src/runloop_api_client/types/gateway_config_view.py
index 7f7cf62d7..587fd3394 100644
--- a/src/runloop_api_client/types/gateway_config_view.py
+++ b/src/runloop_api_client/types/gateway_config_view.py
@@ -27,10 +27,7 @@ class GatewayConfigView(BaseModel):
"""The target endpoint URL (e.g., 'https://api.anthropic.com')."""
name: str
- """The human-readable name of the GatewayConfig.
-
- Unique per account (or globally for system configs).
- """
+ """The human-readable name of the GatewayConfig. Names may be reused."""
account_id: Optional[str] = None
"""The account ID that owns this config."""
diff --git a/src/runloop_api_client/types/input_context.py b/src/runloop_api_client/types/input_context.py
deleted file mode 100644
index 2daae5d45..000000000
--- a/src/runloop_api_client/types/input_context.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Optional
-
-from .._models import BaseModel
-
-__all__ = ["InputContext"]
-
-
-class InputContext(BaseModel):
- """
- InputContextView specifies the problem statement along with all additional context for a Scenario.
- """
-
- problem_statement: str
- """The problem statement for the Scenario."""
-
- additional_context: Optional[object] = None
- """Additional JSON structured input context."""
diff --git a/src/runloop_api_client/types/input_context_param.py b/src/runloop_api_client/types/input_context_param.py
deleted file mode 100644
index b0b495c4d..000000000
--- a/src/runloop_api_client/types/input_context_param.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import Required, TypedDict
-
-__all__ = ["InputContextParam"]
-
-
-class InputContextParam(TypedDict, total=False):
- """
- InputContextView specifies the problem statement along with all additional context for a Scenario.
- """
-
- problem_statement: Required[str]
- """The problem statement for the Scenario."""
-
- additional_context: Optional[object]
- """Additional JSON structured input context."""
diff --git a/src/runloop_api_client/types/input_context_update_param.py b/src/runloop_api_client/types/input_context_update_param.py
deleted file mode 100644
index 30580cf9a..000000000
--- a/src/runloop_api_client/types/input_context_update_param.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import TypedDict
-
-__all__ = ["InputContextUpdateParam"]
-
-
-class InputContextUpdateParam(TypedDict, total=False):
- additional_context: Optional[object]
- """Additional JSON structured input context."""
-
- problem_statement: Optional[str]
- """The problem statement for the Scenario."""
diff --git a/src/runloop_api_client/types/mcp_config_create_params.py b/src/runloop_api_client/types/mcp_config_create_params.py
index cb4164863..300fb5bbb 100644
--- a/src/runloop_api_client/types/mcp_config_create_params.py
+++ b/src/runloop_api_client/types/mcp_config_create_params.py
@@ -26,7 +26,7 @@ class McpConfigCreateParams(TypedDict, total=False):
name: Required[str]
"""The human-readable name for the McpConfig.
- Must be unique within your account. The first segment before '-' is used as the
+ Names are labels and may be reused. The first segment before '-' is used as the
service name for tool routing (e.g., 'github-readonly' uses 'github' as the
service name).
"""
diff --git a/src/runloop_api_client/types/mcp_config_update_params.py b/src/runloop_api_client/types/mcp_config_update_params.py
index e57849953..6f70ac3c9 100644
--- a/src/runloop_api_client/types/mcp_config_update_params.py
+++ b/src/runloop_api_client/types/mcp_config_update_params.py
@@ -39,4 +39,4 @@ class McpConfigUpdateParams(TypedDict, total=False):
"""New target MCP server endpoint URL."""
name: Optional[str]
- """New name for the McpConfig. Must be unique within your account."""
+ """New name for the McpConfig. Names are labels and may be reused."""
diff --git a/src/runloop_api_client/types/mcp_config_view.py b/src/runloop_api_client/types/mcp_config_view.py
index 4dd65503d..a67db009b 100644
--- a/src/runloop_api_client/types/mcp_config_view.py
+++ b/src/runloop_api_client/types/mcp_config_view.py
@@ -33,7 +33,7 @@ class McpConfigView(BaseModel):
"""The target MCP server endpoint URL (e.g., 'https://mcp.example.com')."""
name: str
- """The human-readable name of the McpConfig. Unique per account."""
+ """The human-readable name of the McpConfig. Names may be reused."""
custom_headers: Optional[List[CustomHeader]] = None
"""Additional headers applied to upstream requests after the credential.
diff --git a/src/runloop_api_client/types/network_policy_create_params.py b/src/runloop_api_client/types/network_policy_create_params.py
index 48b49f456..c1cbf4538 100644
--- a/src/runloop_api_client/types/network_policy_create_params.py
+++ b/src/runloop_api_client/types/network_policy_create_params.py
@@ -15,7 +15,7 @@ class NetworkPolicyCreateParams(TypedDict, total=False):
name: Required[str]
"""The human-readable name for the NetworkPolicy.
- Must be unique within the account.
+ Names are labels and may be reused.
"""
allow_agent_gateway: Optional[bool]
@@ -43,12 +43,6 @@ class NetworkPolicyCreateParams(TypedDict, total=False):
Defaults to false.
"""
- allow_runloop_mirrors: Optional[bool]
- """
- (Optional) If true, allows devbox egress to Runloop's package/image registry
- mirrors. Defaults to false. Implicitly allowed when allow_all is true.
- """
-
allowed_cidrs: Optional[Iterable[AllowedCidrParam]]
"""
(Optional) IPv4 CIDR-based allow list with optional port restrictions, additive
diff --git a/src/runloop_api_client/types/network_policy_update_params.py b/src/runloop_api_client/types/network_policy_update_params.py
index 0bfca295f..7f0932346 100644
--- a/src/runloop_api_client/types/network_policy_update_params.py
+++ b/src/runloop_api_client/types/network_policy_update_params.py
@@ -24,12 +24,6 @@ class NetworkPolicyUpdateParams(TypedDict, total=False):
allow_mcp_gateway: Optional[bool]
"""If true, allows devbox egress to the MCP hub."""
- allow_runloop_mirrors: Optional[bool]
- """If true, allows devbox egress to Runloop's package/image registry mirrors.
-
- Implicitly allowed when allow_all is true.
- """
-
allowed_cidrs: Optional[Iterable[AllowedCidrParam]]
"""
Updated IPv4 CIDR-based allow list with optional port restrictions, additive
diff --git a/src/runloop_api_client/types/network_policy_view.py b/src/runloop_api_client/types/network_policy_view.py
index aa6395e5a..9dd6ac50d 100644
--- a/src/runloop_api_client/types/network_policy_view.py
+++ b/src/runloop_api_client/types/network_policy_view.py
@@ -26,12 +26,6 @@ class Egress(BaseModel):
allow_mcp_gateway: bool
"""If true, allows devbox egress to the MCP hub for MCP server access."""
- allow_runloop_mirrors: bool
- """If true, allows devbox egress to Runloop's package/image registry mirrors.
-
- Implicitly allowed when allow_all is true.
- """
-
allowed_cidrs: List[AllowedCidr]
"""
CIDR-based allow list with optional port restrictions, additive with
@@ -62,7 +56,7 @@ class NetworkPolicyView(BaseModel):
"""The egress rules for this policy."""
name: str
- """The human-readable name of the NetworkPolicy. Unique per account."""
+ """The human-readable name of the NetworkPolicy. Names may be reused."""
update_time_ms: int
"""Last update time of the NetworkPolicy (Unix timestamp in milliseconds)."""
diff --git a/src/runloop_api_client/types/object_view.py b/src/runloop_api_client/types/object_view.py
index 6823e2b36..d33a73981 100644
--- a/src/runloop_api_client/types/object_view.py
+++ b/src/runloop_api_client/types/object_view.py
@@ -35,5 +35,11 @@ class ObjectView(BaseModel):
size_bytes: Optional[int] = None
"""The size of the Object content in bytes (null until uploaded)."""
+ upload_headers: Optional[Dict[str, str]] = None
+ """
+ HTTP headers required when uploading to upload_url (only present when needed on
+ create).
+ """
+
upload_url: Optional[str] = None
- """Presigned URL for uploading content to S3 (only present on create)."""
+ """Presigned URL for uploading content with HTTP PUT (only present on create)."""
diff --git a/src/runloop_api_client/types/scenario_create_params.py b/src/runloop_api_client/types/scenario_create_params.py
deleted file mode 100644
index 4249c4d0b..000000000
--- a/src/runloop_api_client/types/scenario_create_params.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Dict, Optional
-from typing_extensions import Literal, Required, TypedDict
-
-from .._types import SequenceNotStr
-from .input_context_param import InputContextParam
-from .scoring_contract_param import ScoringContractParam
-from .scenario_environment_param import ScenarioEnvironmentParam
-
-__all__ = ["ScenarioCreateParams"]
-
-
-class ScenarioCreateParams(TypedDict, total=False):
- input_context: Required[InputContextParam]
- """The input context for the Scenario."""
-
- name: Required[str]
- """Name of the scenario."""
-
- scoring_contract: Required[ScoringContractParam]
- """The scoring contract for the Scenario."""
-
- environment_parameters: Optional[ScenarioEnvironmentParam]
- """
- ScenarioEnvironmentParameters specify the environment in which a Scenario will
- be run.
- """
-
- metadata: Optional[Dict[str, str]]
- """User defined metadata to attach to the scenario for organization."""
-
- reference_output: Optional[str]
- """A string representation of the reference output to solve the scenario.
-
- Commonly can be the result of a git diff or a sequence of command actions to
- apply to the environment.
- """
-
- required_environment_variables: Optional[SequenceNotStr[str]]
- """Environment variables required to run the scenario.
-
- If these variables are not provided, the scenario will fail to start.
- """
-
- required_secret_names: Optional[SequenceNotStr[str]]
- """
- Secrets required to run the scenario (user secret name to scenario required
- secret name). If these secrets are not provided or the mapping is incorrect, the
- scenario will fail to start.
- """
-
- scorer_timeout_sec: Optional[int]
- """Timeout for scoring in seconds. Default 30 minutes (1800s)."""
-
- validation_type: Optional[Literal["UNSPECIFIED", "FORWARD", "REVERSE", "EVALUATION"]]
- """Validation strategy."""
diff --git a/src/runloop_api_client/types/scenario_definition_list_view.py b/src/runloop_api_client/types/scenario_definition_list_view.py
deleted file mode 100644
index 25bad1da5..000000000
--- a/src/runloop_api_client/types/scenario_definition_list_view.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List, Optional
-
-from .._models import BaseModel
-from .scenario_view import ScenarioView
-
-__all__ = ["ScenarioDefinitionListView"]
-
-
-class ScenarioDefinitionListView(BaseModel):
- has_more: bool
-
- scenarios: List[ScenarioView]
- """List of Scenarios matching filter."""
-
- total_count: Optional[int] = None
diff --git a/src/runloop_api_client/types/scenario_environment.py b/src/runloop_api_client/types/scenario_environment.py
deleted file mode 100644
index 5df2d6fb6..000000000
--- a/src/runloop_api_client/types/scenario_environment.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Optional
-
-from .._models import BaseModel
-from .shared.launch_parameters import LaunchParameters
-
-__all__ = ["ScenarioEnvironment"]
-
-
-class ScenarioEnvironment(BaseModel):
- """
- ScenarioEnvironmentParameters specify the environment in which a Scenario will be run.
- """
-
- blueprint_id: Optional[str] = None
- """Use the blueprint with matching ID."""
-
- launch_parameters: Optional[LaunchParameters] = None
- """
- LaunchParameters enable you to customize the resources available to your Devbox
- as well as the environment set up that should be completed before the Devbox is
- marked as 'running'.
- """
-
- snapshot_id: Optional[str] = None
- """Use the snapshot with matching ID."""
-
- working_directory: Optional[str] = None
- """The working directory where the agent is expected to fulfill the scenario.
-
- Scoring functions also run from the working directory.
- """
diff --git a/src/runloop_api_client/types/scenario_environment_param.py b/src/runloop_api_client/types/scenario_environment_param.py
deleted file mode 100644
index 54bb014f1..000000000
--- a/src/runloop_api_client/types/scenario_environment_param.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import TypedDict
-
-from .shared_params.launch_parameters import LaunchParameters
-
-__all__ = ["ScenarioEnvironmentParam"]
-
-
-class ScenarioEnvironmentParam(TypedDict, total=False):
- """
- ScenarioEnvironmentParameters specify the environment in which a Scenario will be run.
- """
-
- blueprint_id: Optional[str]
- """Use the blueprint with matching ID."""
-
- launch_parameters: Optional[LaunchParameters]
- """
- LaunchParameters enable you to customize the resources available to your Devbox
- as well as the environment set up that should be completed before the Devbox is
- marked as 'running'.
- """
-
- snapshot_id: Optional[str]
- """Use the snapshot with matching ID."""
-
- working_directory: Optional[str]
- """The working directory where the agent is expected to fulfill the scenario.
-
- Scoring functions also run from the working directory.
- """
diff --git a/src/runloop_api_client/types/scenario_list_params.py b/src/runloop_api_client/types/scenario_list_params.py
deleted file mode 100644
index cdc21050e..000000000
--- a/src/runloop_api_client/types/scenario_list_params.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import TypedDict
-
-__all__ = ["ScenarioListParams"]
-
-
-class ScenarioListParams(TypedDict, total=False):
- benchmark_id: str
- """Filter scenarios by benchmark ID."""
-
- include_total_count: bool
- """If true (default), includes total_count in the response.
-
- Set to false to skip the count query for better performance on large datasets.
- """
-
- limit: int
- """The limit of items to return. Default is 20. Max is 5000."""
-
- name: str
- """Query for Scenarios with a given name."""
-
- search: str
- """Search by scenario ID or name."""
-
- starting_after: str
- """Load the next page of data starting after the item with the given ID."""
-
- validation_type: str
- """Filter by validation type"""
diff --git a/src/runloop_api_client/types/scenario_list_public_params.py b/src/runloop_api_client/types/scenario_list_public_params.py
deleted file mode 100644
index a1714d8a5..000000000
--- a/src/runloop_api_client/types/scenario_list_public_params.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import TypedDict
-
-__all__ = ["ScenarioListPublicParams"]
-
-
-class ScenarioListPublicParams(TypedDict, total=False):
- include_total_count: bool
- """If true (default), includes total_count in the response.
-
- Set to false to skip the count query for better performance on large datasets.
- """
-
- limit: int
- """The limit of items to return. Default is 20. Max is 5000."""
-
- name: str
- """Query for Scenarios with a given name."""
-
- search: str
- """Search by scenario ID or name."""
-
- starting_after: str
- """Load the next page of data starting after the item with the given ID."""
diff --git a/src/runloop_api_client/types/scenario_run_list_view.py b/src/runloop_api_client/types/scenario_run_list_view.py
deleted file mode 100644
index 6083dbde2..000000000
--- a/src/runloop_api_client/types/scenario_run_list_view.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List, Optional
-
-from .._models import BaseModel
-from .scenario_run_view import ScenarioRunView
-
-__all__ = ["ScenarioRunListView"]
-
-
-class ScenarioRunListView(BaseModel):
- has_more: bool
-
- runs: List[ScenarioRunView]
- """List of ScenarioRuns matching filter."""
-
- total_count: Optional[int] = None
diff --git a/src/runloop_api_client/types/scenario_run_view.py b/src/runloop_api_client/types/scenario_run_view.py
deleted file mode 100644
index 0ff41e17d..000000000
--- a/src/runloop_api_client/types/scenario_run_view.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Dict, Optional
-from typing_extensions import Literal
-
-from .._models import BaseModel
-from .scoring_contract_result_view import ScoringContractResultView
-
-__all__ = ["ScenarioRunView"]
-
-
-class ScenarioRunView(BaseModel):
- """A ScenarioRunView represents a single run of a Scenario on a Devbox.
-
- When completed, the ScenarioRun will contain the final score and output of the run.
- """
-
- id: str
- """ID of the ScenarioRun."""
-
- devbox_id: str
- """ID of the Devbox on which the Scenario is running."""
-
- metadata: Dict[str, str]
- """User defined metadata to attach to the scenario run for organization."""
-
- scenario_id: str
- """ID of the Scenario that has been run."""
-
- state: Literal["running", "scoring", "scored", "completed", "canceled", "timeout", "failed"]
- """The state of the ScenarioRun."""
-
- benchmark_run_id: Optional[str] = None
- """ID of the BenchmarkRun that this Scenario is associated with, if any."""
-
- duration_ms: Optional[int] = None
- """Duration scenario took to run."""
-
- environment_variables: Optional[Dict[str, str]] = None
- """Environment variables used to run the scenario."""
-
- name: Optional[str] = None
- """Optional name of ScenarioRun."""
-
- purpose: Optional[str] = None
- """Purpose of the ScenarioRun."""
-
- scoring_contract_result: Optional[ScoringContractResultView] = None
- """
- A ScoringContractResultView represents the result of running all scoring
- functions on a given input context.
- """
-
- secrets_provided: Optional[Dict[str, str]] = None
- """User secrets used to run the scenario."""
-
- start_time_ms: Optional[int] = None
- """The time that the scenario started"""
diff --git a/src/runloop_api_client/types/scenario_start_run_params.py b/src/runloop_api_client/types/scenario_start_run_params.py
deleted file mode 100644
index b6da90434..000000000
--- a/src/runloop_api_client/types/scenario_start_run_params.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Dict, Optional
-from typing_extensions import Required, Annotated, TypedDict
-
-from .._utils import PropertyInfo
-from .shared_params.run_profile import RunProfile
-
-__all__ = ["ScenarioStartRunParams"]
-
-
-class ScenarioStartRunBaseParams(TypedDict, total=False):
- benchmark_run_id: Optional[str]
- """Benchmark to associate the run."""
-
- metadata: Optional[Dict[str, str]]
- """User defined metadata to attach to the run for organization."""
-
- run_name: Optional[str]
- """Display name of the run."""
-
- run_profile: Annotated[Optional[RunProfile], PropertyInfo(alias="runProfile")]
- """Runtime configuration to use for this benchmark run"""
-
-
-class ScenarioStartRunParams(ScenarioStartRunBaseParams):
- scenario_id: Required[str]
- """ID of the Scenario to run."""
diff --git a/src/runloop_api_client/types/scenario_update_params.py b/src/runloop_api_client/types/scenario_update_params.py
deleted file mode 100644
index e10f885d2..000000000
--- a/src/runloop_api_client/types/scenario_update_params.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Dict, Optional
-from typing_extensions import Literal, TypedDict
-
-from .._types import SequenceNotStr
-from .input_context_update_param import InputContextUpdateParam
-from .scenario_environment_param import ScenarioEnvironmentParam
-from .scoring_contract_update_param import ScoringContractUpdateParam
-
-__all__ = ["ScenarioUpdateParams"]
-
-
-class ScenarioUpdateParams(TypedDict, total=False):
- environment_parameters: Optional[ScenarioEnvironmentParam]
- """
- ScenarioEnvironmentParameters specify the environment in which a Scenario will
- be run.
- """
-
- input_context: Optional[InputContextUpdateParam]
- """The input context for the Scenario."""
-
- metadata: Optional[Dict[str, str]]
- """User defined metadata to attach to the scenario. Pass in empty map to clear."""
-
- name: Optional[str]
- """Name of the scenario. Cannot be blank."""
-
- reference_output: Optional[str]
- """A string representation of the reference output to solve the scenario.
-
- Commonly can be the result of a git diff or a sequence of command actions to
- apply to the environment. Pass in empty string to clear.
- """
-
- required_environment_variables: Optional[SequenceNotStr[str]]
- """Environment variables required to run the scenario.
-
- Pass in empty list to clear.
- """
-
- required_secret_names: Optional[SequenceNotStr[str]]
- """Secrets required to run the scenario. Pass in empty list to clear."""
-
- scorer_timeout_sec: Optional[int]
- """Timeout for scoring in seconds. Default 30 minutes (1800s)."""
-
- scoring_contract: Optional[ScoringContractUpdateParam]
- """The scoring contract for the Scenario."""
-
- validation_type: Optional[Literal["UNSPECIFIED", "FORWARD", "REVERSE", "EVALUATION"]]
- """Validation strategy. Pass in empty string to clear."""
diff --git a/src/runloop_api_client/types/scenario_view.py b/src/runloop_api_client/types/scenario_view.py
deleted file mode 100644
index 69b51ac23..000000000
--- a/src/runloop_api_client/types/scenario_view.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Dict, List, Optional
-from typing_extensions import Literal
-
-from .._models import BaseModel
-from .input_context import InputContext
-from .scoring_contract import ScoringContract
-from .scenario_environment import ScenarioEnvironment
-
-__all__ = ["ScenarioView"]
-
-
-class ScenarioView(BaseModel):
- """
- A ScenarioDefinitionView represents a repeatable AI coding evaluation test, complete with initial environment and scoring contract.
- """
-
- id: str
- """The ID of the Scenario."""
-
- input_context: InputContext
- """The input context for the Scenario."""
-
- metadata: Dict[str, str]
- """User defined metadata to attach to the scenario for organization."""
-
- name: str
- """The name of the Scenario."""
-
- scoring_contract: ScoringContract
- """The scoring contract for the Scenario."""
-
- status: Literal["active", "archived"]
- """Whether the scenario is active or archived.
-
- Archived scenarios are excluded from listings and cannot be updated.
- """
-
- environment: Optional[ScenarioEnvironment] = None
- """
- ScenarioEnvironmentParameters specify the environment in which a Scenario will
- be run.
- """
-
- is_public: Optional[bool] = None
- """Whether this scenario is public."""
-
- reference_output: Optional[str] = None
- """A string representation of the reference output to solve the scenario.
-
- Commonly can be the result of a git diff or a sequence of command actions to
- apply to the environment.
- """
-
- required_environment_variables: Optional[List[str]] = None
- """Environment variables required to run the scenario.
-
- If any required environment variables are missing, the scenario will fail to
- start.
- """
-
- required_secret_names: Optional[List[str]] = None
- """Environment variables required to run the scenario.
-
- If any required secrets are missing, the scenario will fail to start.
- """
-
- scorer_timeout_sec: Optional[int] = None
- """Timeout for scoring in seconds. Default 30 minutes (1800s)."""
-
- validation_type: Optional[Literal["UNSPECIFIED", "FORWARD", "REVERSE", "EVALUATION"]] = None
- """Validation strategy."""
diff --git a/src/runloop_api_client/types/scenarios/__init__.py b/src/runloop_api_client/types/scenarios/__init__.py
deleted file mode 100644
index 97efebd9d..000000000
--- a/src/runloop_api_client/types/scenarios/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from .run_list_params import RunListParams as RunListParams
-from .scorer_list_params import ScorerListParams as ScorerListParams
-from .scorer_create_params import ScorerCreateParams as ScorerCreateParams
-from .scorer_list_response import ScorerListResponse as ScorerListResponse
-from .scorer_update_params import ScorerUpdateParams as ScorerUpdateParams
-from .scorer_create_response import ScorerCreateResponse as ScorerCreateResponse
-from .scorer_update_response import ScorerUpdateResponse as ScorerUpdateResponse
-from .scorer_retrieve_response import ScorerRetrieveResponse as ScorerRetrieveResponse
diff --git a/src/runloop_api_client/types/scenarios/run_list_params.py b/src/runloop_api_client/types/scenarios/run_list_params.py
deleted file mode 100644
index d2de9e951..000000000
--- a/src/runloop_api_client/types/scenarios/run_list_params.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import TypedDict
-
-__all__ = ["RunListParams"]
-
-
-class RunListParams(TypedDict, total=False):
- benchmark_run_id: str
- """Filter by benchmark run ID"""
-
- include_total_count: bool
- """If true (default), includes total_count in the response.
-
- Set to false to skip the count query for better performance on large datasets.
- """
-
- limit: int
- """The limit of items to return. Default is 20. Max is 5000."""
-
- name: str
- """Filter by name"""
-
- scenario_id: str
- """Filter runs associated to Scenario given ID"""
-
- search: str
- """Search by scenario run ID or name."""
-
- starting_after: str
- """Load the next page of data starting after the item with the given ID."""
-
- state: str
- """Filter by state"""
diff --git a/src/runloop_api_client/types/scenarios/scorer_create_params.py b/src/runloop_api_client/types/scenarios/scorer_create_params.py
deleted file mode 100644
index 62a4e33f9..000000000
--- a/src/runloop_api_client/types/scenarios/scorer_create_params.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import Required, TypedDict
-
-__all__ = ["ScorerCreateParams"]
-
-
-class ScorerCreateParams(TypedDict, total=False):
- bash_script: Required[str]
- """
- Bash script for the custom scorer taking context as a json object
- $RL_SCORER_CONTEXT.
- """
-
- type: Required[str]
- """Name of the type of custom scorer."""
diff --git a/src/runloop_api_client/types/scenarios/scorer_create_response.py b/src/runloop_api_client/types/scenarios/scorer_create_response.py
deleted file mode 100644
index 2b6e665a1..000000000
--- a/src/runloop_api_client/types/scenarios/scorer_create_response.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from ..._models import BaseModel
-
-__all__ = ["ScorerCreateResponse"]
-
-
-class ScorerCreateResponse(BaseModel):
- """A ScenarioScorerView represents a custom scoring function for a Scenario."""
-
- id: str
- """ID for the scenario scorer."""
-
- bash_script: str
- """Bash script that takes in $RL_SCORER_CONTEXT as env variable and runs scoring."""
-
- type: str
- """Name of the type of scenario scorer."""
diff --git a/src/runloop_api_client/types/scenarios/scorer_list_params.py b/src/runloop_api_client/types/scenarios/scorer_list_params.py
deleted file mode 100644
index 4ae433725..000000000
--- a/src/runloop_api_client/types/scenarios/scorer_list_params.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import TypedDict
-
-__all__ = ["ScorerListParams"]
-
-
-class ScorerListParams(TypedDict, total=False):
- include_total_count: bool
- """If true (default), includes total_count in the response.
-
- Set to false to skip the count query for better performance on large datasets.
- """
-
- limit: int
- """The limit of items to return. Default is 20. Max is 5000."""
-
- search: str
- """Search by scenario scorer ID or type."""
-
- starting_after: str
- """Load the next page of data starting after the item with the given ID."""
diff --git a/src/runloop_api_client/types/scenarios/scorer_list_response.py b/src/runloop_api_client/types/scenarios/scorer_list_response.py
deleted file mode 100644
index 46eb8802e..000000000
--- a/src/runloop_api_client/types/scenarios/scorer_list_response.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from ..._models import BaseModel
-
-__all__ = ["ScorerListResponse"]
-
-
-class ScorerListResponse(BaseModel):
- """A ScenarioScorerView represents a custom scoring function for a Scenario."""
-
- id: str
- """ID for the scenario scorer."""
-
- bash_script: str
- """Bash script that takes in $RL_SCORER_CONTEXT as env variable and runs scoring."""
-
- type: str
- """Name of the type of scenario scorer."""
diff --git a/src/runloop_api_client/types/scenarios/scorer_retrieve_response.py b/src/runloop_api_client/types/scenarios/scorer_retrieve_response.py
deleted file mode 100644
index a67cd35c0..000000000
--- a/src/runloop_api_client/types/scenarios/scorer_retrieve_response.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from ..._models import BaseModel
-
-__all__ = ["ScorerRetrieveResponse"]
-
-
-class ScorerRetrieveResponse(BaseModel):
- """A ScenarioScorerView represents a custom scoring function for a Scenario."""
-
- id: str
- """ID for the scenario scorer."""
-
- bash_script: str
- """Bash script that takes in $RL_SCORER_CONTEXT as env variable and runs scoring."""
-
- type: str
- """Name of the type of scenario scorer."""
diff --git a/src/runloop_api_client/types/scenarios/scorer_update_params.py b/src/runloop_api_client/types/scenarios/scorer_update_params.py
deleted file mode 100644
index dcc7816a4..000000000
--- a/src/runloop_api_client/types/scenarios/scorer_update_params.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import Required, TypedDict
-
-__all__ = ["ScorerUpdateParams"]
-
-
-class ScorerUpdateParams(TypedDict, total=False):
- bash_script: Required[str]
- """
- Bash script for the custom scorer taking context as a json object
- $RL_SCORER_CONTEXT.
- """
-
- type: Required[str]
- """Name of the type of custom scorer."""
diff --git a/src/runloop_api_client/types/scenarios/scorer_update_response.py b/src/runloop_api_client/types/scenarios/scorer_update_response.py
deleted file mode 100644
index 91e668d22..000000000
--- a/src/runloop_api_client/types/scenarios/scorer_update_response.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from ..._models import BaseModel
-
-__all__ = ["ScorerUpdateResponse"]
-
-
-class ScorerUpdateResponse(BaseModel):
- """A ScenarioScorerView represents a custom scoring function for a Scenario."""
-
- id: str
- """ID for the scenario scorer."""
-
- bash_script: str
- """Bash script that takes in $RL_SCORER_CONTEXT as env variable and runs scoring."""
-
- type: str
- """Name of the type of scenario scorer."""
diff --git a/src/runloop_api_client/types/scope_entry_view.py b/src/runloop_api_client/types/scope_entry_view.py
index 00e7f9fff..679705c31 100644
--- a/src/runloop_api_client/types/scope_entry_view.py
+++ b/src/runloop_api_client/types/scope_entry_view.py
@@ -16,9 +16,6 @@ class ScopeEntryView(BaseModel):
"RESOURCE_TYPE_DEVBOXES",
"RESOURCE_TYPE_BLUEPRINTS",
"RESOURCE_TYPE_SNAPSHOTS",
- "RESOURCE_TYPE_BENCHMARKS",
- "RESOURCE_TYPE_SCENARIOS",
- "RESOURCE_TYPE_AGENTS",
"RESOURCE_TYPE_OBJECTS",
"RESOURCE_TYPE_ACCOUNT",
]
diff --git a/src/runloop_api_client/types/scope_entry_view_param.py b/src/runloop_api_client/types/scope_entry_view_param.py
index 47b6cac2d..8dfcdcf6a 100644
--- a/src/runloop_api_client/types/scope_entry_view_param.py
+++ b/src/runloop_api_client/types/scope_entry_view_param.py
@@ -14,9 +14,6 @@ class ScopeEntryViewParam(TypedDict, total=False):
"RESOURCE_TYPE_DEVBOXES",
"RESOURCE_TYPE_BLUEPRINTS",
"RESOURCE_TYPE_SNAPSHOTS",
- "RESOURCE_TYPE_BENCHMARKS",
- "RESOURCE_TYPE_SCENARIOS",
- "RESOURCE_TYPE_AGENTS",
"RESOURCE_TYPE_OBJECTS",
"RESOURCE_TYPE_ACCOUNT",
]
diff --git a/src/runloop_api_client/types/scoring_contract.py b/src/runloop_api_client/types/scoring_contract.py
deleted file mode 100644
index d3c646fda..000000000
--- a/src/runloop_api_client/types/scoring_contract.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List
-
-from .._models import BaseModel
-from .scoring_function import ScoringFunction
-
-__all__ = ["ScoringContract"]
-
-
-class ScoringContract(BaseModel):
- """
- InputContextView specifies the problem statement along with all additional context for a Scenario.
- """
-
- scoring_function_parameters: List[ScoringFunction]
- """A list of scoring functions used to evaluate the Scenario."""
diff --git a/src/runloop_api_client/types/scoring_contract_param.py b/src/runloop_api_client/types/scoring_contract_param.py
deleted file mode 100644
index 46f1b7b6d..000000000
--- a/src/runloop_api_client/types/scoring_contract_param.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Iterable
-from typing_extensions import Required, TypedDict
-
-from .scoring_function_param import ScoringFunctionParam
-
-__all__ = ["ScoringContractParam"]
-
-
-class ScoringContractParam(TypedDict, total=False):
- """
- InputContextView specifies the problem statement along with all additional context for a Scenario.
- """
-
- scoring_function_parameters: Required[Iterable[ScoringFunctionParam]]
- """A list of scoring functions used to evaluate the Scenario."""
diff --git a/src/runloop_api_client/types/scoring_contract_result_view.py b/src/runloop_api_client/types/scoring_contract_result_view.py
deleted file mode 100644
index 85e1a42c4..000000000
--- a/src/runloop_api_client/types/scoring_contract_result_view.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List
-
-from .._models import BaseModel
-from .scoring_function_result_view import ScoringFunctionResultView
-
-__all__ = ["ScoringContractResultView"]
-
-
-class ScoringContractResultView(BaseModel):
- """
- A ScoringContractResultView represents the result of running all scoring functions on a given input context.
- """
-
- score: float
- """Total score for all scoring contracts. This will be a value between 0 and 1."""
-
- scoring_function_results: List[ScoringFunctionResultView]
- """List of all individual scoring function results."""
diff --git a/src/runloop_api_client/types/scoring_contract_update_param.py b/src/runloop_api_client/types/scoring_contract_update_param.py
deleted file mode 100644
index ce2c85289..000000000
--- a/src/runloop_api_client/types/scoring_contract_update_param.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Iterable, Optional
-from typing_extensions import TypedDict
-
-from .scoring_function_param import ScoringFunctionParam
-
-__all__ = ["ScoringContractUpdateParam"]
-
-
-class ScoringContractUpdateParam(TypedDict, total=False):
- scoring_function_parameters: Optional[Iterable[ScoringFunctionParam]]
- """A list of scoring functions used to evaluate the Scenario."""
diff --git a/src/runloop_api_client/types/scoring_function.py b/src/runloop_api_client/types/scoring_function.py
deleted file mode 100644
index fe5d2a467..000000000
--- a/src/runloop_api_client/types/scoring_function.py
+++ /dev/null
@@ -1,157 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List, Union, Optional
-from typing_extensions import Literal, Annotated, TypeAlias
-
-from .._utils import PropertyInfo
-from .._models import BaseModel
-
-__all__ = [
- "ScoringFunction",
- "Scorer",
- "ScorerAstGrepScoringFunction",
- "ScorerBashScriptScoringFunction",
- "ScorerCommandScoringFunction",
- "ScorerCustomScoringFunction",
- "ScorerPythonScriptScoringFunction",
- "ScorerTestBasedScoringFunction",
- "ScorerTestBasedScoringFunctionTestFile",
-]
-
-
-class ScorerAstGrepScoringFunction(BaseModel):
- """AstGrepScoringFunction utilizes structured coach search for scoring."""
-
- pattern: str
- """AST pattern to match.
-
- Pattern will be passed to ast-grep using the commandline surround by double
- quotes ("), so make sure to use proper escaping (for example, \\$$\\$$\\$$).
- """
-
- search_directory: str
- """The path to search."""
-
- type: Literal["ast_grep_scorer"]
-
- lang: Optional[str] = None
- """The language of the pattern."""
-
-
-class ScorerBashScriptScoringFunction(BaseModel):
- """
- BashScriptScoringFunction is a scoring function specified by a bash script that will be run in the context of your environment.
- """
-
- type: Literal["bash_script_scorer"]
-
- bash_script: Optional[str] = None
- """
- A single bash script that sets up the environment, scores, and prints the final
- score to standard out. Score should be a float between 0.0 and 1.0, and look
- like "score=[0.0..1.0].
- """
-
-
-class ScorerCommandScoringFunction(BaseModel):
- """
- CommandScoringFunction executes a single command and checks the result.The output of the command will be printed. Scoring will passed if the command returns status code 0, otherwise it will be failed.
- """
-
- type: Literal["command_scorer"]
-
- command: Optional[str] = None
- """The command to execute."""
-
-
-class ScorerCustomScoringFunction(BaseModel):
- """CustomScoringFunction is a custom, user defined scoring function."""
-
- custom_scorer_type: str
- """Type of the scoring function, previously registered with Runloop."""
-
- type: Literal["custom_scorer"]
-
- scorer_params: Optional[object] = None
- """Additional JSON structured context to pass to the scoring function."""
-
-
-class ScorerPythonScriptScoringFunction(BaseModel):
- """
- PythonScriptScoringFunction will run a python script in the context of your environment as a ScoringFunction.
- """
-
- python_script: str
- """Python script to be run.
-
- The script should output the score to standard out as a float between 0.0 and
- 1.0.
- """
-
- type: Literal["python_script_scorer"]
-
- python_version_constraint: Optional[str] = None
- """Python version to run scoring. Default is "==3.12.10" """
-
- requirements_contents: Optional[str] = None
- """Package dependencies to be installed.
-
- The requirements should be a valid requirements.txt file.
- """
-
-
-class ScorerTestBasedScoringFunctionTestFile(BaseModel):
- file_contents: Optional[str] = None
- """Content of the test file"""
-
- file_path: Optional[str] = None
- """
- Path to write content of the test file, relative to your environment's working
- directory
- """
-
-
-class ScorerTestBasedScoringFunction(BaseModel):
- """
- TestBasedScoringFunction writes test files to disk and executes a test command to verify the solution.
- """
-
- type: Literal["test_based_scorer"]
-
- test_command: Optional[str] = None
- """The command to execute for running the tests"""
-
- test_files: Optional[List[ScorerTestBasedScoringFunctionTestFile]] = None
- """List of test files to create"""
-
-
-Scorer: TypeAlias = Annotated[
- Union[
- ScorerAstGrepScoringFunction,
- ScorerBashScriptScoringFunction,
- ScorerCommandScoringFunction,
- ScorerCustomScoringFunction,
- ScorerPythonScriptScoringFunction,
- ScorerTestBasedScoringFunction,
- ],
- PropertyInfo(discriminator="type"),
-]
-
-
-class ScoringFunction(BaseModel):
- """ScoringFunction specifies a method of scoring a Scenario."""
-
- name: str
- """Name of scoring function. Names must only contain ``[a-zA-Z0-9_-]``."""
-
- scorer: Scorer
- """The scoring function to use for evaluating this scenario.
-
- The type field determines which built-in function to use.
- """
-
- weight: float
- """Weight to apply to scoring function score.
-
- Weights of all scoring functions should sum to 1.0.
- """
diff --git a/src/runloop_api_client/types/scoring_function_param.py b/src/runloop_api_client/types/scoring_function_param.py
deleted file mode 100644
index 033101d52..000000000
--- a/src/runloop_api_client/types/scoring_function_param.py
+++ /dev/null
@@ -1,153 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Union, Iterable, Optional
-from typing_extensions import Literal, Required, TypeAlias, TypedDict
-
-__all__ = [
- "ScoringFunctionParam",
- "Scorer",
- "ScorerAstGrepScoringFunction",
- "ScorerBashScriptScoringFunction",
- "ScorerCommandScoringFunction",
- "ScorerCustomScoringFunction",
- "ScorerPythonScriptScoringFunction",
- "ScorerTestBasedScoringFunction",
- "ScorerTestBasedScoringFunctionTestFile",
-]
-
-
-class ScorerAstGrepScoringFunction(TypedDict, total=False):
- """AstGrepScoringFunction utilizes structured coach search for scoring."""
-
- pattern: Required[str]
- """AST pattern to match.
-
- Pattern will be passed to ast-grep using the commandline surround by double
- quotes ("), so make sure to use proper escaping (for example, \\$$\\$$\\$$).
- """
-
- search_directory: Required[str]
- """The path to search."""
-
- type: Required[Literal["ast_grep_scorer"]]
-
- lang: str
- """The language of the pattern."""
-
-
-class ScorerBashScriptScoringFunction(TypedDict, total=False):
- """
- BashScriptScoringFunction is a scoring function specified by a bash script that will be run in the context of your environment.
- """
-
- type: Required[Literal["bash_script_scorer"]]
-
- bash_script: str
- """
- A single bash script that sets up the environment, scores, and prints the final
- score to standard out. Score should be a float between 0.0 and 1.0, and look
- like "score=[0.0..1.0].
- """
-
-
-class ScorerCommandScoringFunction(TypedDict, total=False):
- """
- CommandScoringFunction executes a single command and checks the result.The output of the command will be printed. Scoring will passed if the command returns status code 0, otherwise it will be failed.
- """
-
- type: Required[Literal["command_scorer"]]
-
- command: str
- """The command to execute."""
-
-
-class ScorerCustomScoringFunction(TypedDict, total=False):
- """CustomScoringFunction is a custom, user defined scoring function."""
-
- custom_scorer_type: Required[str]
- """Type of the scoring function, previously registered with Runloop."""
-
- type: Required[Literal["custom_scorer"]]
-
- scorer_params: Optional[object]
- """Additional JSON structured context to pass to the scoring function."""
-
-
-class ScorerPythonScriptScoringFunction(TypedDict, total=False):
- """
- PythonScriptScoringFunction will run a python script in the context of your environment as a ScoringFunction.
- """
-
- python_script: Required[str]
- """Python script to be run.
-
- The script should output the score to standard out as a float between 0.0 and
- 1.0.
- """
-
- type: Required[Literal["python_script_scorer"]]
-
- python_version_constraint: Optional[str]
- """Python version to run scoring. Default is "==3.12.10" """
-
- requirements_contents: Optional[str]
- """Package dependencies to be installed.
-
- The requirements should be a valid requirements.txt file.
- """
-
-
-class ScorerTestBasedScoringFunctionTestFile(TypedDict, total=False):
- file_contents: str
- """Content of the test file"""
-
- file_path: str
- """
- Path to write content of the test file, relative to your environment's working
- directory
- """
-
-
-class ScorerTestBasedScoringFunction(TypedDict, total=False):
- """
- TestBasedScoringFunction writes test files to disk and executes a test command to verify the solution.
- """
-
- type: Required[Literal["test_based_scorer"]]
-
- test_command: str
- """The command to execute for running the tests"""
-
- test_files: Iterable[ScorerTestBasedScoringFunctionTestFile]
- """List of test files to create"""
-
-
-Scorer: TypeAlias = Union[
- ScorerAstGrepScoringFunction,
- ScorerBashScriptScoringFunction,
- ScorerCommandScoringFunction,
- ScorerCustomScoringFunction,
- ScorerPythonScriptScoringFunction,
- ScorerTestBasedScoringFunction,
-]
-
-
-class ScoringFunctionParam(TypedDict, total=False):
- """ScoringFunction specifies a method of scoring a Scenario."""
-
- name: Required[str]
- """Name of scoring function. Names must only contain ``[a-zA-Z0-9_-]``."""
-
- scorer: Required[Scorer]
- """The scoring function to use for evaluating this scenario.
-
- The type field determines which built-in function to use.
- """
-
- weight: Required[float]
- """Weight to apply to scoring function score.
-
- Weights of all scoring functions should sum to 1.0.
- """
diff --git a/src/runloop_api_client/types/scoring_function_result_view.py b/src/runloop_api_client/types/scoring_function_result_view.py
deleted file mode 100644
index 4fe5b67cb..000000000
--- a/src/runloop_api_client/types/scoring_function_result_view.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing_extensions import Literal
-
-from .._models import BaseModel
-
-__all__ = ["ScoringFunctionResultView"]
-
-
-class ScoringFunctionResultView(BaseModel):
- """
- A ScoringFunctionResultView represents the result of running a single scoring function on a given input context.
- """
-
- output: str
- """Log output of the scoring function."""
-
- score: float
- """Final score for the given scoring function."""
-
- scoring_function_name: str
- """Scoring function name that ran."""
-
- state: Literal["unknown", "complete", "error"]
- """The state of the scoring function application."""
diff --git a/src/runloop_api_client/types/secret_create_params.py b/src/runloop_api_client/types/secret_create_params.py
index 5d5234b56..14bb1b0c7 100644
--- a/src/runloop_api_client/types/secret_create_params.py
+++ b/src/runloop_api_client/types/secret_create_params.py
@@ -9,7 +9,7 @@
class SecretCreateParams(TypedDict, total=False):
name: Required[str]
- """The globally unique name for the Secret.
+ """The name for the Secret.
Must be a valid environment variable name (alphanumeric and underscores only).
Example: 'DATABASE_PASSWORD'
diff --git a/src/runloop_api_client/types/secret_update_by_id_params.py b/src/runloop_api_client/types/secret_update_by_id_params.py
new file mode 100644
index 000000000..5f22395e4
--- /dev/null
+++ b/src/runloop_api_client/types/secret_update_by_id_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["SecretUpdateByIDParams"]
+
+
+class SecretUpdateByIDParams(TypedDict, total=False):
+ value: Required[str]
+ """The new value for the Secret.
+
+ This will replace the existing value and be encrypted at rest. Example:
+ 'my-updated-secure-password'
+ """
diff --git a/src/runloop_api_client/types/secret_view.py b/src/runloop_api_client/types/secret_view.py
index bd1c8811e..3ca5ee161 100644
--- a/src/runloop_api_client/types/secret_view.py
+++ b/src/runloop_api_client/types/secret_view.py
@@ -17,10 +17,7 @@ class SecretView(BaseModel):
"""Creation time of the Secret (Unix timestamp in milliseconds)."""
name: str
- """The globally unique name of the Secret.
-
- Used as the environment variable name in Devboxes.
- """
+ """The name of the Secret. Used as the environment variable name in Devboxes."""
update_time_ms: int
"""Last update time of the Secret (Unix timestamp in milliseconds)."""
diff --git a/src/runloop_api_client/types/shared/__init__.py b/src/runloop_api_client/types/shared/__init__.py
index d2276afb6..4cb085210 100644
--- a/src/runloop_api_client/types/shared/__init__.py
+++ b/src/runloop_api_client/types/shared/__init__.py
@@ -2,9 +2,6 @@
from .mount import Mount as Mount
from .after_idle import AfterIdle as AfterIdle
-from .agent_mount import AgentMount as AgentMount
-from .run_profile import RunProfile as RunProfile
-from .agent_source import AgentSource as AgentSource
from .broker_mount import BrokerMount as BrokerMount
from .object_mount import ObjectMount as ObjectMount
from .custom_header import CustomHeader as CustomHeader
diff --git a/src/runloop_api_client/types/shared/agent_mount.py b/src/runloop_api_client/types/shared/agent_mount.py
deleted file mode 100644
index cdb196dd5..000000000
--- a/src/runloop_api_client/types/shared/agent_mount.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Optional
-from typing_extensions import Literal
-
-from ..._models import BaseModel
-
-__all__ = ["AgentMount"]
-
-
-class AgentMount(BaseModel):
- agent_id: Optional[str] = None
- """The ID of the agent to mount. Either agent_id or name must be set."""
-
- agent_name: Optional[str] = None
- """The name of the agent to mount.
-
- Returns the most recent agent with a matching name if no agent id string
- provided. Either agent id or name must be set
- """
-
- type: Literal["agent_mount"]
-
- agent_path: Optional[str] = None
- """Path to mount the agent on the Devbox.
-
- Required for git and object agents. Use absolute path (e.g., /home/user/agent)
- """
-
- auth_token: Optional[str] = None
- """Optional auth token for private repositories. Only used for git agents."""
diff --git a/src/runloop_api_client/types/shared/agent_source.py b/src/runloop_api_client/types/shared/agent_source.py
deleted file mode 100644
index 96df82cef..000000000
--- a/src/runloop_api_client/types/shared/agent_source.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List, Optional
-
-from ..._models import BaseModel
-
-__all__ = ["AgentSource", "Git", "Npm", "Object", "Pip"]
-
-
-class Git(BaseModel):
- """Git-based agent source configuration."""
-
- repository: str
- """Git repository URL"""
-
- agent_setup: Optional[List[str]] = None
- """Setup commands to run after cloning"""
-
- ref: Optional[str] = None
- """Optional Git ref (branch/tag/commit), defaults to main/HEAD"""
-
-
-class Npm(BaseModel):
- """NPM-based agent source configuration."""
-
- package_name: str
- """NPM package name"""
-
- agent_setup: Optional[List[str]] = None
- """Setup commands to run after installation"""
-
- registry_url: Optional[str] = None
- """NPM registry URL"""
-
-
-class Object(BaseModel):
- """Object store agent source configuration."""
-
- object_id: str
- """Object ID"""
-
- agent_setup: Optional[List[str]] = None
- """Setup commands to run after unpacking"""
-
-
-class Pip(BaseModel):
- """Pip-based agent source configuration."""
-
- package_name: str
- """Pip package name"""
-
- agent_setup: Optional[List[str]] = None
- """Setup commands to run after installation"""
-
- registry_url: Optional[str] = None
- """Pip registry URL"""
-
-
-class AgentSource(BaseModel):
- """Agent source configuration."""
-
- type: str
- """Source type: npm, pip, object, or git"""
-
- git: Optional[Git] = None
- """Git-based agent source configuration."""
-
- npm: Optional[Npm] = None
- """NPM-based agent source configuration."""
-
- object: Optional[Object] = None
- """Object store agent source configuration."""
-
- pip: Optional[Pip] = None
- """Pip-based agent source configuration."""
diff --git a/src/runloop_api_client/types/shared/launch_parameters.py b/src/runloop_api_client/types/shared/launch_parameters.py
index c5b412720..a11645e2f 100644
--- a/src/runloop_api_client/types/shared/launch_parameters.py
+++ b/src/runloop_api_client/types/shared/launch_parameters.py
@@ -42,7 +42,7 @@ class LaunchParameters(BaseModel):
This field is ignored.
"""
- custom_cpu_cores: Optional[int] = None
+ custom_cpu_cores: Optional[float] = None
"""Custom CPU cores. Must be 0.5, 1, or a multiple of 2. Max is 16."""
custom_disk_size: Optional[int] = None
diff --git a/src/runloop_api_client/types/shared/mount.py b/src/runloop_api_client/types/shared/mount.py
index 80b57f8c0..470080a82 100644
--- a/src/runloop_api_client/types/shared/mount.py
+++ b/src/runloop_api_client/types/shared/mount.py
@@ -5,7 +5,6 @@
from ..._utils import PropertyInfo
from ..._models import BaseModel
-from .agent_mount import AgentMount
from .broker_mount import BrokerMount
from .object_mount import ObjectMount
@@ -47,6 +46,4 @@ class FileMount(BaseModel):
type: Literal["file_mount"]
-Mount: TypeAlias = Annotated[
- Union[ObjectMount, AgentMount, CodeMount, FileMount, BrokerMount], PropertyInfo(discriminator="type")
-]
+Mount: TypeAlias = Annotated[Union[ObjectMount, CodeMount, FileMount, BrokerMount], PropertyInfo(discriminator="type")]
diff --git a/src/runloop_api_client/types/shared/run_profile.py b/src/runloop_api_client/types/shared/run_profile.py
deleted file mode 100644
index a7b1349f3..000000000
--- a/src/runloop_api_client/types/shared/run_profile.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Dict, List, Optional
-
-from pydantic import Field as FieldInfo
-
-from .mount import Mount
-from ..._models import BaseModel
-from .launch_parameters import LaunchParameters
-
-__all__ = ["RunProfile"]
-
-
-class RunProfile(BaseModel):
- env_vars: Optional[Dict[str, str]] = FieldInfo(alias="envVars", default=None)
- """Mapping of Environment Variable to Value.
-
- May be shown in devbox logging. Example: {"DB_PASS": "DATABASE_PASSWORD"} would
- set the environment variable 'DB_PASS' to the value 'DATABASE_PASSWORD_VALUE'.
- """
-
- launch_parameters: Optional[LaunchParameters] = FieldInfo(alias="launchParameters", default=None)
- """
- LaunchParameters enable you to customize the resources available to your Devbox
- as well as the environment set up that should be completed before the Devbox is
- marked as 'running'.
- """
-
- mounts: Optional[List[Mount]] = None
- """A list of mounts to be included in the scenario run."""
-
- purpose: Optional[str] = None
- """Purpose of the run."""
-
- secrets: Optional[Dict[str, str]] = None
- """Mapping of Environment Variable to User Secret Name.
-
- Never shown in devbox logging. Example: {"DB_PASS": "DATABASE_PASSWORD"} would
- set the environment variable 'DB_PASS' to the value of the secret
- 'DATABASE_PASSWORD'.
- """
diff --git a/src/runloop_api_client/types/shared_params/__init__.py b/src/runloop_api_client/types/shared_params/__init__.py
index d2276afb6..4cb085210 100644
--- a/src/runloop_api_client/types/shared_params/__init__.py
+++ b/src/runloop_api_client/types/shared_params/__init__.py
@@ -2,9 +2,6 @@
from .mount import Mount as Mount
from .after_idle import AfterIdle as AfterIdle
-from .agent_mount import AgentMount as AgentMount
-from .run_profile import RunProfile as RunProfile
-from .agent_source import AgentSource as AgentSource
from .broker_mount import BrokerMount as BrokerMount
from .object_mount import ObjectMount as ObjectMount
from .custom_header import CustomHeader as CustomHeader
diff --git a/src/runloop_api_client/types/shared_params/agent_mount.py b/src/runloop_api_client/types/shared_params/agent_mount.py
deleted file mode 100644
index 0c83a5fd7..000000000
--- a/src/runloop_api_client/types/shared_params/agent_mount.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import Literal, Required, TypedDict
-
-__all__ = ["AgentMount"]
-
-
-class AgentMount(TypedDict, total=False):
- agent_id: Required[Optional[str]]
- """The ID of the agent to mount. Either agent_id or name must be set."""
-
- agent_name: Required[Optional[str]]
- """The name of the agent to mount.
-
- Returns the most recent agent with a matching name if no agent id string
- provided. Either agent id or name must be set
- """
-
- type: Required[Literal["agent_mount"]]
-
- agent_path: Optional[str]
- """Path to mount the agent on the Devbox.
-
- Required for git and object agents. Use absolute path (e.g., /home/user/agent)
- """
-
- auth_token: Optional[str]
- """Optional auth token for private repositories. Only used for git agents."""
diff --git a/src/runloop_api_client/types/shared_params/agent_source.py b/src/runloop_api_client/types/shared_params/agent_source.py
deleted file mode 100644
index 14ac68ec0..000000000
--- a/src/runloop_api_client/types/shared_params/agent_source.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import Required, TypedDict
-
-from ..._types import SequenceNotStr
-
-__all__ = ["AgentSource", "Git", "Npm", "Object", "Pip"]
-
-
-class Git(TypedDict, total=False):
- """Git-based agent source configuration."""
-
- repository: Required[str]
- """Git repository URL"""
-
- agent_setup: Optional[SequenceNotStr[str]]
- """Setup commands to run after cloning"""
-
- ref: Optional[str]
- """Optional Git ref (branch/tag/commit), defaults to main/HEAD"""
-
-
-class Npm(TypedDict, total=False):
- """NPM-based agent source configuration."""
-
- package_name: Required[str]
- """NPM package name"""
-
- agent_setup: Optional[SequenceNotStr[str]]
- """Setup commands to run after installation"""
-
- registry_url: Optional[str]
- """NPM registry URL"""
-
-
-class Object(TypedDict, total=False):
- """Object store agent source configuration."""
-
- object_id: Required[str]
- """Object ID"""
-
- agent_setup: Optional[SequenceNotStr[str]]
- """Setup commands to run after unpacking"""
-
-
-class Pip(TypedDict, total=False):
- """Pip-based agent source configuration."""
-
- package_name: Required[str]
- """Pip package name"""
-
- agent_setup: Optional[SequenceNotStr[str]]
- """Setup commands to run after installation"""
-
- registry_url: Optional[str]
- """Pip registry URL"""
-
-
-class AgentSource(TypedDict, total=False):
- """Agent source configuration."""
-
- type: Required[str]
- """Source type: npm, pip, object, or git"""
-
- git: Optional[Git]
- """Git-based agent source configuration."""
-
- npm: Optional[Npm]
- """NPM-based agent source configuration."""
-
- object: Optional[Object]
- """Object store agent source configuration."""
-
- pip: Optional[Pip]
- """Pip-based agent source configuration."""
diff --git a/src/runloop_api_client/types/shared_params/launch_parameters.py b/src/runloop_api_client/types/shared_params/launch_parameters.py
index 1fe21fe9a..b29e7e223 100644
--- a/src/runloop_api_client/types/shared_params/launch_parameters.py
+++ b/src/runloop_api_client/types/shared_params/launch_parameters.py
@@ -44,7 +44,7 @@ class LaunchParameters(TypedDict, total=False):
This field is ignored.
"""
- custom_cpu_cores: Optional[int]
+ custom_cpu_cores: Optional[float]
"""Custom CPU cores. Must be 0.5, 1, or a multiple of 2. Max is 16."""
custom_disk_size: Optional[int]
diff --git a/src/runloop_api_client/types/shared_params/mount.py b/src/runloop_api_client/types/shared_params/mount.py
index cafbeedbd..4839f77e4 100644
--- a/src/runloop_api_client/types/shared_params/mount.py
+++ b/src/runloop_api_client/types/shared_params/mount.py
@@ -5,7 +5,6 @@
from typing import Union, Optional
from typing_extensions import Literal, Required, TypeAlias, TypedDict
-from .agent_mount import AgentMount
from .broker_mount import BrokerMount
from .object_mount import ObjectMount
@@ -47,4 +46,4 @@ class FileMount(TypedDict, total=False):
type: Required[Literal["file_mount"]]
-Mount: TypeAlias = Union[ObjectMount, AgentMount, CodeMount, FileMount, BrokerMount]
+Mount: TypeAlias = Union[ObjectMount, CodeMount, FileMount, BrokerMount]
diff --git a/src/runloop_api_client/types/shared_params/run_profile.py b/src/runloop_api_client/types/shared_params/run_profile.py
deleted file mode 100644
index 5c04914cf..000000000
--- a/src/runloop_api_client/types/shared_params/run_profile.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Dict, Iterable, Optional
-from typing_extensions import Annotated, TypedDict
-
-from .mount import Mount
-from ..._utils import PropertyInfo
-from .launch_parameters import LaunchParameters
-
-__all__ = ["RunProfile"]
-
-
-class RunProfile(TypedDict, total=False):
- env_vars: Annotated[Optional[Dict[str, str]], PropertyInfo(alias="envVars")]
- """Mapping of Environment Variable to Value.
-
- May be shown in devbox logging. Example: {"DB_PASS": "DATABASE_PASSWORD"} would
- set the environment variable 'DB_PASS' to the value 'DATABASE_PASSWORD_VALUE'.
- """
-
- launch_parameters: Annotated[Optional[LaunchParameters], PropertyInfo(alias="launchParameters")]
- """
- LaunchParameters enable you to customize the resources available to your Devbox
- as well as the environment set up that should be completed before the Devbox is
- marked as 'running'.
- """
-
- mounts: Optional[Iterable[Mount]]
- """A list of mounts to be included in the scenario run."""
-
- purpose: Optional[str]
- """Purpose of the run."""
-
- secrets: Optional[Dict[str, str]]
- """Mapping of Environment Variable to User Secret Name.
-
- Never shown in devbox logging. Example: {"DB_PASS": "DATABASE_PASSWORD"} would
- set the environment variable 'DB_PASS' to the value of the secret
- 'DATABASE_PASSWORD'.
- """
diff --git a/tests/api_resources/devboxes/test_executions.py b/tests/api_resources/devboxes/test_executions.py
index ae69a42e4..0e2c5f6d5 100755
--- a/tests/api_resources/devboxes/test_executions.py
+++ b/tests/api_resources/devboxes/test_executions.py
@@ -95,7 +95,6 @@ def test_method_execute_async_with_all_params(self, client: Runloop) -> None:
id="id",
command="command",
attach_stdin=True,
- shell_name="shell_name",
)
assert_matches_type(DevboxAsyncExecutionDetailView, execution, path=["response"])
@@ -150,7 +149,6 @@ def test_method_execute_sync_with_all_params(self, client: Runloop) -> None:
id="id",
command="command",
attach_stdin=True,
- shell_name="shell_name",
)
assert_matches_type(DevboxExecutionDetailView, execution, path=["response"])
@@ -678,7 +676,6 @@ async def test_method_execute_async_with_all_params(self, async_client: AsyncRun
id="id",
command="command",
attach_stdin=True,
- shell_name="shell_name",
)
assert_matches_type(DevboxAsyncExecutionDetailView, execution, path=["response"])
@@ -733,7 +730,6 @@ async def test_method_execute_sync_with_all_params(self, async_client: AsyncRunl
id="id",
command="command",
attach_stdin=True,
- shell_name="shell_name",
)
assert_matches_type(DevboxExecutionDetailView, execution, path=["response"])
diff --git a/tests/api_resources/devboxes/test_logs.py b/tests/api_resources/devboxes/test_logs.py
index 80f10afca..21062eeb6 100644
--- a/tests/api_resources/devboxes/test_logs.py
+++ b/tests/api_resources/devboxes/test_logs.py
@@ -29,7 +29,6 @@ def test_method_list_with_all_params(self, client: Runloop) -> None:
log = client.devboxes.logs.list(
id="id",
execution_id="execution_id",
- shell_name="shell_name",
)
assert_matches_type(DevboxLogsListView, log, path=["response"])
@@ -82,7 +81,6 @@ async def test_method_list_with_all_params(self, async_client: AsyncRunloop) ->
log = await async_client.devboxes.logs.list(
id="id",
execution_id="execution_id",
- shell_name="shell_name",
)
assert_matches_type(DevboxLogsListView, log, path=["response"])
diff --git a/tests/api_resources/scenarios/__init__.py b/tests/api_resources/scenarios/__init__.py
deleted file mode 100644
index fd8019a9a..000000000
--- a/tests/api_resources/scenarios/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/scenarios/test_runs.py b/tests/api_resources/scenarios/test_runs.py
deleted file mode 100644
index ec3871b27..000000000
--- a/tests/api_resources/scenarios/test_runs.py
+++ /dev/null
@@ -1,527 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, cast
-
-import httpx
-import pytest
-from respx import MockRouter
-
-from tests.utils import assert_matches_type
-from runloop_api_client import Runloop, AsyncRunloop
-from runloop_api_client.types import ScenarioRunView
-from runloop_api_client._response import (
- BinaryAPIResponse,
- AsyncBinaryAPIResponse,
- StreamedBinaryAPIResponse,
- AsyncStreamedBinaryAPIResponse,
-)
-from runloop_api_client.pagination import SyncBenchmarkRunsCursorIDPage, AsyncBenchmarkRunsCursorIDPage
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestRuns:
- parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
- @parametrize
- def test_method_retrieve(self, client: Runloop) -> None:
- run = client.scenarios.runs.retrieve(
- "id",
- )
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- def test_raw_response_retrieve(self, client: Runloop) -> None:
- response = client.scenarios.runs.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- run = response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- def test_streaming_response_retrieve(self, client: Runloop) -> None:
- with client.scenarios.runs.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- run = response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_retrieve(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.scenarios.runs.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- def test_method_list(self, client: Runloop) -> None:
- run = client.scenarios.runs.list()
- assert_matches_type(SyncBenchmarkRunsCursorIDPage[ScenarioRunView], run, path=["response"])
-
- @parametrize
- def test_method_list_with_all_params(self, client: Runloop) -> None:
- run = client.scenarios.runs.list(
- benchmark_run_id="benchmark_run_id",
- include_total_count=True,
- limit=0,
- name="name",
- scenario_id="scenario_id",
- search="search",
- starting_after="starting_after",
- state="state",
- )
- assert_matches_type(SyncBenchmarkRunsCursorIDPage[ScenarioRunView], run, path=["response"])
-
- @parametrize
- def test_raw_response_list(self, client: Runloop) -> None:
- response = client.scenarios.runs.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- run = response.parse()
- assert_matches_type(SyncBenchmarkRunsCursorIDPage[ScenarioRunView], run, path=["response"])
-
- @parametrize
- def test_streaming_response_list(self, client: Runloop) -> None:
- with client.scenarios.runs.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- run = response.parse()
- assert_matches_type(SyncBenchmarkRunsCursorIDPage[ScenarioRunView], run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_cancel(self, client: Runloop) -> None:
- run = client.scenarios.runs.cancel(
- "id",
- )
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- def test_raw_response_cancel(self, client: Runloop) -> None:
- response = client.scenarios.runs.with_raw_response.cancel(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- run = response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- def test_streaming_response_cancel(self, client: Runloop) -> None:
- with client.scenarios.runs.with_streaming_response.cancel(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- run = response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_cancel(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.scenarios.runs.with_raw_response.cancel(
- "",
- )
-
- @parametrize
- def test_method_complete(self, client: Runloop) -> None:
- run = client.scenarios.runs.complete(
- "id",
- )
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- def test_raw_response_complete(self, client: Runloop) -> None:
- response = client.scenarios.runs.with_raw_response.complete(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- run = response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- def test_streaming_response_complete(self, client: Runloop) -> None:
- with client.scenarios.runs.with_streaming_response.complete(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- run = response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_complete(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.scenarios.runs.with_raw_response.complete(
- "",
- )
-
- @parametrize
- @pytest.mark.respx(base_url=base_url)
- def test_method_download_logs(self, client: Runloop, respx_mock: MockRouter) -> None:
- respx_mock.post("/v1/scenarios/runs/id/download_logs").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
- run = client.scenarios.runs.download_logs(
- "id",
- )
- assert run.is_closed
- assert run.json() == {"foo": "bar"}
- assert cast(Any, run.is_closed) is True
- assert isinstance(run, BinaryAPIResponse)
-
- @parametrize
- @pytest.mark.respx(base_url=base_url)
- def test_raw_response_download_logs(self, client: Runloop, respx_mock: MockRouter) -> None:
- respx_mock.post("/v1/scenarios/runs/id/download_logs").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
-
- run = client.scenarios.runs.with_raw_response.download_logs(
- "id",
- )
-
- assert run.is_closed is True
- assert run.http_request.headers.get("X-Stainless-Lang") == "python"
- assert run.json() == {"foo": "bar"}
- assert isinstance(run, BinaryAPIResponse)
-
- @parametrize
- @pytest.mark.respx(base_url=base_url)
- def test_streaming_response_download_logs(self, client: Runloop, respx_mock: MockRouter) -> None:
- respx_mock.post("/v1/scenarios/runs/id/download_logs").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
- with client.scenarios.runs.with_streaming_response.download_logs(
- "id",
- ) as run:
- assert not run.is_closed
- assert run.http_request.headers.get("X-Stainless-Lang") == "python"
-
- assert run.json() == {"foo": "bar"}
- assert cast(Any, run.is_closed) is True
- assert isinstance(run, StreamedBinaryAPIResponse)
-
- assert cast(Any, run.is_closed) is True
-
- @parametrize
- @pytest.mark.respx(base_url=base_url)
- def test_path_params_download_logs(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.scenarios.runs.with_raw_response.download_logs(
- "",
- )
-
- @parametrize
- def test_method_score(self, client: Runloop) -> None:
- run = client.scenarios.runs.score(
- "id",
- )
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- def test_raw_response_score(self, client: Runloop) -> None:
- response = client.scenarios.runs.with_raw_response.score(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- run = response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- def test_streaming_response_score(self, client: Runloop) -> None:
- with client.scenarios.runs.with_streaming_response.score(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- run = response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_score(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.scenarios.runs.with_raw_response.score(
- "",
- )
-
-
-class TestAsyncRuns:
- parametrize = pytest.mark.parametrize(
- "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
- )
-
- @parametrize
- async def test_method_retrieve(self, async_client: AsyncRunloop) -> None:
- run = await async_client.scenarios.runs.retrieve(
- "id",
- )
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- async def test_raw_response_retrieve(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.runs.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- run = await response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- async def test_streaming_response_retrieve(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.runs.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- run = await response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_retrieve(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.scenarios.runs.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- async def test_method_list(self, async_client: AsyncRunloop) -> None:
- run = await async_client.scenarios.runs.list()
- assert_matches_type(AsyncBenchmarkRunsCursorIDPage[ScenarioRunView], run, path=["response"])
-
- @parametrize
- async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None:
- run = await async_client.scenarios.runs.list(
- benchmark_run_id="benchmark_run_id",
- include_total_count=True,
- limit=0,
- name="name",
- scenario_id="scenario_id",
- search="search",
- starting_after="starting_after",
- state="state",
- )
- assert_matches_type(AsyncBenchmarkRunsCursorIDPage[ScenarioRunView], run, path=["response"])
-
- @parametrize
- async def test_raw_response_list(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.runs.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- run = await response.parse()
- assert_matches_type(AsyncBenchmarkRunsCursorIDPage[ScenarioRunView], run, path=["response"])
-
- @parametrize
- async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.runs.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- run = await response.parse()
- assert_matches_type(AsyncBenchmarkRunsCursorIDPage[ScenarioRunView], run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_cancel(self, async_client: AsyncRunloop) -> None:
- run = await async_client.scenarios.runs.cancel(
- "id",
- )
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- async def test_raw_response_cancel(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.runs.with_raw_response.cancel(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- run = await response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- async def test_streaming_response_cancel(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.runs.with_streaming_response.cancel(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- run = await response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_cancel(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.scenarios.runs.with_raw_response.cancel(
- "",
- )
-
- @parametrize
- async def test_method_complete(self, async_client: AsyncRunloop) -> None:
- run = await async_client.scenarios.runs.complete(
- "id",
- )
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- async def test_raw_response_complete(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.runs.with_raw_response.complete(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- run = await response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- async def test_streaming_response_complete(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.runs.with_streaming_response.complete(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- run = await response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_complete(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.scenarios.runs.with_raw_response.complete(
- "",
- )
-
- @parametrize
- @pytest.mark.respx(base_url=base_url)
- async def test_method_download_logs(self, async_client: AsyncRunloop, respx_mock: MockRouter) -> None:
- respx_mock.post("/v1/scenarios/runs/id/download_logs").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
- run = await async_client.scenarios.runs.download_logs(
- "id",
- )
- assert run.is_closed
- assert await run.json() == {"foo": "bar"}
- assert cast(Any, run.is_closed) is True
- assert isinstance(run, AsyncBinaryAPIResponse)
-
- @parametrize
- @pytest.mark.respx(base_url=base_url)
- async def test_raw_response_download_logs(self, async_client: AsyncRunloop, respx_mock: MockRouter) -> None:
- respx_mock.post("/v1/scenarios/runs/id/download_logs").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
-
- run = await async_client.scenarios.runs.with_raw_response.download_logs(
- "id",
- )
-
- assert run.is_closed is True
- assert run.http_request.headers.get("X-Stainless-Lang") == "python"
- assert await run.json() == {"foo": "bar"}
- assert isinstance(run, AsyncBinaryAPIResponse)
-
- @parametrize
- @pytest.mark.respx(base_url=base_url)
- async def test_streaming_response_download_logs(self, async_client: AsyncRunloop, respx_mock: MockRouter) -> None:
- respx_mock.post("/v1/scenarios/runs/id/download_logs").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
- async with async_client.scenarios.runs.with_streaming_response.download_logs(
- "id",
- ) as run:
- assert not run.is_closed
- assert run.http_request.headers.get("X-Stainless-Lang") == "python"
-
- assert await run.json() == {"foo": "bar"}
- assert cast(Any, run.is_closed) is True
- assert isinstance(run, AsyncStreamedBinaryAPIResponse)
-
- assert cast(Any, run.is_closed) is True
-
- @parametrize
- @pytest.mark.respx(base_url=base_url)
- async def test_path_params_download_logs(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.scenarios.runs.with_raw_response.download_logs(
- "",
- )
-
- @parametrize
- async def test_method_score(self, async_client: AsyncRunloop) -> None:
- run = await async_client.scenarios.runs.score(
- "id",
- )
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- async def test_raw_response_score(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.runs.with_raw_response.score(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- run = await response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- @parametrize
- async def test_streaming_response_score(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.runs.with_streaming_response.score(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- run = await response.parse()
- assert_matches_type(ScenarioRunView, run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_score(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.scenarios.runs.with_raw_response.score(
- "",
- )
diff --git a/tests/api_resources/scenarios/test_scorers.py b/tests/api_resources/scenarios/test_scorers.py
deleted file mode 100644
index c7cbbe555..000000000
--- a/tests/api_resources/scenarios/test_scorers.py
+++ /dev/null
@@ -1,336 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, cast
-
-import pytest
-
-from tests.utils import assert_matches_type
-from runloop_api_client import Runloop, AsyncRunloop
-from runloop_api_client.pagination import SyncScenarioScorersCursorIDPage, AsyncScenarioScorersCursorIDPage
-from runloop_api_client.types.scenarios import (
- ScorerListResponse,
- ScorerCreateResponse,
- ScorerUpdateResponse,
- ScorerRetrieveResponse,
-)
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestScorers:
- parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
- @parametrize
- def test_method_create(self, client: Runloop) -> None:
- scorer = client.scenarios.scorers.create(
- bash_script="bash_script",
- type="type",
- )
- assert_matches_type(ScorerCreateResponse, scorer, path=["response"])
-
- @parametrize
- def test_raw_response_create(self, client: Runloop) -> None:
- response = client.scenarios.scorers.with_raw_response.create(
- bash_script="bash_script",
- type="type",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scorer = response.parse()
- assert_matches_type(ScorerCreateResponse, scorer, path=["response"])
-
- @parametrize
- def test_streaming_response_create(self, client: Runloop) -> None:
- with client.scenarios.scorers.with_streaming_response.create(
- bash_script="bash_script",
- type="type",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scorer = response.parse()
- assert_matches_type(ScorerCreateResponse, scorer, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_retrieve(self, client: Runloop) -> None:
- scorer = client.scenarios.scorers.retrieve(
- "id",
- )
- assert_matches_type(ScorerRetrieveResponse, scorer, path=["response"])
-
- @parametrize
- def test_raw_response_retrieve(self, client: Runloop) -> None:
- response = client.scenarios.scorers.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scorer = response.parse()
- assert_matches_type(ScorerRetrieveResponse, scorer, path=["response"])
-
- @parametrize
- def test_streaming_response_retrieve(self, client: Runloop) -> None:
- with client.scenarios.scorers.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scorer = response.parse()
- assert_matches_type(ScorerRetrieveResponse, scorer, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_retrieve(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.scenarios.scorers.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- def test_method_update(self, client: Runloop) -> None:
- scorer = client.scenarios.scorers.update(
- id="id",
- bash_script="bash_script",
- type="type",
- )
- assert_matches_type(ScorerUpdateResponse, scorer, path=["response"])
-
- @parametrize
- def test_raw_response_update(self, client: Runloop) -> None:
- response = client.scenarios.scorers.with_raw_response.update(
- id="id",
- bash_script="bash_script",
- type="type",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scorer = response.parse()
- assert_matches_type(ScorerUpdateResponse, scorer, path=["response"])
-
- @parametrize
- def test_streaming_response_update(self, client: Runloop) -> None:
- with client.scenarios.scorers.with_streaming_response.update(
- id="id",
- bash_script="bash_script",
- type="type",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scorer = response.parse()
- assert_matches_type(ScorerUpdateResponse, scorer, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_update(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.scenarios.scorers.with_raw_response.update(
- id="",
- bash_script="bash_script",
- type="type",
- )
-
- @parametrize
- def test_method_list(self, client: Runloop) -> None:
- scorer = client.scenarios.scorers.list()
- assert_matches_type(SyncScenarioScorersCursorIDPage[ScorerListResponse], scorer, path=["response"])
-
- @parametrize
- def test_method_list_with_all_params(self, client: Runloop) -> None:
- scorer = client.scenarios.scorers.list(
- include_total_count=True,
- limit=0,
- search="search",
- starting_after="starting_after",
- )
- assert_matches_type(SyncScenarioScorersCursorIDPage[ScorerListResponse], scorer, path=["response"])
-
- @parametrize
- def test_raw_response_list(self, client: Runloop) -> None:
- response = client.scenarios.scorers.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scorer = response.parse()
- assert_matches_type(SyncScenarioScorersCursorIDPage[ScorerListResponse], scorer, path=["response"])
-
- @parametrize
- def test_streaming_response_list(self, client: Runloop) -> None:
- with client.scenarios.scorers.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scorer = response.parse()
- assert_matches_type(SyncScenarioScorersCursorIDPage[ScorerListResponse], scorer, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
-
-class TestAsyncScorers:
- parametrize = pytest.mark.parametrize(
- "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
- )
-
- @parametrize
- async def test_method_create(self, async_client: AsyncRunloop) -> None:
- scorer = await async_client.scenarios.scorers.create(
- bash_script="bash_script",
- type="type",
- )
- assert_matches_type(ScorerCreateResponse, scorer, path=["response"])
-
- @parametrize
- async def test_raw_response_create(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.scorers.with_raw_response.create(
- bash_script="bash_script",
- type="type",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scorer = await response.parse()
- assert_matches_type(ScorerCreateResponse, scorer, path=["response"])
-
- @parametrize
- async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.scorers.with_streaming_response.create(
- bash_script="bash_script",
- type="type",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scorer = await response.parse()
- assert_matches_type(ScorerCreateResponse, scorer, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_retrieve(self, async_client: AsyncRunloop) -> None:
- scorer = await async_client.scenarios.scorers.retrieve(
- "id",
- )
- assert_matches_type(ScorerRetrieveResponse, scorer, path=["response"])
-
- @parametrize
- async def test_raw_response_retrieve(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.scorers.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scorer = await response.parse()
- assert_matches_type(ScorerRetrieveResponse, scorer, path=["response"])
-
- @parametrize
- async def test_streaming_response_retrieve(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.scorers.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scorer = await response.parse()
- assert_matches_type(ScorerRetrieveResponse, scorer, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_retrieve(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.scenarios.scorers.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- async def test_method_update(self, async_client: AsyncRunloop) -> None:
- scorer = await async_client.scenarios.scorers.update(
- id="id",
- bash_script="bash_script",
- type="type",
- )
- assert_matches_type(ScorerUpdateResponse, scorer, path=["response"])
-
- @parametrize
- async def test_raw_response_update(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.scorers.with_raw_response.update(
- id="id",
- bash_script="bash_script",
- type="type",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scorer = await response.parse()
- assert_matches_type(ScorerUpdateResponse, scorer, path=["response"])
-
- @parametrize
- async def test_streaming_response_update(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.scorers.with_streaming_response.update(
- id="id",
- bash_script="bash_script",
- type="type",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scorer = await response.parse()
- assert_matches_type(ScorerUpdateResponse, scorer, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_update(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.scenarios.scorers.with_raw_response.update(
- id="",
- bash_script="bash_script",
- type="type",
- )
-
- @parametrize
- async def test_method_list(self, async_client: AsyncRunloop) -> None:
- scorer = await async_client.scenarios.scorers.list()
- assert_matches_type(AsyncScenarioScorersCursorIDPage[ScorerListResponse], scorer, path=["response"])
-
- @parametrize
- async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None:
- scorer = await async_client.scenarios.scorers.list(
- include_total_count=True,
- limit=0,
- search="search",
- starting_after="starting_after",
- )
- assert_matches_type(AsyncScenarioScorersCursorIDPage[ScorerListResponse], scorer, path=["response"])
-
- @parametrize
- async def test_raw_response_list(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.scorers.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scorer = await response.parse()
- assert_matches_type(AsyncScenarioScorersCursorIDPage[ScorerListResponse], scorer, path=["response"])
-
- @parametrize
- async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.scorers.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scorer = await response.parse()
- assert_matches_type(AsyncScenarioScorersCursorIDPage[ScorerListResponse], scorer, path=["response"])
-
- assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_agents.py b/tests/api_resources/test_agents.py
deleted file mode 100644
index d4a98d26e..000000000
--- a/tests/api_resources/test_agents.py
+++ /dev/null
@@ -1,502 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, cast
-
-import pytest
-
-from tests.utils import assert_matches_type
-from runloop_api_client import Runloop, AsyncRunloop
-from runloop_api_client.types import (
- AgentView,
- AgentDevboxCountsView,
-)
-from runloop_api_client.pagination import SyncAgentsCursorIDPage, AsyncAgentsCursorIDPage
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestAgents:
- parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
- @parametrize
- def test_method_create(self, client: Runloop) -> None:
- agent = client.agents.create(
- name="name",
- )
- assert_matches_type(AgentView, agent, path=["response"])
-
- @parametrize
- def test_method_create_with_all_params(self, client: Runloop) -> None:
- agent = client.agents.create(
- name="name",
- source={
- "type": "type",
- "git": {
- "repository": "repository",
- "agent_setup": ["string"],
- "ref": "ref",
- },
- "npm": {
- "package_name": "package_name",
- "agent_setup": ["string"],
- "registry_url": "registry_url",
- },
- "object": {
- "object_id": "object_id",
- "agent_setup": ["string"],
- },
- "pip": {
- "package_name": "package_name",
- "agent_setup": ["string"],
- "registry_url": "registry_url",
- },
- },
- version="version",
- )
- assert_matches_type(AgentView, agent, path=["response"])
-
- @parametrize
- def test_raw_response_create(self, client: Runloop) -> None:
- response = client.agents.with_raw_response.create(
- name="name",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- agent = response.parse()
- assert_matches_type(AgentView, agent, path=["response"])
-
- @parametrize
- def test_streaming_response_create(self, client: Runloop) -> None:
- with client.agents.with_streaming_response.create(
- name="name",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- agent = response.parse()
- assert_matches_type(AgentView, agent, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_retrieve(self, client: Runloop) -> None:
- agent = client.agents.retrieve(
- "id",
- )
- assert_matches_type(AgentView, agent, path=["response"])
-
- @parametrize
- def test_raw_response_retrieve(self, client: Runloop) -> None:
- response = client.agents.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- agent = response.parse()
- assert_matches_type(AgentView, agent, path=["response"])
-
- @parametrize
- def test_streaming_response_retrieve(self, client: Runloop) -> None:
- with client.agents.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- agent = response.parse()
- assert_matches_type(AgentView, agent, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_retrieve(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.agents.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- def test_method_list(self, client: Runloop) -> None:
- agent = client.agents.list()
- assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- @parametrize
- def test_method_list_with_all_params(self, client: Runloop) -> None:
- agent = client.agents.list(
- include_total_count=True,
- is_public=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- version="version",
- )
- assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- @parametrize
- def test_raw_response_list(self, client: Runloop) -> None:
- response = client.agents.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- agent = response.parse()
- assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- @parametrize
- def test_streaming_response_list(self, client: Runloop) -> None:
- with client.agents.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- agent = response.parse()
- assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_delete(self, client: Runloop) -> None:
- agent = client.agents.delete(
- "id",
- )
- assert_matches_type(object, agent, path=["response"])
-
- @parametrize
- def test_raw_response_delete(self, client: Runloop) -> None:
- response = client.agents.with_raw_response.delete(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- agent = response.parse()
- assert_matches_type(object, agent, path=["response"])
-
- @parametrize
- def test_streaming_response_delete(self, client: Runloop) -> None:
- with client.agents.with_streaming_response.delete(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- agent = response.parse()
- assert_matches_type(object, agent, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_delete(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.agents.with_raw_response.delete(
- "",
- )
-
- @parametrize
- def test_method_devbox_counts(self, client: Runloop) -> None:
- agent = client.agents.devbox_counts()
- assert_matches_type(AgentDevboxCountsView, agent, path=["response"])
-
- @parametrize
- def test_raw_response_devbox_counts(self, client: Runloop) -> None:
- response = client.agents.with_raw_response.devbox_counts()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- agent = response.parse()
- assert_matches_type(AgentDevboxCountsView, agent, path=["response"])
-
- @parametrize
- def test_streaming_response_devbox_counts(self, client: Runloop) -> None:
- with client.agents.with_streaming_response.devbox_counts() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- agent = response.parse()
- assert_matches_type(AgentDevboxCountsView, agent, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_list_public(self, client: Runloop) -> None:
- agent = client.agents.list_public()
- assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- @parametrize
- def test_method_list_public_with_all_params(self, client: Runloop) -> None:
- agent = client.agents.list_public(
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- version="version",
- )
- assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- @parametrize
- def test_raw_response_list_public(self, client: Runloop) -> None:
- response = client.agents.with_raw_response.list_public()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- agent = response.parse()
- assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- @parametrize
- def test_streaming_response_list_public(self, client: Runloop) -> None:
- with client.agents.with_streaming_response.list_public() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- agent = response.parse()
- assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
-
-class TestAsyncAgents:
- parametrize = pytest.mark.parametrize(
- "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
- )
-
- @parametrize
- async def test_method_create(self, async_client: AsyncRunloop) -> None:
- agent = await async_client.agents.create(
- name="name",
- )
- assert_matches_type(AgentView, agent, path=["response"])
-
- @parametrize
- async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None:
- agent = await async_client.agents.create(
- name="name",
- source={
- "type": "type",
- "git": {
- "repository": "repository",
- "agent_setup": ["string"],
- "ref": "ref",
- },
- "npm": {
- "package_name": "package_name",
- "agent_setup": ["string"],
- "registry_url": "registry_url",
- },
- "object": {
- "object_id": "object_id",
- "agent_setup": ["string"],
- },
- "pip": {
- "package_name": "package_name",
- "agent_setup": ["string"],
- "registry_url": "registry_url",
- },
- },
- version="version",
- )
- assert_matches_type(AgentView, agent, path=["response"])
-
- @parametrize
- async def test_raw_response_create(self, async_client: AsyncRunloop) -> None:
- response = await async_client.agents.with_raw_response.create(
- name="name",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- agent = await response.parse()
- assert_matches_type(AgentView, agent, path=["response"])
-
- @parametrize
- async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None:
- async with async_client.agents.with_streaming_response.create(
- name="name",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- agent = await response.parse()
- assert_matches_type(AgentView, agent, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_retrieve(self, async_client: AsyncRunloop) -> None:
- agent = await async_client.agents.retrieve(
- "id",
- )
- assert_matches_type(AgentView, agent, path=["response"])
-
- @parametrize
- async def test_raw_response_retrieve(self, async_client: AsyncRunloop) -> None:
- response = await async_client.agents.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- agent = await response.parse()
- assert_matches_type(AgentView, agent, path=["response"])
-
- @parametrize
- async def test_streaming_response_retrieve(self, async_client: AsyncRunloop) -> None:
- async with async_client.agents.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- agent = await response.parse()
- assert_matches_type(AgentView, agent, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_retrieve(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.agents.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- async def test_method_list(self, async_client: AsyncRunloop) -> None:
- agent = await async_client.agents.list()
- assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- @parametrize
- async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None:
- agent = await async_client.agents.list(
- include_total_count=True,
- is_public=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- version="version",
- )
- assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- @parametrize
- async def test_raw_response_list(self, async_client: AsyncRunloop) -> None:
- response = await async_client.agents.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- agent = await response.parse()
- assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- @parametrize
- async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None:
- async with async_client.agents.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- agent = await response.parse()
- assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_delete(self, async_client: AsyncRunloop) -> None:
- agent = await async_client.agents.delete(
- "id",
- )
- assert_matches_type(object, agent, path=["response"])
-
- @parametrize
- async def test_raw_response_delete(self, async_client: AsyncRunloop) -> None:
- response = await async_client.agents.with_raw_response.delete(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- agent = await response.parse()
- assert_matches_type(object, agent, path=["response"])
-
- @parametrize
- async def test_streaming_response_delete(self, async_client: AsyncRunloop) -> None:
- async with async_client.agents.with_streaming_response.delete(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- agent = await response.parse()
- assert_matches_type(object, agent, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_delete(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.agents.with_raw_response.delete(
- "",
- )
-
- @parametrize
- async def test_method_devbox_counts(self, async_client: AsyncRunloop) -> None:
- agent = await async_client.agents.devbox_counts()
- assert_matches_type(AgentDevboxCountsView, agent, path=["response"])
-
- @parametrize
- async def test_raw_response_devbox_counts(self, async_client: AsyncRunloop) -> None:
- response = await async_client.agents.with_raw_response.devbox_counts()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- agent = await response.parse()
- assert_matches_type(AgentDevboxCountsView, agent, path=["response"])
-
- @parametrize
- async def test_streaming_response_devbox_counts(self, async_client: AsyncRunloop) -> None:
- async with async_client.agents.with_streaming_response.devbox_counts() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- agent = await response.parse()
- assert_matches_type(AgentDevboxCountsView, agent, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_list_public(self, async_client: AsyncRunloop) -> None:
- agent = await async_client.agents.list_public()
- assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- @parametrize
- async def test_method_list_public_with_all_params(self, async_client: AsyncRunloop) -> None:
- agent = await async_client.agents.list_public(
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- version="version",
- )
- assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- @parametrize
- async def test_raw_response_list_public(self, async_client: AsyncRunloop) -> None:
- response = await async_client.agents.with_raw_response.list_public()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- agent = await response.parse()
- assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- @parametrize
- async def test_streaming_response_list_public(self, async_client: AsyncRunloop) -> None:
- async with async_client.agents.with_streaming_response.list_public() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- agent = await response.parse()
- assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"])
-
- assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_axons.py b/tests/api_resources/test_axons.py
index 97f4f663c..aaf8cd219 100644
--- a/tests/api_resources/test_axons.py
+++ b/tests/api_resources/test_axons.py
@@ -29,6 +29,7 @@ def test_method_create(self, client: Runloop) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Runloop) -> None:
axon = client.axons.create(
+ metadata={"foo": "string"},
name="name",
)
assert_matches_type(AxonView, axon, path=["response"])
@@ -91,6 +92,52 @@ def test_path_params_retrieve(self, client: Runloop) -> None:
"",
)
+ @parametrize
+ def test_method_update(self, client: Runloop) -> None:
+ axon = client.axons.update(
+ id="id",
+ )
+ assert_matches_type(AxonView, axon, path=["response"])
+
+ @parametrize
+ def test_method_update_with_all_params(self, client: Runloop) -> None:
+ axon = client.axons.update(
+ id="id",
+ metadata={"foo": "string"},
+ )
+ assert_matches_type(AxonView, axon, path=["response"])
+
+ @parametrize
+ def test_raw_response_update(self, client: Runloop) -> None:
+ response = client.axons.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ axon = response.parse()
+ assert_matches_type(AxonView, axon, path=["response"])
+
+ @parametrize
+ def test_streaming_response_update(self, client: Runloop) -> None:
+ with client.axons.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ axon = response.parse()
+ assert_matches_type(AxonView, axon, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_update(self, client: Runloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.axons.with_raw_response.update(
+ id="",
+ )
+
@parametrize
def test_method_list(self, client: Runloop) -> None:
axon = client.axons.list()
@@ -102,6 +149,8 @@ def test_method_list_with_all_params(self, client: Runloop) -> None:
id="id",
include_total_count=True,
limit=0,
+ metadata_key="metadata[key]",
+ metadata_key_in="metadata[key][in]",
name="name",
search="search",
starting_after="starting_after",
@@ -279,6 +328,7 @@ async def test_method_create(self, async_client: AsyncRunloop) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None:
axon = await async_client.axons.create(
+ metadata={"foo": "string"},
name="name",
)
assert_matches_type(AxonView, axon, path=["response"])
@@ -341,6 +391,52 @@ async def test_path_params_retrieve(self, async_client: AsyncRunloop) -> None:
"",
)
+ @parametrize
+ async def test_method_update(self, async_client: AsyncRunloop) -> None:
+ axon = await async_client.axons.update(
+ id="id",
+ )
+ assert_matches_type(AxonView, axon, path=["response"])
+
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncRunloop) -> None:
+ axon = await async_client.axons.update(
+ id="id",
+ metadata={"foo": "string"},
+ )
+ assert_matches_type(AxonView, axon, path=["response"])
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.axons.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ axon = await response.parse()
+ assert_matches_type(AxonView, axon, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncRunloop) -> None:
+ async with async_client.axons.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ axon = await response.parse()
+ assert_matches_type(AxonView, axon, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncRunloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.axons.with_raw_response.update(
+ id="",
+ )
+
@parametrize
async def test_method_list(self, async_client: AsyncRunloop) -> None:
axon = await async_client.axons.list()
@@ -352,6 +448,8 @@ async def test_method_list_with_all_params(self, async_client: AsyncRunloop) ->
id="id",
include_total_count=True,
limit=0,
+ metadata_key="metadata[key]",
+ metadata_key_in="metadata[key][in]",
name="name",
search="search",
starting_after="starting_after",
diff --git a/tests/api_resources/test_benchmark_jobs.py b/tests/api_resources/test_benchmark_jobs.py
deleted file mode 100644
index e109ad8dc..000000000
--- a/tests/api_resources/test_benchmark_jobs.py
+++ /dev/null
@@ -1,247 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, cast
-
-import pytest
-
-from tests.utils import assert_matches_type
-from runloop_api_client import Runloop, AsyncRunloop
-from runloop_api_client.types import (
- BenchmarkJobView,
- BenchmarkJobListView,
-)
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestBenchmarkJobs:
- parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
- @parametrize
- def test_method_create(self, client: Runloop) -> None:
- benchmark_job = client.benchmark_jobs.create()
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- @parametrize
- def test_method_create_with_all_params(self, client: Runloop) -> None:
- benchmark_job = client.benchmark_jobs.create(
- name="name",
- spec={
- "inline_yaml": "inline_yaml",
- "type": "harbor",
- },
- )
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- @parametrize
- def test_raw_response_create(self, client: Runloop) -> None:
- response = client.benchmark_jobs.with_raw_response.create()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_job = response.parse()
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- @parametrize
- def test_streaming_response_create(self, client: Runloop) -> None:
- with client.benchmark_jobs.with_streaming_response.create() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_job = response.parse()
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_retrieve(self, client: Runloop) -> None:
- benchmark_job = client.benchmark_jobs.retrieve(
- "id",
- )
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- @parametrize
- def test_raw_response_retrieve(self, client: Runloop) -> None:
- response = client.benchmark_jobs.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_job = response.parse()
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- @parametrize
- def test_streaming_response_retrieve(self, client: Runloop) -> None:
- with client.benchmark_jobs.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_job = response.parse()
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_retrieve(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.benchmark_jobs.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- def test_method_list(self, client: Runloop) -> None:
- benchmark_job = client.benchmark_jobs.list()
- assert_matches_type(BenchmarkJobListView, benchmark_job, path=["response"])
-
- @parametrize
- def test_method_list_with_all_params(self, client: Runloop) -> None:
- benchmark_job = client.benchmark_jobs.list(
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- )
- assert_matches_type(BenchmarkJobListView, benchmark_job, path=["response"])
-
- @parametrize
- def test_raw_response_list(self, client: Runloop) -> None:
- response = client.benchmark_jobs.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_job = response.parse()
- assert_matches_type(BenchmarkJobListView, benchmark_job, path=["response"])
-
- @parametrize
- def test_streaming_response_list(self, client: Runloop) -> None:
- with client.benchmark_jobs.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_job = response.parse()
- assert_matches_type(BenchmarkJobListView, benchmark_job, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
-
-class TestAsyncBenchmarkJobs:
- parametrize = pytest.mark.parametrize(
- "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
- )
-
- @parametrize
- async def test_method_create(self, async_client: AsyncRunloop) -> None:
- benchmark_job = await async_client.benchmark_jobs.create()
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- @parametrize
- async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None:
- benchmark_job = await async_client.benchmark_jobs.create(
- name="name",
- spec={
- "inline_yaml": "inline_yaml",
- "type": "harbor",
- },
- )
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- @parametrize
- async def test_raw_response_create(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmark_jobs.with_raw_response.create()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_job = await response.parse()
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- @parametrize
- async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmark_jobs.with_streaming_response.create() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_job = await response.parse()
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_retrieve(self, async_client: AsyncRunloop) -> None:
- benchmark_job = await async_client.benchmark_jobs.retrieve(
- "id",
- )
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- @parametrize
- async def test_raw_response_retrieve(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmark_jobs.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_job = await response.parse()
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- @parametrize
- async def test_streaming_response_retrieve(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmark_jobs.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_job = await response.parse()
- assert_matches_type(BenchmarkJobView, benchmark_job, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_retrieve(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.benchmark_jobs.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- async def test_method_list(self, async_client: AsyncRunloop) -> None:
- benchmark_job = await async_client.benchmark_jobs.list()
- assert_matches_type(BenchmarkJobListView, benchmark_job, path=["response"])
-
- @parametrize
- async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None:
- benchmark_job = await async_client.benchmark_jobs.list(
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- )
- assert_matches_type(BenchmarkJobListView, benchmark_job, path=["response"])
-
- @parametrize
- async def test_raw_response_list(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmark_jobs.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_job = await response.parse()
- assert_matches_type(BenchmarkJobListView, benchmark_job, path=["response"])
-
- @parametrize
- async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmark_jobs.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_job = await response.parse()
- assert_matches_type(BenchmarkJobListView, benchmark_job, path=["response"])
-
- assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_benchmark_runs.py b/tests/api_resources/test_benchmark_runs.py
deleted file mode 100644
index 3ce64b8eb..000000000
--- a/tests/api_resources/test_benchmark_runs.py
+++ /dev/null
@@ -1,432 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, cast
-
-import pytest
-
-from tests.utils import assert_matches_type
-from runloop_api_client import Runloop, AsyncRunloop
-from runloop_api_client.types import (
- ScenarioRunView,
- BenchmarkRunView,
-)
-from runloop_api_client.pagination import SyncBenchmarkRunsCursorIDPage, AsyncBenchmarkRunsCursorIDPage
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestBenchmarkRuns:
- parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
- @parametrize
- def test_method_retrieve(self, client: Runloop) -> None:
- benchmark_run = client.benchmark_runs.retrieve(
- "id",
- )
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- @parametrize
- def test_raw_response_retrieve(self, client: Runloop) -> None:
- response = client.benchmark_runs.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_run = response.parse()
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- @parametrize
- def test_streaming_response_retrieve(self, client: Runloop) -> None:
- with client.benchmark_runs.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_run = response.parse()
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_retrieve(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.benchmark_runs.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- def test_method_list(self, client: Runloop) -> None:
- benchmark_run = client.benchmark_runs.list()
- assert_matches_type(SyncBenchmarkRunsCursorIDPage[BenchmarkRunView], benchmark_run, path=["response"])
-
- @parametrize
- def test_method_list_with_all_params(self, client: Runloop) -> None:
- benchmark_run = client.benchmark_runs.list(
- benchmark_id="benchmark_id",
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- state="state",
- )
- assert_matches_type(SyncBenchmarkRunsCursorIDPage[BenchmarkRunView], benchmark_run, path=["response"])
-
- @parametrize
- def test_raw_response_list(self, client: Runloop) -> None:
- response = client.benchmark_runs.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_run = response.parse()
- assert_matches_type(SyncBenchmarkRunsCursorIDPage[BenchmarkRunView], benchmark_run, path=["response"])
-
- @parametrize
- def test_streaming_response_list(self, client: Runloop) -> None:
- with client.benchmark_runs.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_run = response.parse()
- assert_matches_type(SyncBenchmarkRunsCursorIDPage[BenchmarkRunView], benchmark_run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_cancel(self, client: Runloop) -> None:
- benchmark_run = client.benchmark_runs.cancel(
- "id",
- )
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- @parametrize
- def test_raw_response_cancel(self, client: Runloop) -> None:
- response = client.benchmark_runs.with_raw_response.cancel(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_run = response.parse()
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- @parametrize
- def test_streaming_response_cancel(self, client: Runloop) -> None:
- with client.benchmark_runs.with_streaming_response.cancel(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_run = response.parse()
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_cancel(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.benchmark_runs.with_raw_response.cancel(
- "",
- )
-
- @parametrize
- def test_method_complete(self, client: Runloop) -> None:
- benchmark_run = client.benchmark_runs.complete(
- "id",
- )
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- @parametrize
- def test_raw_response_complete(self, client: Runloop) -> None:
- response = client.benchmark_runs.with_raw_response.complete(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_run = response.parse()
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- @parametrize
- def test_streaming_response_complete(self, client: Runloop) -> None:
- with client.benchmark_runs.with_streaming_response.complete(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_run = response.parse()
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_complete(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.benchmark_runs.with_raw_response.complete(
- "",
- )
-
- @parametrize
- def test_method_list_scenario_runs(self, client: Runloop) -> None:
- benchmark_run = client.benchmark_runs.list_scenario_runs(
- id="id",
- )
- assert_matches_type(SyncBenchmarkRunsCursorIDPage[ScenarioRunView], benchmark_run, path=["response"])
-
- @parametrize
- def test_method_list_scenario_runs_with_all_params(self, client: Runloop) -> None:
- benchmark_run = client.benchmark_runs.list_scenario_runs(
- id="id",
- include_total_count=True,
- limit=0,
- search="search",
- starting_after="starting_after",
- state="running",
- )
- assert_matches_type(SyncBenchmarkRunsCursorIDPage[ScenarioRunView], benchmark_run, path=["response"])
-
- @parametrize
- def test_raw_response_list_scenario_runs(self, client: Runloop) -> None:
- response = client.benchmark_runs.with_raw_response.list_scenario_runs(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_run = response.parse()
- assert_matches_type(SyncBenchmarkRunsCursorIDPage[ScenarioRunView], benchmark_run, path=["response"])
-
- @parametrize
- def test_streaming_response_list_scenario_runs(self, client: Runloop) -> None:
- with client.benchmark_runs.with_streaming_response.list_scenario_runs(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_run = response.parse()
- assert_matches_type(SyncBenchmarkRunsCursorIDPage[ScenarioRunView], benchmark_run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_list_scenario_runs(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.benchmark_runs.with_raw_response.list_scenario_runs(
- id="",
- )
-
-
-class TestAsyncBenchmarkRuns:
- parametrize = pytest.mark.parametrize(
- "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
- )
-
- @parametrize
- async def test_method_retrieve(self, async_client: AsyncRunloop) -> None:
- benchmark_run = await async_client.benchmark_runs.retrieve(
- "id",
- )
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- @parametrize
- async def test_raw_response_retrieve(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmark_runs.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_run = await response.parse()
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- @parametrize
- async def test_streaming_response_retrieve(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmark_runs.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_run = await response.parse()
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_retrieve(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.benchmark_runs.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- async def test_method_list(self, async_client: AsyncRunloop) -> None:
- benchmark_run = await async_client.benchmark_runs.list()
- assert_matches_type(AsyncBenchmarkRunsCursorIDPage[BenchmarkRunView], benchmark_run, path=["response"])
-
- @parametrize
- async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None:
- benchmark_run = await async_client.benchmark_runs.list(
- benchmark_id="benchmark_id",
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- state="state",
- )
- assert_matches_type(AsyncBenchmarkRunsCursorIDPage[BenchmarkRunView], benchmark_run, path=["response"])
-
- @parametrize
- async def test_raw_response_list(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmark_runs.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_run = await response.parse()
- assert_matches_type(AsyncBenchmarkRunsCursorIDPage[BenchmarkRunView], benchmark_run, path=["response"])
-
- @parametrize
- async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmark_runs.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_run = await response.parse()
- assert_matches_type(AsyncBenchmarkRunsCursorIDPage[BenchmarkRunView], benchmark_run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_cancel(self, async_client: AsyncRunloop) -> None:
- benchmark_run = await async_client.benchmark_runs.cancel(
- "id",
- )
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- @parametrize
- async def test_raw_response_cancel(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmark_runs.with_raw_response.cancel(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_run = await response.parse()
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- @parametrize
- async def test_streaming_response_cancel(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmark_runs.with_streaming_response.cancel(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_run = await response.parse()
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_cancel(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.benchmark_runs.with_raw_response.cancel(
- "",
- )
-
- @parametrize
- async def test_method_complete(self, async_client: AsyncRunloop) -> None:
- benchmark_run = await async_client.benchmark_runs.complete(
- "id",
- )
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- @parametrize
- async def test_raw_response_complete(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmark_runs.with_raw_response.complete(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_run = await response.parse()
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- @parametrize
- async def test_streaming_response_complete(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmark_runs.with_streaming_response.complete(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_run = await response.parse()
- assert_matches_type(BenchmarkRunView, benchmark_run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_complete(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.benchmark_runs.with_raw_response.complete(
- "",
- )
-
- @parametrize
- async def test_method_list_scenario_runs(self, async_client: AsyncRunloop) -> None:
- benchmark_run = await async_client.benchmark_runs.list_scenario_runs(
- id="id",
- )
- assert_matches_type(AsyncBenchmarkRunsCursorIDPage[ScenarioRunView], benchmark_run, path=["response"])
-
- @parametrize
- async def test_method_list_scenario_runs_with_all_params(self, async_client: AsyncRunloop) -> None:
- benchmark_run = await async_client.benchmark_runs.list_scenario_runs(
- id="id",
- include_total_count=True,
- limit=0,
- search="search",
- starting_after="starting_after",
- state="running",
- )
- assert_matches_type(AsyncBenchmarkRunsCursorIDPage[ScenarioRunView], benchmark_run, path=["response"])
-
- @parametrize
- async def test_raw_response_list_scenario_runs(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmark_runs.with_raw_response.list_scenario_runs(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark_run = await response.parse()
- assert_matches_type(AsyncBenchmarkRunsCursorIDPage[ScenarioRunView], benchmark_run, path=["response"])
-
- @parametrize
- async def test_streaming_response_list_scenario_runs(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmark_runs.with_streaming_response.list_scenario_runs(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark_run = await response.parse()
- assert_matches_type(AsyncBenchmarkRunsCursorIDPage[ScenarioRunView], benchmark_run, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_list_scenario_runs(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.benchmark_runs.with_raw_response.list_scenario_runs(
- id="",
- )
diff --git a/tests/api_resources/test_benchmarks.py b/tests/api_resources/test_benchmarks.py
deleted file mode 100644
index f6de5f2af..000000000
--- a/tests/api_resources/test_benchmarks.py
+++ /dev/null
@@ -1,803 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, cast
-
-import pytest
-
-from tests.utils import assert_matches_type
-from runloop_api_client import Runloop, AsyncRunloop
-from runloop_api_client.types import (
- BenchmarkView,
- BenchmarkRunView,
- ScenarioDefinitionListView,
-)
-from runloop_api_client.pagination import SyncBenchmarksCursorIDPage, AsyncBenchmarksCursorIDPage
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestBenchmarks:
- parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
- @parametrize
- def test_method_create(self, client: Runloop) -> None:
- benchmark = client.benchmarks.create(
- name="name",
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- def test_method_create_with_all_params(self, client: Runloop) -> None:
- benchmark = client.benchmarks.create(
- name="name",
- attribution="attribution",
- description="description",
- metadata={"foo": "string"},
- required_environment_variables=["string"],
- required_secret_names=["string"],
- scenario_ids=["string"],
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- def test_raw_response_create(self, client: Runloop) -> None:
- response = client.benchmarks.with_raw_response.create(
- name="name",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- def test_streaming_response_create(self, client: Runloop) -> None:
- with client.benchmarks.with_streaming_response.create(
- name="name",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_retrieve(self, client: Runloop) -> None:
- benchmark = client.benchmarks.retrieve(
- "id",
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- def test_raw_response_retrieve(self, client: Runloop) -> None:
- response = client.benchmarks.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- def test_streaming_response_retrieve(self, client: Runloop) -> None:
- with client.benchmarks.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_retrieve(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.benchmarks.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- def test_method_update(self, client: Runloop) -> None:
- benchmark = client.benchmarks.update(
- id="id",
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- def test_method_update_with_all_params(self, client: Runloop) -> None:
- benchmark = client.benchmarks.update(
- id="id",
- attribution="attribution",
- description="description",
- metadata={"foo": "string"},
- name="name",
- required_environment_variables=["string"],
- required_secret_names=["string"],
- scenario_ids=["string"],
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- def test_raw_response_update(self, client: Runloop) -> None:
- response = client.benchmarks.with_raw_response.update(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- def test_streaming_response_update(self, client: Runloop) -> None:
- with client.benchmarks.with_streaming_response.update(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_update(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.benchmarks.with_raw_response.update(
- id="",
- )
-
- @parametrize
- def test_method_list(self, client: Runloop) -> None:
- benchmark = client.benchmarks.list()
- assert_matches_type(SyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- @parametrize
- def test_method_list_with_all_params(self, client: Runloop) -> None:
- benchmark = client.benchmarks.list(
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- )
- assert_matches_type(SyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- @parametrize
- def test_raw_response_list(self, client: Runloop) -> None:
- response = client.benchmarks.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = response.parse()
- assert_matches_type(SyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- @parametrize
- def test_streaming_response_list(self, client: Runloop) -> None:
- with client.benchmarks.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = response.parse()
- assert_matches_type(SyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_definitions(self, client: Runloop) -> None:
- benchmark = client.benchmarks.definitions(
- id="id",
- )
- assert_matches_type(ScenarioDefinitionListView, benchmark, path=["response"])
-
- @parametrize
- def test_method_definitions_with_all_params(self, client: Runloop) -> None:
- benchmark = client.benchmarks.definitions(
- id="id",
- limit=0,
- starting_after="starting_after",
- )
- assert_matches_type(ScenarioDefinitionListView, benchmark, path=["response"])
-
- @parametrize
- def test_raw_response_definitions(self, client: Runloop) -> None:
- response = client.benchmarks.with_raw_response.definitions(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = response.parse()
- assert_matches_type(ScenarioDefinitionListView, benchmark, path=["response"])
-
- @parametrize
- def test_streaming_response_definitions(self, client: Runloop) -> None:
- with client.benchmarks.with_streaming_response.definitions(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = response.parse()
- assert_matches_type(ScenarioDefinitionListView, benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_definitions(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.benchmarks.with_raw_response.definitions(
- id="",
- )
-
- @parametrize
- def test_method_list_public(self, client: Runloop) -> None:
- benchmark = client.benchmarks.list_public()
- assert_matches_type(SyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- @parametrize
- def test_method_list_public_with_all_params(self, client: Runloop) -> None:
- benchmark = client.benchmarks.list_public(
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- )
- assert_matches_type(SyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- @parametrize
- def test_raw_response_list_public(self, client: Runloop) -> None:
- response = client.benchmarks.with_raw_response.list_public()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = response.parse()
- assert_matches_type(SyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- @parametrize
- def test_streaming_response_list_public(self, client: Runloop) -> None:
- with client.benchmarks.with_streaming_response.list_public() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = response.parse()
- assert_matches_type(SyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_start_run(self, client: Runloop) -> None:
- benchmark = client.benchmarks.start_run(
- benchmark_id="benchmark_id",
- )
- assert_matches_type(BenchmarkRunView, benchmark, path=["response"])
-
- @parametrize
- def test_method_start_run_with_all_params(self, client: Runloop) -> None:
- benchmark = client.benchmarks.start_run(
- benchmark_id="benchmark_id",
- metadata={"foo": "string"},
- run_name="run_name",
- run_profile={
- "env_vars": {"foo": "string"},
- "launch_parameters": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "architecture": "x86_64",
- "available_ports": [0],
- "custom_cpu_cores": 0,
- "custom_disk_size": 0,
- "custom_gb_memory": 0,
- "keep_alive_time_seconds": 0,
- "launch_commands": ["string"],
- "lifecycle": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "lifecycle_hooks": {
- "suspend_commands": ["string"],
- "suspend_deadline_ms": 0,
- },
- "resume_triggers": {
- "axon_event": True,
- "http": True,
- },
- },
- "network_policy_id": "network_policy_id",
- "provisioning_tier": "standard",
- "required_services": ["string"],
- "resource_size_request": "X_SMALL",
- "user_parameters": {
- "uid": 0,
- "username": "username",
- },
- },
- "mounts": [
- {
- "object_id": "object_id",
- "object_path": "object_path",
- "type": "object_mount",
- }
- ],
- "purpose": "purpose",
- "secrets": {"foo": "string"},
- },
- )
- assert_matches_type(BenchmarkRunView, benchmark, path=["response"])
-
- @parametrize
- def test_raw_response_start_run(self, client: Runloop) -> None:
- response = client.benchmarks.with_raw_response.start_run(
- benchmark_id="benchmark_id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = response.parse()
- assert_matches_type(BenchmarkRunView, benchmark, path=["response"])
-
- @parametrize
- def test_streaming_response_start_run(self, client: Runloop) -> None:
- with client.benchmarks.with_streaming_response.start_run(
- benchmark_id="benchmark_id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = response.parse()
- assert_matches_type(BenchmarkRunView, benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_update_scenarios(self, client: Runloop) -> None:
- benchmark = client.benchmarks.update_scenarios(
- id="id",
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- def test_method_update_scenarios_with_all_params(self, client: Runloop) -> None:
- benchmark = client.benchmarks.update_scenarios(
- id="id",
- scenarios_to_add=["string"],
- scenarios_to_remove=["string"],
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- def test_raw_response_update_scenarios(self, client: Runloop) -> None:
- response = client.benchmarks.with_raw_response.update_scenarios(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- def test_streaming_response_update_scenarios(self, client: Runloop) -> None:
- with client.benchmarks.with_streaming_response.update_scenarios(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_update_scenarios(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.benchmarks.with_raw_response.update_scenarios(
- id="",
- )
-
-
-class TestAsyncBenchmarks:
- parametrize = pytest.mark.parametrize(
- "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
- )
-
- @parametrize
- async def test_method_create(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.create(
- name="name",
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.create(
- name="name",
- attribution="attribution",
- description="description",
- metadata={"foo": "string"},
- required_environment_variables=["string"],
- required_secret_names=["string"],
- scenario_ids=["string"],
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- async def test_raw_response_create(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmarks.with_raw_response.create(
- name="name",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = await response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmarks.with_streaming_response.create(
- name="name",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = await response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_retrieve(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.retrieve(
- "id",
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- async def test_raw_response_retrieve(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmarks.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = await response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- async def test_streaming_response_retrieve(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmarks.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = await response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_retrieve(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.benchmarks.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- async def test_method_update(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.update(
- id="id",
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- async def test_method_update_with_all_params(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.update(
- id="id",
- attribution="attribution",
- description="description",
- metadata={"foo": "string"},
- name="name",
- required_environment_variables=["string"],
- required_secret_names=["string"],
- scenario_ids=["string"],
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- async def test_raw_response_update(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmarks.with_raw_response.update(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = await response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- async def test_streaming_response_update(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmarks.with_streaming_response.update(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = await response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_update(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.benchmarks.with_raw_response.update(
- id="",
- )
-
- @parametrize
- async def test_method_list(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.list()
- assert_matches_type(AsyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- @parametrize
- async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.list(
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- )
- assert_matches_type(AsyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- @parametrize
- async def test_raw_response_list(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmarks.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = await response.parse()
- assert_matches_type(AsyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- @parametrize
- async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmarks.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = await response.parse()
- assert_matches_type(AsyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_definitions(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.definitions(
- id="id",
- )
- assert_matches_type(ScenarioDefinitionListView, benchmark, path=["response"])
-
- @parametrize
- async def test_method_definitions_with_all_params(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.definitions(
- id="id",
- limit=0,
- starting_after="starting_after",
- )
- assert_matches_type(ScenarioDefinitionListView, benchmark, path=["response"])
-
- @parametrize
- async def test_raw_response_definitions(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmarks.with_raw_response.definitions(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = await response.parse()
- assert_matches_type(ScenarioDefinitionListView, benchmark, path=["response"])
-
- @parametrize
- async def test_streaming_response_definitions(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmarks.with_streaming_response.definitions(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = await response.parse()
- assert_matches_type(ScenarioDefinitionListView, benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_definitions(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.benchmarks.with_raw_response.definitions(
- id="",
- )
-
- @parametrize
- async def test_method_list_public(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.list_public()
- assert_matches_type(AsyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- @parametrize
- async def test_method_list_public_with_all_params(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.list_public(
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- )
- assert_matches_type(AsyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- @parametrize
- async def test_raw_response_list_public(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmarks.with_raw_response.list_public()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = await response.parse()
- assert_matches_type(AsyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- @parametrize
- async def test_streaming_response_list_public(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmarks.with_streaming_response.list_public() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = await response.parse()
- assert_matches_type(AsyncBenchmarksCursorIDPage[BenchmarkView], benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_start_run(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.start_run(
- benchmark_id="benchmark_id",
- )
- assert_matches_type(BenchmarkRunView, benchmark, path=["response"])
-
- @parametrize
- async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.start_run(
- benchmark_id="benchmark_id",
- metadata={"foo": "string"},
- run_name="run_name",
- run_profile={
- "env_vars": {"foo": "string"},
- "launch_parameters": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "architecture": "x86_64",
- "available_ports": [0],
- "custom_cpu_cores": 0,
- "custom_disk_size": 0,
- "custom_gb_memory": 0,
- "keep_alive_time_seconds": 0,
- "launch_commands": ["string"],
- "lifecycle": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "lifecycle_hooks": {
- "suspend_commands": ["string"],
- "suspend_deadline_ms": 0,
- },
- "resume_triggers": {
- "axon_event": True,
- "http": True,
- },
- },
- "network_policy_id": "network_policy_id",
- "provisioning_tier": "standard",
- "required_services": ["string"],
- "resource_size_request": "X_SMALL",
- "user_parameters": {
- "uid": 0,
- "username": "username",
- },
- },
- "mounts": [
- {
- "object_id": "object_id",
- "object_path": "object_path",
- "type": "object_mount",
- }
- ],
- "purpose": "purpose",
- "secrets": {"foo": "string"},
- },
- )
- assert_matches_type(BenchmarkRunView, benchmark, path=["response"])
-
- @parametrize
- async def test_raw_response_start_run(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmarks.with_raw_response.start_run(
- benchmark_id="benchmark_id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = await response.parse()
- assert_matches_type(BenchmarkRunView, benchmark, path=["response"])
-
- @parametrize
- async def test_streaming_response_start_run(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmarks.with_streaming_response.start_run(
- benchmark_id="benchmark_id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = await response.parse()
- assert_matches_type(BenchmarkRunView, benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_update_scenarios(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.update_scenarios(
- id="id",
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- async def test_method_update_scenarios_with_all_params(self, async_client: AsyncRunloop) -> None:
- benchmark = await async_client.benchmarks.update_scenarios(
- id="id",
- scenarios_to_add=["string"],
- scenarios_to_remove=["string"],
- )
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- async def test_raw_response_update_scenarios(self, async_client: AsyncRunloop) -> None:
- response = await async_client.benchmarks.with_raw_response.update_scenarios(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- benchmark = await response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- @parametrize
- async def test_streaming_response_update_scenarios(self, async_client: AsyncRunloop) -> None:
- async with async_client.benchmarks.with_streaming_response.update_scenarios(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- benchmark = await response.parse()
- assert_matches_type(BenchmarkView, benchmark, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_update_scenarios(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.benchmarks.with_raw_response.update_scenarios(
- id="",
- )
diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py
index 42c4aad15..a479aaf91 100644
--- a/tests/api_resources/test_devboxes.py
+++ b/tests/api_resources/test_devboxes.py
@@ -21,7 +21,6 @@
DevboxSnapshotView,
DevboxResourceUsageView,
DevboxExecutionDetailView,
- DevboxCreateSSHKeyResponse,
DevboxAsyncExecutionDetailView,
)
from runloop_api_client._response import (
@@ -406,44 +405,6 @@ def test_path_params_create_pty_tunnel(self, client: Runloop) -> None:
"",
)
- @parametrize
- def test_method_create_ssh_key(self, client: Runloop) -> None:
- devbox = client.devboxes.create_ssh_key(
- "id",
- )
- assert_matches_type(DevboxCreateSSHKeyResponse, devbox, path=["response"])
-
- @parametrize
- def test_raw_response_create_ssh_key(self, client: Runloop) -> None:
- response = client.devboxes.with_raw_response.create_ssh_key(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- devbox = response.parse()
- assert_matches_type(DevboxCreateSSHKeyResponse, devbox, path=["response"])
-
- @parametrize
- def test_streaming_response_create_ssh_key(self, client: Runloop) -> None:
- with client.devboxes.with_streaming_response.create_ssh_key(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- devbox = response.parse()
- assert_matches_type(DevboxCreateSSHKeyResponse, devbox, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_create_ssh_key(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.devboxes.with_raw_response.create_ssh_key(
- "",
- )
-
@parametrize
def test_method_delete_disk_snapshot(self, client: Runloop) -> None:
devbox = client.devboxes.delete_disk_snapshot(
@@ -605,7 +566,6 @@ def test_method_execute_with_all_params(self, client: Runloop) -> None:
command_id="command_id",
last_n="last_n",
optimistic_timeout=0,
- shell_name="shell_name",
)
assert_matches_type(DevboxAsyncExecutionDetailView, devbox, path=["response"])
@@ -660,7 +620,6 @@ def test_method_execute_async_with_all_params(self, client: Runloop) -> None:
id="id",
command="command",
attach_stdin=True,
- shell_name="shell_name",
)
assert_matches_type(DevboxAsyncExecutionDetailView, devbox, path=["response"])
@@ -715,7 +674,6 @@ def test_method_execute_sync_with_all_params(self, client: Runloop) -> None:
id="id",
command="command",
attach_stdin=True,
- shell_name="shell_name",
)
assert_matches_type(DevboxExecutionDetailView, devbox, path=["response"])
@@ -2261,44 +2219,6 @@ async def test_path_params_create_pty_tunnel(self, async_client: AsyncRunloop) -
"",
)
- @parametrize
- async def test_method_create_ssh_key(self, async_client: AsyncRunloop) -> None:
- devbox = await async_client.devboxes.create_ssh_key(
- "id",
- )
- assert_matches_type(DevboxCreateSSHKeyResponse, devbox, path=["response"])
-
- @parametrize
- async def test_raw_response_create_ssh_key(self, async_client: AsyncRunloop) -> None:
- response = await async_client.devboxes.with_raw_response.create_ssh_key(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- devbox = await response.parse()
- assert_matches_type(DevboxCreateSSHKeyResponse, devbox, path=["response"])
-
- @parametrize
- async def test_streaming_response_create_ssh_key(self, async_client: AsyncRunloop) -> None:
- async with async_client.devboxes.with_streaming_response.create_ssh_key(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- devbox = await response.parse()
- assert_matches_type(DevboxCreateSSHKeyResponse, devbox, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_create_ssh_key(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.devboxes.with_raw_response.create_ssh_key(
- "",
- )
-
@parametrize
async def test_method_delete_disk_snapshot(self, async_client: AsyncRunloop) -> None:
devbox = await async_client.devboxes.delete_disk_snapshot(
@@ -2460,7 +2380,6 @@ async def test_method_execute_with_all_params(self, async_client: AsyncRunloop)
command_id="command_id",
last_n="last_n",
optimistic_timeout=0,
- shell_name="shell_name",
)
assert_matches_type(DevboxAsyncExecutionDetailView, devbox, path=["response"])
@@ -2515,7 +2434,6 @@ async def test_method_execute_async_with_all_params(self, async_client: AsyncRun
id="id",
command="command",
attach_stdin=True,
- shell_name="shell_name",
)
assert_matches_type(DevboxAsyncExecutionDetailView, devbox, path=["response"])
@@ -2570,7 +2488,6 @@ async def test_method_execute_sync_with_all_params(self, async_client: AsyncRunl
id="id",
command="command",
attach_stdin=True,
- shell_name="shell_name",
)
assert_matches_type(DevboxExecutionDetailView, devbox, path=["response"])
diff --git a/tests/api_resources/test_network_policies.py b/tests/api_resources/test_network_policies.py
index fb70f4069..63c27b599 100644
--- a/tests/api_resources/test_network_policies.py
+++ b/tests/api_resources/test_network_policies.py
@@ -35,7 +35,6 @@ def test_method_create_with_all_params(self, client: Runloop) -> None:
allow_all=True,
allow_devbox_to_devbox=True,
allow_mcp_gateway=True,
- allow_runloop_mirrors=True,
allowed_cidrs=[
{
"cidr": "cidr",
@@ -130,7 +129,6 @@ def test_method_update_with_all_params(self, client: Runloop) -> None:
allow_all=True,
allow_devbox_to_devbox=True,
allow_mcp_gateway=True,
- allow_runloop_mirrors=True,
allowed_cidrs=[
{
"cidr": "cidr",
@@ -276,7 +274,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -
allow_all=True,
allow_devbox_to_devbox=True,
allow_mcp_gateway=True,
- allow_runloop_mirrors=True,
allowed_cidrs=[
{
"cidr": "cidr",
@@ -371,7 +368,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncRunloop) -
allow_all=True,
allow_devbox_to_devbox=True,
allow_mcp_gateway=True,
- allow_runloop_mirrors=True,
allowed_cidrs=[
{
"cidr": "cidr",
diff --git a/tests/api_resources/test_scenarios.py b/tests/api_resources/test_scenarios.py
deleted file mode 100644
index 59ab6c634..000000000
--- a/tests/api_resources/test_scenarios.py
+++ /dev/null
@@ -1,1012 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, cast
-
-import pytest
-
-from tests.utils import assert_matches_type
-from runloop_api_client import Runloop, AsyncRunloop
-from runloop_api_client.types import (
- ScenarioView,
- ScenarioRunView,
-)
-from runloop_api_client.pagination import SyncScenariosCursorIDPage, AsyncScenariosCursorIDPage
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestScenarios:
- parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
- @parametrize
- def test_method_create(self, client: Runloop) -> None:
- scenario = client.scenarios.create(
- input_context={"problem_statement": "problem_statement"},
- name="name",
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "name",
- "scorer": {
- "pattern": "pattern",
- "search_directory": "search_directory",
- "type": "ast_grep_scorer",
- },
- "weight": 0,
- }
- ]
- },
- )
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- def test_method_create_with_all_params(self, client: Runloop) -> None:
- scenario = client.scenarios.create(
- input_context={
- "problem_statement": "problem_statement",
- "additional_context": {},
- },
- name="name",
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "name",
- "scorer": {
- "pattern": "pattern",
- "search_directory": "search_directory",
- "type": "ast_grep_scorer",
- "lang": "lang",
- },
- "weight": 0,
- }
- ]
- },
- environment_parameters={
- "blueprint_id": "blueprint_id",
- "launch_parameters": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "architecture": "x86_64",
- "available_ports": [0],
- "custom_cpu_cores": 0,
- "custom_disk_size": 0,
- "custom_gb_memory": 0,
- "keep_alive_time_seconds": 0,
- "launch_commands": ["string"],
- "lifecycle": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "lifecycle_hooks": {
- "suspend_commands": ["string"],
- "suspend_deadline_ms": 0,
- },
- "resume_triggers": {
- "axon_event": True,
- "http": True,
- },
- },
- "network_policy_id": "network_policy_id",
- "provisioning_tier": "standard",
- "required_services": ["string"],
- "resource_size_request": "X_SMALL",
- "user_parameters": {
- "uid": 0,
- "username": "username",
- },
- },
- "snapshot_id": "snapshot_id",
- "working_directory": "working_directory",
- },
- metadata={"foo": "string"},
- reference_output="reference_output",
- required_environment_variables=["string"],
- required_secret_names=["string"],
- scorer_timeout_sec=0,
- validation_type="UNSPECIFIED",
- )
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- def test_raw_response_create(self, client: Runloop) -> None:
- response = client.scenarios.with_raw_response.create(
- input_context={"problem_statement": "problem_statement"},
- name="name",
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "name",
- "scorer": {
- "pattern": "pattern",
- "search_directory": "search_directory",
- "type": "ast_grep_scorer",
- },
- "weight": 0,
- }
- ]
- },
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- def test_streaming_response_create(self, client: Runloop) -> None:
- with client.scenarios.with_streaming_response.create(
- input_context={"problem_statement": "problem_statement"},
- name="name",
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "name",
- "scorer": {
- "pattern": "pattern",
- "search_directory": "search_directory",
- "type": "ast_grep_scorer",
- },
- "weight": 0,
- }
- ]
- },
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_retrieve(self, client: Runloop) -> None:
- scenario = client.scenarios.retrieve(
- "id",
- )
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- def test_raw_response_retrieve(self, client: Runloop) -> None:
- response = client.scenarios.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- def test_streaming_response_retrieve(self, client: Runloop) -> None:
- with client.scenarios.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_retrieve(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.scenarios.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- def test_method_update(self, client: Runloop) -> None:
- scenario = client.scenarios.update(
- id="id",
- )
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- def test_method_update_with_all_params(self, client: Runloop) -> None:
- scenario = client.scenarios.update(
- id="id",
- environment_parameters={
- "blueprint_id": "blueprint_id",
- "launch_parameters": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "architecture": "x86_64",
- "available_ports": [0],
- "custom_cpu_cores": 0,
- "custom_disk_size": 0,
- "custom_gb_memory": 0,
- "keep_alive_time_seconds": 0,
- "launch_commands": ["string"],
- "lifecycle": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "lifecycle_hooks": {
- "suspend_commands": ["string"],
- "suspend_deadline_ms": 0,
- },
- "resume_triggers": {
- "axon_event": True,
- "http": True,
- },
- },
- "network_policy_id": "network_policy_id",
- "provisioning_tier": "standard",
- "required_services": ["string"],
- "resource_size_request": "X_SMALL",
- "user_parameters": {
- "uid": 0,
- "username": "username",
- },
- },
- "snapshot_id": "snapshot_id",
- "working_directory": "working_directory",
- },
- input_context={
- "additional_context": {},
- "problem_statement": "problem_statement",
- },
- metadata={"foo": "string"},
- name="name",
- reference_output="reference_output",
- required_environment_variables=["string"],
- required_secret_names=["string"],
- scorer_timeout_sec=0,
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "name",
- "scorer": {
- "pattern": "pattern",
- "search_directory": "search_directory",
- "type": "ast_grep_scorer",
- "lang": "lang",
- },
- "weight": 0,
- }
- ]
- },
- validation_type="UNSPECIFIED",
- )
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- def test_raw_response_update(self, client: Runloop) -> None:
- response = client.scenarios.with_raw_response.update(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- def test_streaming_response_update(self, client: Runloop) -> None:
- with client.scenarios.with_streaming_response.update(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_update(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.scenarios.with_raw_response.update(
- id="",
- )
-
- @parametrize
- def test_method_list(self, client: Runloop) -> None:
- scenario = client.scenarios.list()
- assert_matches_type(SyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- @parametrize
- def test_method_list_with_all_params(self, client: Runloop) -> None:
- scenario = client.scenarios.list(
- benchmark_id="benchmark_id",
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- validation_type="validation_type",
- )
- assert_matches_type(SyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- @parametrize
- def test_raw_response_list(self, client: Runloop) -> None:
- response = client.scenarios.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = response.parse()
- assert_matches_type(SyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- @parametrize
- def test_streaming_response_list(self, client: Runloop) -> None:
- with client.scenarios.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = response.parse()
- assert_matches_type(SyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_archive(self, client: Runloop) -> None:
- scenario = client.scenarios.archive(
- "id",
- )
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- def test_raw_response_archive(self, client: Runloop) -> None:
- response = client.scenarios.with_raw_response.archive(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- def test_streaming_response_archive(self, client: Runloop) -> None:
- with client.scenarios.with_streaming_response.archive(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_archive(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.scenarios.with_raw_response.archive(
- "",
- )
-
- @parametrize
- def test_method_list_public(self, client: Runloop) -> None:
- scenario = client.scenarios.list_public()
- assert_matches_type(SyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- @parametrize
- def test_method_list_public_with_all_params(self, client: Runloop) -> None:
- scenario = client.scenarios.list_public(
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- )
- assert_matches_type(SyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- @parametrize
- def test_raw_response_list_public(self, client: Runloop) -> None:
- response = client.scenarios.with_raw_response.list_public()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = response.parse()
- assert_matches_type(SyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- @parametrize
- def test_streaming_response_list_public(self, client: Runloop) -> None:
- with client.scenarios.with_streaming_response.list_public() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = response.parse()
- assert_matches_type(SyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_start_run(self, client: Runloop) -> None:
- scenario = client.scenarios.start_run(
- scenario_id="scenario_id",
- )
- assert_matches_type(ScenarioRunView, scenario, path=["response"])
-
- @parametrize
- def test_method_start_run_with_all_params(self, client: Runloop) -> None:
- scenario = client.scenarios.start_run(
- scenario_id="scenario_id",
- benchmark_run_id="benchmark_run_id",
- metadata={"foo": "string"},
- run_name="run_name",
- run_profile={
- "env_vars": {"foo": "string"},
- "launch_parameters": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "architecture": "x86_64",
- "available_ports": [0],
- "custom_cpu_cores": 0,
- "custom_disk_size": 0,
- "custom_gb_memory": 0,
- "keep_alive_time_seconds": 0,
- "launch_commands": ["string"],
- "lifecycle": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "lifecycle_hooks": {
- "suspend_commands": ["string"],
- "suspend_deadline_ms": 0,
- },
- "resume_triggers": {
- "axon_event": True,
- "http": True,
- },
- },
- "network_policy_id": "network_policy_id",
- "provisioning_tier": "standard",
- "required_services": ["string"],
- "resource_size_request": "X_SMALL",
- "user_parameters": {
- "uid": 0,
- "username": "username",
- },
- },
- "mounts": [
- {
- "object_id": "object_id",
- "object_path": "object_path",
- "type": "object_mount",
- }
- ],
- "purpose": "purpose",
- "secrets": {"foo": "string"},
- },
- )
- assert_matches_type(ScenarioRunView, scenario, path=["response"])
-
- @parametrize
- def test_raw_response_start_run(self, client: Runloop) -> None:
- response = client.scenarios.with_raw_response.start_run(
- scenario_id="scenario_id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = response.parse()
- assert_matches_type(ScenarioRunView, scenario, path=["response"])
-
- @parametrize
- def test_streaming_response_start_run(self, client: Runloop) -> None:
- with client.scenarios.with_streaming_response.start_run(
- scenario_id="scenario_id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = response.parse()
- assert_matches_type(ScenarioRunView, scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
-
-class TestAsyncScenarios:
- parametrize = pytest.mark.parametrize(
- "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
- )
-
- @parametrize
- async def test_method_create(self, async_client: AsyncRunloop) -> None:
- scenario = await async_client.scenarios.create(
- input_context={"problem_statement": "problem_statement"},
- name="name",
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "name",
- "scorer": {
- "pattern": "pattern",
- "search_directory": "search_directory",
- "type": "ast_grep_scorer",
- },
- "weight": 0,
- }
- ]
- },
- )
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None:
- scenario = await async_client.scenarios.create(
- input_context={
- "problem_statement": "problem_statement",
- "additional_context": {},
- },
- name="name",
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "name",
- "scorer": {
- "pattern": "pattern",
- "search_directory": "search_directory",
- "type": "ast_grep_scorer",
- "lang": "lang",
- },
- "weight": 0,
- }
- ]
- },
- environment_parameters={
- "blueprint_id": "blueprint_id",
- "launch_parameters": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "architecture": "x86_64",
- "available_ports": [0],
- "custom_cpu_cores": 0,
- "custom_disk_size": 0,
- "custom_gb_memory": 0,
- "keep_alive_time_seconds": 0,
- "launch_commands": ["string"],
- "lifecycle": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "lifecycle_hooks": {
- "suspend_commands": ["string"],
- "suspend_deadline_ms": 0,
- },
- "resume_triggers": {
- "axon_event": True,
- "http": True,
- },
- },
- "network_policy_id": "network_policy_id",
- "provisioning_tier": "standard",
- "required_services": ["string"],
- "resource_size_request": "X_SMALL",
- "user_parameters": {
- "uid": 0,
- "username": "username",
- },
- },
- "snapshot_id": "snapshot_id",
- "working_directory": "working_directory",
- },
- metadata={"foo": "string"},
- reference_output="reference_output",
- required_environment_variables=["string"],
- required_secret_names=["string"],
- scorer_timeout_sec=0,
- validation_type="UNSPECIFIED",
- )
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- async def test_raw_response_create(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.with_raw_response.create(
- input_context={"problem_statement": "problem_statement"},
- name="name",
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "name",
- "scorer": {
- "pattern": "pattern",
- "search_directory": "search_directory",
- "type": "ast_grep_scorer",
- },
- "weight": 0,
- }
- ]
- },
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = await response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.with_streaming_response.create(
- input_context={"problem_statement": "problem_statement"},
- name="name",
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "name",
- "scorer": {
- "pattern": "pattern",
- "search_directory": "search_directory",
- "type": "ast_grep_scorer",
- },
- "weight": 0,
- }
- ]
- },
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = await response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_retrieve(self, async_client: AsyncRunloop) -> None:
- scenario = await async_client.scenarios.retrieve(
- "id",
- )
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- async def test_raw_response_retrieve(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = await response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- async def test_streaming_response_retrieve(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = await response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_retrieve(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.scenarios.with_raw_response.retrieve(
- "",
- )
-
- @parametrize
- async def test_method_update(self, async_client: AsyncRunloop) -> None:
- scenario = await async_client.scenarios.update(
- id="id",
- )
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- async def test_method_update_with_all_params(self, async_client: AsyncRunloop) -> None:
- scenario = await async_client.scenarios.update(
- id="id",
- environment_parameters={
- "blueprint_id": "blueprint_id",
- "launch_parameters": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "architecture": "x86_64",
- "available_ports": [0],
- "custom_cpu_cores": 0,
- "custom_disk_size": 0,
- "custom_gb_memory": 0,
- "keep_alive_time_seconds": 0,
- "launch_commands": ["string"],
- "lifecycle": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "lifecycle_hooks": {
- "suspend_commands": ["string"],
- "suspend_deadline_ms": 0,
- },
- "resume_triggers": {
- "axon_event": True,
- "http": True,
- },
- },
- "network_policy_id": "network_policy_id",
- "provisioning_tier": "standard",
- "required_services": ["string"],
- "resource_size_request": "X_SMALL",
- "user_parameters": {
- "uid": 0,
- "username": "username",
- },
- },
- "snapshot_id": "snapshot_id",
- "working_directory": "working_directory",
- },
- input_context={
- "additional_context": {},
- "problem_statement": "problem_statement",
- },
- metadata={"foo": "string"},
- name="name",
- reference_output="reference_output",
- required_environment_variables=["string"],
- required_secret_names=["string"],
- scorer_timeout_sec=0,
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "name",
- "scorer": {
- "pattern": "pattern",
- "search_directory": "search_directory",
- "type": "ast_grep_scorer",
- "lang": "lang",
- },
- "weight": 0,
- }
- ]
- },
- validation_type="UNSPECIFIED",
- )
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- async def test_raw_response_update(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.with_raw_response.update(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = await response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- async def test_streaming_response_update(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.with_streaming_response.update(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = await response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_update(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.scenarios.with_raw_response.update(
- id="",
- )
-
- @parametrize
- async def test_method_list(self, async_client: AsyncRunloop) -> None:
- scenario = await async_client.scenarios.list()
- assert_matches_type(AsyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- @parametrize
- async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None:
- scenario = await async_client.scenarios.list(
- benchmark_id="benchmark_id",
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- validation_type="validation_type",
- )
- assert_matches_type(AsyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- @parametrize
- async def test_raw_response_list(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = await response.parse()
- assert_matches_type(AsyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- @parametrize
- async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = await response.parse()
- assert_matches_type(AsyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_archive(self, async_client: AsyncRunloop) -> None:
- scenario = await async_client.scenarios.archive(
- "id",
- )
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- async def test_raw_response_archive(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.with_raw_response.archive(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = await response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- @parametrize
- async def test_streaming_response_archive(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.with_streaming_response.archive(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = await response.parse()
- assert_matches_type(ScenarioView, scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_archive(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.scenarios.with_raw_response.archive(
- "",
- )
-
- @parametrize
- async def test_method_list_public(self, async_client: AsyncRunloop) -> None:
- scenario = await async_client.scenarios.list_public()
- assert_matches_type(AsyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- @parametrize
- async def test_method_list_public_with_all_params(self, async_client: AsyncRunloop) -> None:
- scenario = await async_client.scenarios.list_public(
- include_total_count=True,
- limit=0,
- name="name",
- search="search",
- starting_after="starting_after",
- )
- assert_matches_type(AsyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- @parametrize
- async def test_raw_response_list_public(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.with_raw_response.list_public()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = await response.parse()
- assert_matches_type(AsyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- @parametrize
- async def test_streaming_response_list_public(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.with_streaming_response.list_public() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = await response.parse()
- assert_matches_type(AsyncScenariosCursorIDPage[ScenarioView], scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_start_run(self, async_client: AsyncRunloop) -> None:
- scenario = await async_client.scenarios.start_run(
- scenario_id="scenario_id",
- )
- assert_matches_type(ScenarioRunView, scenario, path=["response"])
-
- @parametrize
- async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop) -> None:
- scenario = await async_client.scenarios.start_run(
- scenario_id="scenario_id",
- benchmark_run_id="benchmark_run_id",
- metadata={"foo": "string"},
- run_name="run_name",
- run_profile={
- "env_vars": {"foo": "string"},
- "launch_parameters": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "architecture": "x86_64",
- "available_ports": [0],
- "custom_cpu_cores": 0,
- "custom_disk_size": 0,
- "custom_gb_memory": 0,
- "keep_alive_time_seconds": 0,
- "launch_commands": ["string"],
- "lifecycle": {
- "after_idle": {
- "idle_time_seconds": 0,
- "on_idle": "shutdown",
- },
- "lifecycle_hooks": {
- "suspend_commands": ["string"],
- "suspend_deadline_ms": 0,
- },
- "resume_triggers": {
- "axon_event": True,
- "http": True,
- },
- },
- "network_policy_id": "network_policy_id",
- "provisioning_tier": "standard",
- "required_services": ["string"],
- "resource_size_request": "X_SMALL",
- "user_parameters": {
- "uid": 0,
- "username": "username",
- },
- },
- "mounts": [
- {
- "object_id": "object_id",
- "object_path": "object_path",
- "type": "object_mount",
- }
- ],
- "purpose": "purpose",
- "secrets": {"foo": "string"},
- },
- )
- assert_matches_type(ScenarioRunView, scenario, path=["response"])
-
- @parametrize
- async def test_raw_response_start_run(self, async_client: AsyncRunloop) -> None:
- response = await async_client.scenarios.with_raw_response.start_run(
- scenario_id="scenario_id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- scenario = await response.parse()
- assert_matches_type(ScenarioRunView, scenario, path=["response"])
-
- @parametrize
- async def test_streaming_response_start_run(self, async_client: AsyncRunloop) -> None:
- async with async_client.scenarios.with_streaming_response.start_run(
- scenario_id="scenario_id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- scenario = await response.parse()
- assert_matches_type(ScenarioRunView, scenario, path=["response"])
-
- assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_secrets.py b/tests/api_resources/test_secrets.py
index 8e0abff43..552b32997 100644
--- a/tests/api_resources/test_secrets.py
+++ b/tests/api_resources/test_secrets.py
@@ -204,6 +204,124 @@ def test_path_params_delete(self, client: Runloop) -> None:
"",
)
+ @parametrize
+ def test_method_delete_by_id(self, client: Runloop) -> None:
+ secret = client.secrets.delete_by_id(
+ "id",
+ )
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ @parametrize
+ def test_raw_response_delete_by_id(self, client: Runloop) -> None:
+ response = client.secrets.with_raw_response.delete_by_id(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ secret = response.parse()
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ @parametrize
+ def test_streaming_response_delete_by_id(self, client: Runloop) -> None:
+ with client.secrets.with_streaming_response.delete_by_id(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ secret = response.parse()
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_delete_by_id(self, client: Runloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.secrets.with_raw_response.delete_by_id(
+ "",
+ )
+
+ @parametrize
+ def test_method_retrieve_by_id(self, client: Runloop) -> None:
+ secret = client.secrets.retrieve_by_id(
+ "id",
+ )
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ @parametrize
+ def test_raw_response_retrieve_by_id(self, client: Runloop) -> None:
+ response = client.secrets.with_raw_response.retrieve_by_id(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ secret = response.parse()
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ @parametrize
+ def test_streaming_response_retrieve_by_id(self, client: Runloop) -> None:
+ with client.secrets.with_streaming_response.retrieve_by_id(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ secret = response.parse()
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_retrieve_by_id(self, client: Runloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.secrets.with_raw_response.retrieve_by_id(
+ "",
+ )
+
+ @parametrize
+ def test_method_update_by_id(self, client: Runloop) -> None:
+ secret = client.secrets.update_by_id(
+ id="id",
+ value="value",
+ )
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ @parametrize
+ def test_raw_response_update_by_id(self, client: Runloop) -> None:
+ response = client.secrets.with_raw_response.update_by_id(
+ id="id",
+ value="value",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ secret = response.parse()
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ @parametrize
+ def test_streaming_response_update_by_id(self, client: Runloop) -> None:
+ with client.secrets.with_streaming_response.update_by_id(
+ id="id",
+ value="value",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ secret = response.parse()
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_update_by_id(self, client: Runloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.secrets.with_raw_response.update_by_id(
+ id="",
+ value="value",
+ )
+
class TestAsyncSecrets:
parametrize = pytest.mark.parametrize(
@@ -393,3 +511,121 @@ async def test_path_params_delete(self, async_client: AsyncRunloop) -> None:
await async_client.secrets.with_raw_response.delete(
"",
)
+
+ @parametrize
+ async def test_method_delete_by_id(self, async_client: AsyncRunloop) -> None:
+ secret = await async_client.secrets.delete_by_id(
+ "id",
+ )
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ @parametrize
+ async def test_raw_response_delete_by_id(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.secrets.with_raw_response.delete_by_id(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ secret = await response.parse()
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_delete_by_id(self, async_client: AsyncRunloop) -> None:
+ async with async_client.secrets.with_streaming_response.delete_by_id(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ secret = await response.parse()
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_delete_by_id(self, async_client: AsyncRunloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.secrets.with_raw_response.delete_by_id(
+ "",
+ )
+
+ @parametrize
+ async def test_method_retrieve_by_id(self, async_client: AsyncRunloop) -> None:
+ secret = await async_client.secrets.retrieve_by_id(
+ "id",
+ )
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ @parametrize
+ async def test_raw_response_retrieve_by_id(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.secrets.with_raw_response.retrieve_by_id(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ secret = await response.parse()
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_retrieve_by_id(self, async_client: AsyncRunloop) -> None:
+ async with async_client.secrets.with_streaming_response.retrieve_by_id(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ secret = await response.parse()
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_retrieve_by_id(self, async_client: AsyncRunloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.secrets.with_raw_response.retrieve_by_id(
+ "",
+ )
+
+ @parametrize
+ async def test_method_update_by_id(self, async_client: AsyncRunloop) -> None:
+ secret = await async_client.secrets.update_by_id(
+ id="id",
+ value="value",
+ )
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ @parametrize
+ async def test_raw_response_update_by_id(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.secrets.with_raw_response.update_by_id(
+ id="id",
+ value="value",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ secret = await response.parse()
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_update_by_id(self, async_client: AsyncRunloop) -> None:
+ async with async_client.secrets.with_streaming_response.update_by_id(
+ id="id",
+ value="value",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ secret = await response.parse()
+ assert_matches_type(SecretView, secret, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_update_by_id(self, async_client: AsyncRunloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.secrets.with_raw_response.update_by_id(
+ id="",
+ value="value",
+ )
diff --git a/tests/sdk/async_devbox/test_interfaces.py b/tests/sdk/async_devbox/test_interfaces.py
index 06ac528ab..c09c28d8b 100644
--- a/tests/sdk/async_devbox/test_interfaces.py
+++ b/tests/sdk/async_devbox/test_interfaces.py
@@ -38,6 +38,19 @@ async def test_exec_without_callbacks(
assert "timeout" not in call_kwargs
mock_async_client.devboxes.executions.await_completed.assert_not_called()
+ @pytest.mark.asyncio
+ async def test_exec_async_forwards_stdin(
+ self, mock_async_client: AsyncMock, execution_view: MockExecutionView
+ ) -> None:
+ mock_async_client.devboxes.execute_async = AsyncMock(return_value=execution_view)
+ devbox = AsyncDevbox(mock_async_client, "dbx_123")
+
+ await devbox.cmd.exec_async("cat", attach_stdin=True)
+
+ call_kwargs = mock_async_client.devboxes.execute_async.call_args.kwargs
+ assert call_kwargs["command"] == "cat"
+ assert call_kwargs["attach_stdin"] is True
+
@pytest.mark.asyncio
async def test_exec_with_stdout_callback(self, mock_async_client: AsyncMock, mock_async_stream: AsyncMock) -> None:
"""Test exec with stdout callback."""
@@ -160,24 +173,6 @@ async def test_upload(self, mock_async_client: AsyncMock, tmp_path: Path) -> Non
class TestAsyncNetworkInterface:
"""Tests for _AsyncNetworkInterface."""
- @pytest.mark.asyncio
- async def test_create_ssh_key(self, mock_async_client: AsyncMock) -> None:
- """Test create SSH key."""
- ssh_key_response = SimpleNamespace(public_key="ssh-rsa ...")
- mock_async_client.devboxes.create_ssh_key = AsyncMock(return_value=ssh_key_response)
-
- devbox = AsyncDevbox(mock_async_client, "dbx_123")
- result = await devbox.net.create_ssh_key(
- extra_headers={"X-Custom": "value"},
- extra_query={"param": "value"},
- extra_body={"key": "value"},
- timeout=30.0,
- idempotency_key="key-123",
- )
-
- assert result == ssh_key_response
- mock_async_client.devboxes.create_ssh_key.assert_called_once()
-
@pytest.mark.asyncio
async def test_remove_tunnel(self, mock_async_client: AsyncMock) -> None:
"""Test remove tunnel."""
diff --git a/tests/sdk/conftest.py b/tests/sdk/conftest.py
index 92ab25f42..5fd5eb2e7 100644
--- a/tests/sdk/conftest.py
+++ b/tests/sdk/conftest.py
@@ -5,7 +5,7 @@
import asyncio
import threading
-from typing import Any, Dict
+from typing import Any
from dataclasses import field, dataclass
from unittest.mock import Mock, AsyncMock
@@ -21,13 +21,7 @@
"snapshot": "snp_123",
"blueprint": "bpt_123",
"object": "obj_123",
- "scorer": "sco_123",
- "agent": "agt_123",
"axon": "axn_123",
- "scenario": "scn_123",
- "scenario_run": "scr_123",
- "benchmark": "bmd_123",
- "benchmark_run": "bmr_123",
"network_policy": "np_123",
"gateway_config": "gwc_123",
}
@@ -93,29 +87,10 @@ class MockObjectView:
id: str = TEST_IDS["object"]
upload_url: str = "https://upload.example.com/obj_123"
+ upload_headers: dict[str, str] | None = None
name: str = "test-object"
-@dataclass
-class MockScorerView:
- """Mock ScorerView for testing."""
-
- id: str = TEST_IDS["scorer"]
- bash_script: str = "echo 'score=1.0'"
- type: str = "test_scorer"
-
-
-@dataclass
-class MockAgentView:
- """Mock AgentView for testing."""
-
- id: str = TEST_IDS["agent"]
- name: str = "test-agent"
- create_time_ms: int = 1234567890000
- is_public: bool = False
- source: Any = None
-
-
@dataclass
class MockAxonView:
"""Mock AxonView for testing."""
@@ -174,50 +149,6 @@ class MockSqlBatchResultView:
results: list[Any] = field(default_factory=lambda: [MockSqlStepResultView()])
-@dataclass
-class MockScenarioView:
- """Mock ScenarioView for testing."""
-
- id: str = TEST_IDS["scenario"]
- name: str = "test-scenario"
- metadata: Dict[str, str] = field(default_factory=dict)
-
-
-@dataclass
-class MockScenarioRunView:
- """Mock ScenarioRunView for testing."""
-
- id: str = TEST_IDS["scenario_run"]
- devbox_id: str = TEST_IDS["devbox"]
- scenario_id: str = TEST_IDS["scenario"]
- state: str = "running"
- metadata: Dict[str, str] = field(default_factory=dict)
- scoring_contract_result: object = None
-
-
-@dataclass
-class MockBenchmarkView:
- """Mock BenchmarkView for testing."""
-
- id: str = TEST_IDS["benchmark"]
- name: str = "test-benchmark"
- metadata: Dict[str, str] = field(default_factory=dict)
- scenario_ids: list[str] = field(default_factory=list)
-
-
-@dataclass
-class MockBenchmarkRunView:
- """Mock BenchmarkRunView for testing."""
-
- id: str = TEST_IDS["benchmark_run"]
- benchmark_id: str = TEST_IDS["benchmark"]
- state: str = "running"
- metadata: Dict[str, str] = field(default_factory=dict)
- start_time_ms: int = 1234567890000
- duration_ms: int | None = None
- score: float | None = None
-
-
@dataclass
class MockEgress:
"""Mock Egress for testing."""
@@ -354,48 +285,12 @@ def object_view() -> MockObjectView:
return MockObjectView()
-@pytest.fixture
-def scorer_view() -> MockScorerView:
- """Create a mock ScorerView."""
- return MockScorerView()
-
-
-@pytest.fixture
-def agent_view() -> MockAgentView:
- """Create a mock AgentView."""
- return MockAgentView()
-
-
@pytest.fixture
def axon_view() -> MockAxonView:
"""Create a mock AxonView."""
return MockAxonView()
-@pytest.fixture
-def scenario_view() -> MockScenarioView:
- """Create a mock ScenarioView."""
- return MockScenarioView()
-
-
-@pytest.fixture
-def scenario_run_view() -> MockScenarioRunView:
- """Create a mock ScenarioRunView."""
- return MockScenarioRunView()
-
-
-@pytest.fixture
-def benchmark_view() -> MockBenchmarkView:
- """Create a mock BenchmarkView."""
- return MockBenchmarkView()
-
-
-@pytest.fixture
-def benchmark_run_view() -> MockBenchmarkRunView:
- """Create a mock BenchmarkRunView."""
- return MockBenchmarkRunView()
-
-
@pytest.fixture
def network_policy_view() -> MockNetworkPolicyView:
"""Create a mock NetworkPolicyView."""
diff --git a/tests/sdk/devbox/test_edge_cases.py b/tests/sdk/devbox/test_edge_cases.py
index 23341f0c0..5d0abb6ce 100644
--- a/tests/sdk/devbox/test_edge_cases.py
+++ b/tests/sdk/devbox/test_edge_cases.py
@@ -138,11 +138,11 @@ def test_path_handling(self, mock_client: Mock, tmp_path: Path) -> None:
http_client = Mock()
mock_response = create_mock_httpx_response()
- http_client.put.return_value = mock_response
+ http_client.send.return_value = mock_response
mock_client._client = http_client
obj = StorageObject(mock_client, "obj_123", "https://upload.example.com")
obj.upload_content(temp_file.read_text())
obj.upload_content(temp_file.read_bytes())
- assert http_client.put.call_count == 2
+ assert http_client.send.call_count == 2
diff --git a/tests/sdk/devbox/test_interfaces.py b/tests/sdk/devbox/test_interfaces.py
index 41ec46c3a..17ecee345 100644
--- a/tests/sdk/devbox/test_interfaces.py
+++ b/tests/sdk/devbox/test_interfaces.py
@@ -35,6 +35,16 @@ def test_exec_without_callbacks(self, mock_client: Mock, execution_view: MockExe
assert "timeout" not in call_kwargs
mock_client.devboxes.executions.await_completed.assert_not_called()
+ def test_exec_async_forwards_stdin(self, mock_client: Mock, execution_view: MockExecutionView) -> None:
+ mock_client.devboxes.execute_async.return_value = execution_view
+ devbox = Devbox(mock_client, "dbx_123")
+
+ devbox.cmd.exec_async("cat", attach_stdin=True)
+
+ call_kwargs = mock_client.devboxes.execute_async.call_args.kwargs
+ assert call_kwargs["command"] == "cat"
+ assert call_kwargs["attach_stdin"] is True
+
def test_exec_with_stdout_callback(self, mock_client: Mock, mock_stream: Mock) -> None:
"""Test exec with stdout callback."""
execution_async = SimpleNamespace(
@@ -255,30 +265,6 @@ def test_upload(self, mock_client: Mock, tmp_path: Path) -> None:
class TestNetworkInterface:
"""Tests for _NetworkInterface."""
- def test_create_ssh_key(self, mock_client: Mock) -> None:
- """Test create SSH key."""
- ssh_key_response = SimpleNamespace(public_key="ssh-rsa ...")
- mock_client.devboxes.create_ssh_key.return_value = ssh_key_response
-
- devbox = Devbox(mock_client, "dbx_123")
- result = devbox.net.create_ssh_key(
- extra_headers={"X-Custom": "value"},
- extra_query={"param": "value"},
- extra_body={"key": "value"},
- timeout=30.0,
- idempotency_key="key-123",
- )
-
- assert result == ssh_key_response
- mock_client.devboxes.create_ssh_key.assert_called_once_with(
- "dbx_123",
- extra_headers={"X-Custom": "value"},
- extra_query={"param": "value"},
- extra_body={"key": "value"},
- timeout=30.0,
- idempotency_key="key-123",
- )
-
def test_remove_tunnel(self, mock_client: Mock) -> None:
"""Test remove tunnel."""
mock_client.devboxes.remove_tunnel.return_value = object()
diff --git a/tests/sdk/test_agent.py b/tests/sdk/test_agent.py
deleted file mode 100644
index f1bbb083d..000000000
--- a/tests/sdk/test_agent.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""Comprehensive tests for sync Agent class."""
-
-from __future__ import annotations
-
-from unittest.mock import Mock
-
-from tests.sdk.conftest import MockAgentView
-from runloop_api_client.sdk import Agent
-
-
-class TestAgent:
- """Tests for Agent class."""
-
- def test_init(self, mock_client: Mock) -> None:
- """Test Agent initialization."""
- agent = Agent(mock_client, "agt_123")
- assert agent.id == "agt_123"
-
- def test_repr(self, mock_client: Mock) -> None:
- """Test Agent string representation."""
- agent = Agent(mock_client, "agt_123")
- assert repr(agent) == ""
-
- def test_get_info(self, mock_client: Mock, agent_view: MockAgentView) -> None:
- """Test get_info method."""
- mock_client.agents.retrieve.return_value = agent_view
-
- agent = Agent(mock_client, "agt_123")
- result = agent.get_info(
- extra_headers={"X-Custom": "value"},
- extra_query={"param": "value"},
- extra_body={"key": "value"},
- timeout=30.0,
- )
-
- assert result == agent_view
- mock_client.agents.retrieve.assert_called_once_with(
- "agt_123",
- extra_headers={"X-Custom": "value"},
- extra_query={"param": "value"},
- extra_body={"key": "value"},
- timeout=30.0,
- )
diff --git a/tests/sdk/test_async_agent.py b/tests/sdk/test_async_agent.py
deleted file mode 100644
index be7efa845..000000000
--- a/tests/sdk/test_async_agent.py
+++ /dev/null
@@ -1,46 +0,0 @@
-"""Comprehensive tests for async AsyncAgent class."""
-
-from __future__ import annotations
-
-from unittest.mock import AsyncMock
-
-import pytest
-
-from tests.sdk.conftest import MockAgentView
-from runloop_api_client.sdk import AsyncAgent
-
-
-class TestAsyncAgent:
- """Tests for AsyncAgent class."""
-
- def test_init(self, mock_async_client: AsyncMock) -> None:
- """Test AsyncAgent initialization."""
- agent = AsyncAgent(mock_async_client, "agt_123")
- assert agent.id == "agt_123"
-
- def test_repr(self, mock_async_client: AsyncMock) -> None:
- """Test AsyncAgent string representation."""
- agent = AsyncAgent(mock_async_client, "agt_123")
- assert repr(agent) == ""
-
- @pytest.mark.asyncio
- async def test_get_info(self, mock_async_client: AsyncMock, agent_view: MockAgentView) -> None:
- """Test get_info method."""
- mock_async_client.agents.retrieve = AsyncMock(return_value=agent_view)
-
- agent = AsyncAgent(mock_async_client, "agt_123")
- result = await agent.get_info(
- extra_headers={"X-Custom": "value"},
- extra_query={"param": "value"},
- extra_body={"key": "value"},
- timeout=30.0,
- )
-
- assert result == agent_view
- mock_async_client.agents.retrieve.assert_called_once_with(
- "agt_123",
- extra_headers={"X-Custom": "value"},
- extra_query={"param": "value"},
- extra_body={"key": "value"},
- timeout=30.0,
- )
diff --git a/tests/sdk/test_async_benchmark.py b/tests/sdk/test_async_benchmark.py
deleted file mode 100644
index 8046e082e..000000000
--- a/tests/sdk/test_async_benchmark.py
+++ /dev/null
@@ -1,130 +0,0 @@
-"""Comprehensive tests for async AsyncBenchmark class."""
-
-from __future__ import annotations
-
-from types import SimpleNamespace
-from unittest.mock import AsyncMock
-
-from tests.sdk.conftest import MockBenchmarkView, MockBenchmarkRunView
-from runloop_api_client.sdk.async_benchmark import AsyncBenchmark
-from runloop_api_client.sdk.async_benchmark_run import AsyncBenchmarkRun
-
-
-class TestAsyncBenchmark:
- """Tests for AsyncBenchmark class."""
-
- def test_init(self, mock_async_client: AsyncMock) -> None:
- """Test AsyncBenchmark initialization."""
- benchmark = AsyncBenchmark(mock_async_client, "bmd_123")
- assert benchmark.id == "bmd_123"
- assert repr(benchmark) == ""
-
- async def test_get_info(self, mock_async_client: AsyncMock, benchmark_view: MockBenchmarkView) -> None:
- """Test get_info method."""
- mock_async_client.benchmarks.retrieve = AsyncMock(return_value=benchmark_view)
-
- benchmark = AsyncBenchmark(mock_async_client, "bmd_123")
- result = await benchmark.get_info()
-
- assert result == benchmark_view
- mock_async_client.benchmarks.retrieve.assert_awaited_once_with("bmd_123")
-
- async def test_update(self, mock_async_client: AsyncMock, benchmark_view: MockBenchmarkView) -> None:
- """Test update method."""
- benchmark_view.name = "updated-name"
- mock_async_client.benchmarks.update = AsyncMock(return_value=benchmark_view)
-
- benchmark = AsyncBenchmark(mock_async_client, "bmd_123")
- result = await benchmark.update(name="updated-name")
-
- assert result == benchmark_view
- mock_async_client.benchmarks.update.assert_awaited_once_with("bmd_123", name="updated-name")
-
- async def test_run(self, mock_async_client: AsyncMock, benchmark_run_view: MockBenchmarkRunView) -> None:
- """Test run method."""
- mock_async_client.benchmarks.start_run = AsyncMock(return_value=benchmark_run_view)
-
- benchmark = AsyncBenchmark(mock_async_client, "bmd_123")
- result = await benchmark.start_run(run_name="test-run", metadata={"key": "value"})
-
- assert isinstance(result, AsyncBenchmarkRun)
- assert result.id == benchmark_run_view.id
- assert result.benchmark_id == benchmark_run_view.benchmark_id
- mock_async_client.benchmarks.start_run.assert_awaited_once_with(
- benchmark_id="bmd_123", run_name="test-run", metadata={"key": "value"}
- )
-
- async def test_add_scenarios(self, mock_async_client: AsyncMock, benchmark_view: MockBenchmarkView) -> None:
- """Test add_scenarios method."""
- benchmark_view.scenario_ids = ["scn_001", "scn_002"]
- mock_async_client.benchmarks.update_scenarios = AsyncMock(return_value=benchmark_view)
-
- benchmark = AsyncBenchmark(mock_async_client, "bmd_123")
- result = await benchmark.add_scenarios(["scn_001", "scn_002"])
-
- assert result == benchmark_view
- mock_async_client.benchmarks.update_scenarios.assert_awaited_once_with(
- "bmd_123", scenarios_to_add=["scn_001", "scn_002"]
- )
-
- async def test_remove_scenarios(self, mock_async_client: AsyncMock, benchmark_view: MockBenchmarkView) -> None:
- """Test remove_scenarios method."""
- mock_async_client.benchmarks.update_scenarios = AsyncMock(return_value=benchmark_view)
-
- benchmark = AsyncBenchmark(mock_async_client, "bmd_123")
- result = await benchmark.remove_scenarios(["scn_001"])
-
- assert result == benchmark_view
- mock_async_client.benchmarks.update_scenarios.assert_awaited_once_with(
- "bmd_123", scenarios_to_remove=["scn_001"]
- )
-
- async def test_list_runs_single(
- self, mock_async_client: AsyncMock, benchmark_run_view: MockBenchmarkRunView
- ) -> None:
- """Test list_runs method with single result."""
- page = SimpleNamespace(runs=[benchmark_run_view])
- mock_async_client.benchmark_runs.list = AsyncMock(return_value=page)
-
- benchmark = AsyncBenchmark(mock_async_client, "bmd_123")
- result = await benchmark.list_runs()
-
- assert len(result) == 1
- assert isinstance(result[0], AsyncBenchmarkRun)
- assert result[0].id == benchmark_run_view.id
- assert result[0].benchmark_id == benchmark_run_view.benchmark_id
- mock_async_client.benchmark_runs.list.assert_awaited_once_with(benchmark_id="bmd_123")
-
- async def test_list_runs_multiple(self, mock_async_client: AsyncMock) -> None:
- """Test list_runs method with multiple results."""
- run_view1 = MockBenchmarkRunView(id="bmr_001")
- run_view2 = MockBenchmarkRunView(id="bmr_002")
- page = SimpleNamespace(runs=[run_view1, run_view2])
- mock_async_client.benchmark_runs.list = AsyncMock(return_value=page)
-
- benchmark = AsyncBenchmark(mock_async_client, "bmd_123")
- result = await benchmark.list_runs()
-
- assert len(result) == 2
- assert isinstance(result[0], AsyncBenchmarkRun)
- assert isinstance(result[1], AsyncBenchmarkRun)
- assert result[0].id == run_view1.id
- assert result[0].benchmark_id == run_view1.benchmark_id
- assert result[1].id == run_view2.id
- assert result[1].benchmark_id == run_view2.benchmark_id
- mock_async_client.benchmark_runs.list.assert_awaited_once_with(benchmark_id="bmd_123")
-
- async def test_list_runs_with_params(
- self, mock_async_client: AsyncMock, benchmark_run_view: MockBenchmarkRunView
- ) -> None:
- """Test list_runs method with filtering parameters."""
- page = SimpleNamespace(runs=[benchmark_run_view])
- mock_async_client.benchmark_runs.list = AsyncMock(return_value=page)
-
- benchmark = AsyncBenchmark(mock_async_client, "bmd_123")
- result = await benchmark.list_runs(limit=10, name="test-run")
-
- assert len(result) == 1
- mock_async_client.benchmark_runs.list.assert_awaited_once_with(
- benchmark_id="bmd_123", limit=10, name="test-run"
- )
diff --git a/tests/sdk/test_async_benchmark_run.py b/tests/sdk/test_async_benchmark_run.py
deleted file mode 100644
index e796d78f7..000000000
--- a/tests/sdk/test_async_benchmark_run.py
+++ /dev/null
@@ -1,120 +0,0 @@
-"""Comprehensive tests for async AsyncBenchmarkRun class."""
-
-from __future__ import annotations
-
-from types import SimpleNamespace
-from unittest.mock import AsyncMock
-
-from tests.sdk.conftest import MockScenarioRunView, MockBenchmarkRunView
-from runloop_api_client.sdk.async_scenario_run import AsyncScenarioRun
-from runloop_api_client.sdk.async_benchmark_run import AsyncBenchmarkRun
-
-
-class TestAsyncBenchmarkRun:
- """Tests for AsyncBenchmarkRun class."""
-
- def test_init(self, mock_async_client: AsyncMock) -> None:
- """Test AsyncBenchmarkRun initialization."""
- run = AsyncBenchmarkRun(mock_async_client, "bmr_123", "bmd_123")
- assert run.id == "bmr_123"
- assert run.benchmark_id == "bmd_123"
-
- def test_repr(self, mock_async_client: AsyncMock) -> None:
- """Test AsyncBenchmarkRun string representation."""
- run = AsyncBenchmarkRun(mock_async_client, "bmr_123", "bmd_123")
- assert repr(run) == ""
-
- async def test_get_info(self, mock_async_client: AsyncMock, benchmark_run_view: MockBenchmarkRunView) -> None:
- """Test get_info method."""
- mock_async_client.benchmark_runs.retrieve = AsyncMock(return_value=benchmark_run_view)
-
- run = AsyncBenchmarkRun(mock_async_client, "bmr_123", "bmd_123")
- result = await run.get_info()
-
- assert result == benchmark_run_view
- mock_async_client.benchmark_runs.retrieve.assert_awaited_once_with("bmr_123")
-
- async def test_cancel(self, mock_async_client: AsyncMock, benchmark_run_view: MockBenchmarkRunView) -> None:
- """Test cancel method."""
- benchmark_run_view.state = "canceled"
- mock_async_client.benchmark_runs.cancel = AsyncMock(return_value=benchmark_run_view)
-
- run = AsyncBenchmarkRun(mock_async_client, "bmr_123", "bmd_123")
- result = await run.cancel()
-
- assert result == benchmark_run_view
- assert result.state == "canceled"
- mock_async_client.benchmark_runs.cancel.assert_awaited_once_with("bmr_123")
-
- async def test_complete(self, mock_async_client: AsyncMock, benchmark_run_view: MockBenchmarkRunView) -> None:
- """Test complete method."""
- benchmark_run_view.state = "completed"
- mock_async_client.benchmark_runs.complete = AsyncMock(return_value=benchmark_run_view)
-
- run = AsyncBenchmarkRun(mock_async_client, "bmr_123", "bmd_123")
- result = await run.complete()
-
- assert result == benchmark_run_view
- assert result.state == "completed"
- mock_async_client.benchmark_runs.complete.assert_awaited_once_with("bmr_123")
-
- async def test_list_scenario_runs_empty(self, mock_async_client: AsyncMock) -> None:
- """Test list_scenario_runs method with empty results."""
- page = SimpleNamespace(runs=[])
- mock_async_client.benchmark_runs.list_scenario_runs = AsyncMock(return_value=page)
-
- run = AsyncBenchmarkRun(mock_async_client, "bmr_123", "bmd_123")
- result = await run.list_scenario_runs()
-
- assert len(result) == 0
- mock_async_client.benchmark_runs.list_scenario_runs.assert_awaited_once_with("bmr_123")
-
- async def test_list_scenario_runs_single(
- self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView
- ) -> None:
- """Test list_scenario_runs method with single result."""
- page = SimpleNamespace(runs=[scenario_run_view])
- mock_async_client.benchmark_runs.list_scenario_runs = AsyncMock(return_value=page)
-
- run = AsyncBenchmarkRun(mock_async_client, "bmr_123", "bmd_123")
- result = await run.list_scenario_runs()
-
- assert len(result) == 1
- assert isinstance(result[0], AsyncScenarioRun)
- assert result[0].id == scenario_run_view.id
- assert result[0].devbox_id == scenario_run_view.devbox_id
- mock_async_client.benchmark_runs.list_scenario_runs.assert_awaited_once_with("bmr_123")
-
- async def test_list_scenario_runs_multiple(self, mock_async_client: AsyncMock) -> None:
- """Test list_scenario_runs method with multiple results."""
- scenario_run_view1 = MockScenarioRunView(id="scr_001", devbox_id="dev_001")
- scenario_run_view2 = MockScenarioRunView(id="scr_002", devbox_id="dev_002")
- page = SimpleNamespace(runs=[scenario_run_view1, scenario_run_view2])
- mock_async_client.benchmark_runs.list_scenario_runs = AsyncMock(return_value=page)
-
- run = AsyncBenchmarkRun(mock_async_client, "bmr_123", "bmd_123")
- result = await run.list_scenario_runs()
-
- assert len(result) == 2
- assert isinstance(result[0], AsyncScenarioRun)
- assert isinstance(result[1], AsyncScenarioRun)
- assert result[0].id == "scr_001"
- assert result[1].id == "scr_002"
- mock_async_client.benchmark_runs.list_scenario_runs.assert_awaited_once_with("bmr_123")
-
- async def test_list_scenario_runs_with_params(
- self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView
- ) -> None:
- """Test list_scenario_runs method with filtering parameters."""
- page = SimpleNamespace(runs=[scenario_run_view])
- mock_async_client.benchmark_runs.list_scenario_runs = AsyncMock(return_value=page)
-
- run = AsyncBenchmarkRun(mock_async_client, "bmr_123", "bmd_123")
- result = await run.list_scenario_runs(limit=10, state="completed")
-
- assert len(result) == 1
- assert isinstance(result[0], AsyncScenarioRun)
- assert result[0].id == scenario_run_view.id
- mock_async_client.benchmark_runs.list_scenario_runs.assert_awaited_once_with(
- "bmr_123", limit=10, state="completed"
- )
diff --git a/tests/sdk/test_async_ops.py b/tests/sdk/test_async_ops.py
index 82a9535ce..dbe901230 100644
--- a/tests/sdk/test_async_ops.py
+++ b/tests/sdk/test_async_ops.py
@@ -12,13 +12,9 @@
from tests.sdk.conftest import (
MockAxonView,
- MockAgentView,
MockDevboxView,
MockObjectView,
- MockScorerView,
- MockScenarioView,
MockSnapshotView,
- MockBenchmarkView,
MockBlueprintView,
MockGatewayConfigView,
MockNetworkPolicyView,
@@ -26,21 +22,13 @@
)
from runloop_api_client.sdk import (
AsyncAxon,
- AsyncAgent,
AsyncDevbox,
- AsyncScorer,
AsyncAxonOps,
- AsyncAgentOps,
- AsyncScenario,
AsyncSnapshot,
- AsyncBenchmark,
AsyncBlueprint,
AsyncDevboxOps,
- AsyncScorerOps,
AsyncRunloopSDK,
- AsyncScenarioOps,
AsyncSnapshotOps,
- AsyncBenchmarkOps,
AsyncBlueprintOps,
AsyncGatewayConfig,
AsyncNetworkPolicy,
@@ -411,7 +399,7 @@ async def test_upload_from_file(
http_client = AsyncMock()
mock_response = create_mock_httpx_response()
- http_client.put = AsyncMock(return_value=mock_response)
+ http_client.send = AsyncMock(return_value=mock_response)
mock_async_client._client = http_client
ops = AsyncStorageObjectOps(mock_async_client)
@@ -425,57 +413,8 @@ async def test_upload_from_file(
metadata=None,
ttl_ms=None,
)
- http_client.put.assert_awaited_once_with(object_view.upload_url, content=b"test content")
- mock_async_client.objects.complete.assert_awaited_once()
-
- @pytest.mark.asyncio
- async def test_upload_from_text(self, mock_async_client: AsyncMock, object_view: MockObjectView) -> None:
- """Test upload_from_text method."""
- mock_async_client.objects.create = AsyncMock(return_value=object_view)
- mock_async_client.objects.complete = AsyncMock(return_value=object_view)
-
- http_client = AsyncMock()
- mock_response = create_mock_httpx_response()
- http_client.put = AsyncMock(return_value=mock_response)
- mock_async_client._client = http_client
-
- ops = AsyncStorageObjectOps(mock_async_client)
- obj = await ops.upload_from_text("test content", name="test.txt", metadata={"key": "value"})
-
- assert isinstance(obj, AsyncStorageObject)
- assert obj.id == "obj_123"
- mock_async_client.objects.create.assert_awaited_once_with(
- name="test.txt",
- content_type="text",
- metadata={"key": "value"},
- ttl_ms=None,
- )
- http_client.put.assert_awaited_once_with(object_view.upload_url, content="test content")
- mock_async_client.objects.complete.assert_awaited_once()
-
- @pytest.mark.asyncio
- async def test_upload_from_bytes(self, mock_async_client: AsyncMock, object_view: MockObjectView) -> None:
- """Test upload_from_bytes method."""
- mock_async_client.objects.create = AsyncMock(return_value=object_view)
- mock_async_client.objects.complete = AsyncMock(return_value=object_view)
-
- http_client = AsyncMock()
- mock_response = create_mock_httpx_response()
- http_client.put = AsyncMock(return_value=mock_response)
- mock_async_client._client = http_client
-
- ops = AsyncStorageObjectOps(mock_async_client)
- obj = await ops.upload_from_bytes(b"test content", name="test.bin", content_type="binary")
-
- assert isinstance(obj, AsyncStorageObject)
- assert obj.id == "obj_123"
- mock_async_client.objects.create.assert_awaited_once_with(
- name="test.bin",
- content_type="binary",
- metadata=None,
- ttl_ms=None,
- )
- http_client.put.assert_awaited_once_with(object_view.upload_url, content=b"test content")
+ assert http_client.send.call_count == 1
+ assert http_client.send.call_args[0][0].read() == b"test content"
mock_async_client.objects.complete.assert_awaited_once()
@pytest.mark.asyncio
@@ -515,7 +454,7 @@ async def test_upload_from_dir(
http_client = AsyncMock()
mock_response = create_mock_httpx_response()
- http_client.put = AsyncMock(return_value=mock_response)
+ http_client.send = AsyncMock(return_value=mock_response)
mock_async_client._client = http_client
ops = AsyncStorageObjectOps(mock_async_client)
@@ -529,13 +468,13 @@ async def test_upload_from_dir(
metadata={"key": "value"},
ttl_ms=None,
)
- # Verify that put was called with tarball content
- http_client.put.assert_awaited_once()
- call_args = http_client.put.call_args
- assert call_args[0][0] == object_view.upload_url
+ # Verify that a request was sent with tarball content
+ http_client.send.assert_awaited_once()
+ call_args = http_client.send.call_args
+ assert str(call_args[0][0].url) == object_view.upload_url
# Verify it's a valid gzipped tarball
- uploaded_content = call_args[1]["content"]
+ uploaded_content = call_args[0][0].read()
with tarfile.open(fileobj=io.BytesIO(uploaded_content), mode="r:gz") as tar:
members = tar.getmembers()
member_names = [m.name for m in members]
@@ -564,7 +503,7 @@ async def test_upload_from_dir_with_inline_ignore_patterns(
http_client = AsyncMock()
mock_response = create_mock_httpx_response()
- http_client.put = AsyncMock(return_value=mock_response)
+ http_client.send = AsyncMock(return_value=mock_response)
mock_async_client._client = http_client
client = AsyncStorageObjectOps(mock_async_client)
@@ -578,7 +517,7 @@ def ignore_logs_and_build(ti: tarfile.TarInfo) -> tarfile.TarInfo | None:
obj = await client.upload_from_dir(test_dir, ignore=ignore_logs_and_build)
assert isinstance(obj, AsyncStorageObject)
- uploaded_content = http_client.put.call_args[1]["content"]
+ uploaded_content = http_client.send.call_args[0][0].read()
with tarfile.open(fileobj=io.BytesIO(uploaded_content), mode="r:gz") as tar:
names = {m.name for m in tar.getmembers()}
@@ -601,7 +540,7 @@ async def test_upload_from_dir_default_name(
http_client = AsyncMock()
mock_response = create_mock_httpx_response()
- http_client.put = AsyncMock(return_value=mock_response)
+ http_client.send = AsyncMock(return_value=mock_response)
mock_async_client._client = http_client
ops = AsyncStorageObjectOps(mock_async_client)
@@ -632,7 +571,7 @@ async def test_upload_from_dir_with_ttl(
http_client = AsyncMock()
mock_response = create_mock_httpx_response()
- http_client.put = AsyncMock(return_value=mock_response)
+ http_client.send = AsyncMock(return_value=mock_response)
mock_async_client._client = http_client
ops = AsyncStorageObjectOps(mock_async_client)
@@ -659,7 +598,7 @@ async def test_upload_from_dir_empty_directory(
http_client = AsyncMock()
mock_response = create_mock_httpx_response()
- http_client.put = AsyncMock(return_value=mock_response)
+ http_client.send = AsyncMock(return_value=mock_response)
mock_async_client._client = http_client
ops = AsyncStorageObjectOps(mock_async_client)
@@ -673,7 +612,7 @@ async def test_upload_from_dir_empty_directory(
metadata=None,
ttl_ms=None,
)
- http_client.put.assert_awaited_once()
+ http_client.send.assert_awaited_once()
mock_async_client.objects.complete.assert_awaited_once()
@pytest.mark.asyncio
@@ -690,7 +629,7 @@ async def test_upload_from_dir_with_string_path(
http_client = AsyncMock()
mock_response = create_mock_httpx_response()
- http_client.put = AsyncMock(return_value=mock_response)
+ http_client.send = AsyncMock(return_value=mock_response)
mock_async_client._client = http_client
ops = AsyncStorageObjectOps(mock_async_client)
@@ -705,84 +644,10 @@ async def test_upload_from_dir_with_string_path(
metadata=None,
ttl_ms=None,
)
- http_client.put.assert_awaited_once()
+ http_client.send.assert_awaited_once()
mock_async_client.objects.complete.assert_awaited_once()
-class TestAsyncScorerOps:
- """Tests for AsyncScorerOps class."""
-
- @pytest.mark.asyncio
- async def test_create(self, mock_async_client: AsyncMock, scorer_view: MockScorerView) -> None:
- """Test create method."""
- mock_async_client.scenarios.scorers.create = AsyncMock(return_value=scorer_view)
-
- ops = AsyncScorerOps(mock_async_client)
- scorer = await ops.create(
- bash_script="echo 'score=1.0'",
- type="test_scorer",
- )
-
- assert isinstance(scorer, AsyncScorer)
- assert scorer.id == "sco_123"
- mock_async_client.scenarios.scorers.create.assert_awaited_once()
-
- def test_from_id(self, mock_async_client: AsyncMock) -> None:
- """Test from_id method."""
- ops = AsyncScorerOps(mock_async_client)
- scorer = ops.from_id("sco_123")
-
- assert isinstance(scorer, AsyncScorer)
- assert scorer.id == "sco_123"
-
- @pytest.mark.asyncio
- async def test_list_empty(self, mock_async_client: AsyncMock) -> None:
- """Test list method with empty results."""
- page = SimpleNamespace(scorers=[])
- mock_async_client.scenarios.scorers.list = AsyncMock(return_value=page)
-
- ops = AsyncScorerOps(mock_async_client)
- scorers = await ops.list(limit=10)
-
- assert len(scorers) == 0
- mock_async_client.scenarios.scorers.list.assert_awaited_once()
-
- @pytest.mark.asyncio
- async def test_list_single(self, mock_async_client: AsyncMock, scorer_view: MockScorerView) -> None:
- """Test list method with single result."""
- page = SimpleNamespace(scorers=[scorer_view])
- mock_async_client.scenarios.scorers.list = AsyncMock(return_value=page)
-
- ops = AsyncScorerOps(mock_async_client)
- scorers = await ops.list(
- limit=10,
- starting_after="scorer_000",
- )
-
- assert len(scorers) == 1
- assert isinstance(scorers[0], AsyncScorer)
- assert scorers[0].id == "sco_123"
- mock_async_client.scenarios.scorers.list.assert_awaited_once()
-
- @pytest.mark.asyncio
- async def test_list_multiple(self, mock_async_client: AsyncMock) -> None:
- """Test list method with multiple results."""
- scorer_view1 = MockScorerView(id="scorer_001", type="scorer-1")
- scorer_view2 = MockScorerView(id="scorer_002", type="scorer-2")
- page = SimpleNamespace(scorers=[scorer_view1, scorer_view2])
- mock_async_client.scenarios.scorers.list = AsyncMock(return_value=page)
-
- ops = AsyncScorerOps(mock_async_client)
- scorers = await ops.list(limit=10)
-
- assert len(scorers) == 2
- assert isinstance(scorers[0], AsyncScorer)
- assert isinstance(scorers[1], AsyncScorer)
- assert scorers[0].id == "scorer_001"
- assert scorers[1].id == "scorer_002"
- mock_async_client.scenarios.scorers.list.assert_awaited_once()
-
-
class TestAsyncAxonOps:
"""Tests for AsyncAxonOps class."""
@@ -851,468 +716,6 @@ async def test_list_multiple(self, mock_async_client: AsyncMock) -> None:
mock_async_client.axons.list.assert_awaited_once()
-class TestAsyncAgentClient:
- """Tests for AsyncAgentClient class."""
-
- @pytest.mark.asyncio
- async def test_create(self, mock_async_client: AsyncMock, agent_view: MockAgentView) -> None:
- """Test create method."""
- mock_async_client.agents.create = AsyncMock(return_value=agent_view)
-
- client = AsyncAgentOps(mock_async_client)
- agent = await client.create(
- name="test-agent",
- version="1.2.3",
- )
-
- assert isinstance(agent, AsyncAgent)
- assert agent.id == "agt_123"
- mock_async_client.agents.create.assert_called_once()
-
- def test_from_id(self, mock_async_client: AsyncMock) -> None:
- """Test from_id method."""
- client = AsyncAgentOps(mock_async_client)
- agent = client.from_id("agt_123")
-
- assert isinstance(agent, AsyncAgent)
- assert agent.id == "agt_123"
-
- @pytest.mark.asyncio
- async def test_list(self, mock_async_client: AsyncMock) -> None:
- """Test list method."""
- # Create three agent views with different data
- agent_view_1 = MockAgentView(
- id="agent_001",
- name="first-agent",
- create_time_ms=1234567890000,
- is_public=False,
- source=None,
- )
- agent_view_2 = MockAgentView(
- id="agent_002",
- name="second-agent",
- create_time_ms=1234567891000,
- is_public=True,
- source={"type": "git", "git": {"repository": "https://github.com/example/repo"}},
- )
- agent_view_3 = MockAgentView(
- id="agent_003",
- name="third-agent",
- create_time_ms=1234567892000,
- is_public=False,
- source={"type": "npm", "npm": {"package_name": "example-package"}},
- )
-
- page = SimpleNamespace(agents=[agent_view_1, agent_view_2, agent_view_3])
- mock_async_client.agents.list = AsyncMock(return_value=page)
-
- # Mock retrieve to return the corresponding agent_view when called
- async def mock_retrieve(agent_id: str):
- if agent_id == "agent_001":
- return agent_view_1
- elif agent_id == "agent_002":
- return agent_view_2
- elif agent_id == "agent_003":
- return agent_view_3
- return None
-
- mock_async_client.agents.retrieve = AsyncMock(side_effect=mock_retrieve)
-
- client = AsyncAgentOps(mock_async_client)
- agents = await client.list(
- limit=10,
- starting_after="agent_000",
- )
-
- # Verify we got three agents
- assert len(agents) == 3
- assert all(isinstance(agent, AsyncAgent) for agent in agents)
-
- # Verify the agent IDs
- assert agents[0].id == "agent_001"
- assert agents[1].id == "agent_002"
- assert agents[2].id == "agent_003"
-
- # Test that get_info() retrieves the AgentView for the first agent
- info = await agents[0].get_info()
- assert info.id == "agent_001"
- assert info.name == "first-agent"
- assert info.create_time_ms == 1234567890000
- assert info.is_public is False
- assert info.source is None
-
- # Test that get_info() retrieves the AgentView for the second agent
- info = await agents[1].get_info()
- assert info.id == "agent_002"
- assert info.name == "second-agent"
- assert info.create_time_ms == 1234567891000
- assert info.is_public is True
- assert info.source == {"type": "git", "git": {"repository": "https://github.com/example/repo"}}
-
- # Test that get_info() retrieves the AgentView for the third agent
- info = await agents[2].get_info()
- assert info.id == "agent_003"
- assert info.name == "third-agent"
- assert info.create_time_ms == 1234567892000
- assert info.is_public is False
- assert info.source == {"type": "npm", "npm": {"package_name": "example-package"}}
-
- # Verify that agents.retrieve was called three times (once for each get_info)
- assert mock_async_client.agents.retrieve.call_count == 3
-
- mock_async_client.agents.list.assert_called_once()
-
- @pytest.mark.asyncio
- async def test_create_from_npm(self, mock_async_client: AsyncMock, agent_view: MockAgentView) -> None:
- """Test create_from_npm factory method."""
- mock_async_client.agents.create = AsyncMock(return_value=agent_view)
-
- client = AsyncAgentOps(mock_async_client)
- agent = await client.create_from_npm(
- name="test-agent",
- package_name="@runloop/example-agent",
- version="1.2.3",
- )
-
- assert isinstance(agent, AsyncAgent)
- assert agent.id == "agt_123"
- mock_async_client.agents.create.assert_awaited_once_with(
- source={
- "type": "npm",
- "npm": {
- "package_name": "@runloop/example-agent",
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
- @pytest.mark.asyncio
- async def test_create_from_npm_with_all_options(
- self, mock_async_client: AsyncMock, agent_view: MockAgentView
- ) -> None:
- """Test create_from_npm factory method with all optional parameters."""
- mock_async_client.agents.create = AsyncMock(return_value=agent_view)
-
- client = AsyncAgentOps(mock_async_client)
- agent = await client.create_from_npm(
- name="test-agent",
- package_name="@runloop/example-agent",
- registry_url="https://registry.example.com",
- agent_setup=["npm install", "npm run setup"],
- version="1.2.3",
- extra_headers={"X-Custom": "header"},
- )
-
- assert isinstance(agent, AsyncAgent)
- assert agent.id == "agt_123"
- mock_async_client.agents.create.assert_awaited_once_with(
- source={
- "type": "npm",
- "npm": {
- "package_name": "@runloop/example-agent",
- "registry_url": "https://registry.example.com",
- "agent_setup": ["npm install", "npm run setup"],
- },
- },
- name="test-agent",
- version="1.2.3",
- extra_headers={"X-Custom": "header"},
- )
-
- @pytest.mark.asyncio
- async def test_create_from_npm_raises_when_source_provided(self, mock_async_client: AsyncMock) -> None:
- """Test create_from_npm raises ValueError when source is provided in params."""
- client = AsyncAgentOps(mock_async_client)
-
- with pytest.raises(ValueError, match="Cannot specify 'source' when using create_from_npm"):
- await client.create_from_npm(
- name="test-agent",
- package_name="@runloop/example-agent",
- version="1.2.3",
- source={"type": "git", "git": {"repository": "https://github.com/example/repo"}},
- )
-
- @pytest.mark.asyncio
- async def test_create_from_pip(self, mock_async_client: AsyncMock, agent_view: MockAgentView) -> None:
- """Test create_from_pip factory method."""
- mock_async_client.agents.create = AsyncMock(return_value=agent_view)
-
- client = AsyncAgentOps(mock_async_client)
- agent = await client.create_from_pip(
- name="test-agent",
- package_name="runloop-example-agent",
- version="1.2.3",
- )
-
- assert isinstance(agent, AsyncAgent)
- assert agent.id == "agt_123"
- mock_async_client.agents.create.assert_awaited_once_with(
- source={
- "type": "pip",
- "pip": {
- "package_name": "runloop-example-agent",
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
- @pytest.mark.asyncio
- async def test_create_from_pip_with_all_options(
- self, mock_async_client: AsyncMock, agent_view: MockAgentView
- ) -> None:
- """Test create_from_pip factory method with all optional parameters."""
- mock_async_client.agents.create = AsyncMock(return_value=agent_view)
-
- client = AsyncAgentOps(mock_async_client)
- agent = await client.create_from_pip(
- name="test-agent",
- package_name="runloop-example-agent",
- registry_url="https://pypi.example.com",
- agent_setup=["pip install extra-deps"],
- version="1.2.3",
- )
-
- assert isinstance(agent, AsyncAgent)
- assert agent.id == "agt_123"
- mock_async_client.agents.create.assert_awaited_once_with(
- source={
- "type": "pip",
- "pip": {
- "package_name": "runloop-example-agent",
- "registry_url": "https://pypi.example.com",
- "agent_setup": ["pip install extra-deps"],
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
- @pytest.mark.asyncio
- async def test_create_from_git(self, mock_async_client: AsyncMock, agent_view: MockAgentView) -> None:
- """Test create_from_git factory method."""
- mock_async_client.agents.create = AsyncMock(return_value=agent_view)
-
- client = AsyncAgentOps(mock_async_client)
- agent = await client.create_from_git(
- name="test-agent",
- repository="https://github.com/example/agent-repo",
- version="1.2.3",
- )
-
- assert isinstance(agent, AsyncAgent)
- assert agent.id == "agt_123"
- mock_async_client.agents.create.assert_awaited_once_with(
- source={
- "type": "git",
- "git": {
- "repository": "https://github.com/example/agent-repo",
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
- @pytest.mark.asyncio
- async def test_create_from_git_with_all_options(
- self, mock_async_client: AsyncMock, agent_view: MockAgentView
- ) -> None:
- """Test create_from_git factory method with all optional parameters."""
- mock_async_client.agents.create = AsyncMock(return_value=agent_view)
-
- client = AsyncAgentOps(mock_async_client)
- agent = await client.create_from_git(
- name="test-agent",
- repository="https://github.com/example/agent-repo",
- ref="develop",
- agent_setup=["npm install", "npm run build"],
- version="1.2.3",
- )
-
- assert isinstance(agent, AsyncAgent)
- assert agent.id == "agt_123"
- mock_async_client.agents.create.assert_awaited_once_with(
- source={
- "type": "git",
- "git": {
- "repository": "https://github.com/example/agent-repo",
- "ref": "develop",
- "agent_setup": ["npm install", "npm run build"],
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
- @pytest.mark.asyncio
- async def test_create_from_object(self, mock_async_client: AsyncMock, agent_view: MockAgentView) -> None:
- """Test create_from_object factory method."""
- mock_async_client.agents.create = AsyncMock(return_value=agent_view)
-
- client = AsyncAgentOps(mock_async_client)
- agent = await client.create_from_object(
- name="test-agent",
- object_id="obj_123",
- version="1.2.3",
- )
-
- assert isinstance(agent, AsyncAgent)
- assert agent.id == "agt_123"
- mock_async_client.agents.create.assert_awaited_once_with(
- source={
- "type": "object",
- "object": {
- "object_id": "obj_123",
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
- @pytest.mark.asyncio
- async def test_create_from_object_with_agent_setup(
- self, mock_async_client: AsyncMock, agent_view: MockAgentView
- ) -> None:
- """Test create_from_object factory method with agent_setup."""
- mock_async_client.agents.create = AsyncMock(return_value=agent_view)
-
- client = AsyncAgentOps(mock_async_client)
- agent = await client.create_from_object(
- name="test-agent",
- object_id="obj_123",
- agent_setup=["chmod +x setup.sh", "./setup.sh"],
- version="1.2.3",
- )
-
- assert isinstance(agent, AsyncAgent)
- assert agent.id == "agt_123"
- mock_async_client.agents.create.assert_awaited_once_with(
- source={
- "type": "object",
- "object": {
- "object_id": "obj_123",
- "agent_setup": ["chmod +x setup.sh", "./setup.sh"],
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
-
-class TestAsyncScenarioOps:
- """Tests for AsyncScenarioOps class."""
-
- def test_from_id(self, mock_async_client: AsyncMock) -> None:
- """Test from_id method."""
-
- ops = AsyncScenarioOps(mock_async_client)
- scenario = ops.from_id("scn_123")
-
- assert isinstance(scenario, AsyncScenario)
- assert scenario.id == "scn_123"
-
- @pytest.mark.asyncio
- async def test_list_empty(self, mock_async_client: AsyncMock) -> None:
- """Test list method with empty results."""
- page = SimpleNamespace(scenarios=[])
- mock_async_client.scenarios.list = AsyncMock(return_value=page)
-
- ops = AsyncScenarioOps(mock_async_client)
- scenarios = await ops.list(limit=10)
-
- assert len(scenarios) == 0
- mock_async_client.scenarios.list.assert_awaited_once()
-
- @pytest.mark.asyncio
- async def test_list_single(self, mock_async_client: AsyncMock, scenario_view: MockScenarioView) -> None:
- """Test list method with single result."""
- page = SimpleNamespace(scenarios=[scenario_view])
- mock_async_client.scenarios.list = AsyncMock(return_value=page)
-
- ops = AsyncScenarioOps(mock_async_client)
- scenarios = await ops.list(limit=10)
-
- assert len(scenarios) == 1
- assert isinstance(scenarios[0], AsyncScenario)
- assert scenarios[0].id == "scn_123"
- mock_async_client.scenarios.list.assert_awaited_once()
-
- @pytest.mark.asyncio
- async def test_list_multiple(self, mock_async_client: AsyncMock) -> None:
- """Test list method with multiple results."""
- scenario_view1 = MockScenarioView(id="scn_001", name="scenario-1")
- scenario_view2 = MockScenarioView(id="scn_002", name="scenario-2")
- page = SimpleNamespace(scenarios=[scenario_view1, scenario_view2])
- mock_async_client.scenarios.list = AsyncMock(return_value=page)
-
- ops = AsyncScenarioOps(mock_async_client)
- scenarios = await ops.list(limit=10)
-
- assert len(scenarios) == 2
- assert isinstance(scenarios[0], AsyncScenario)
- assert isinstance(scenarios[1], AsyncScenario)
- assert scenarios[0].id == "scn_001"
- assert scenarios[1].id == "scn_002"
- mock_async_client.scenarios.list.assert_awaited_once()
-
-
-class TestAsyncBenchmarkOps:
- """Tests for AsyncBenchmarkOps class."""
-
- @pytest.mark.asyncio
- async def test_create(self, mock_async_client: AsyncMock, benchmark_view: MockBenchmarkView) -> None:
- """Test create method."""
- mock_async_client.benchmarks.create = AsyncMock(return_value=benchmark_view)
-
- ops = AsyncBenchmarkOps(mock_async_client)
- benchmark = await ops.create(name="test-benchmark", scenario_ids=["scn_001", "scn_002"])
-
- assert isinstance(benchmark, AsyncBenchmark)
- assert benchmark.id == "bmd_123"
- mock_async_client.benchmarks.create.assert_awaited_once_with(
- name="test-benchmark", scenario_ids=["scn_001", "scn_002"]
- )
-
- def test_from_id(self, mock_async_client: AsyncMock) -> None:
- """Test from_id method."""
- ops = AsyncBenchmarkOps(mock_async_client)
- benchmark = ops.from_id("bmd_123")
-
- assert isinstance(benchmark, AsyncBenchmark)
- assert benchmark.id == "bmd_123"
-
- @pytest.mark.asyncio
- async def test_list_multiple(self, mock_async_client: AsyncMock) -> None:
- """Test list method with multiple results."""
- benchmark_view1 = MockBenchmarkView(id="bmd_001", name="benchmark-1")
- benchmark_view2 = MockBenchmarkView(id="bmd_002", name="benchmark-2")
- page = SimpleNamespace(benchmarks=[benchmark_view1, benchmark_view2])
- mock_async_client.benchmarks.list = AsyncMock(return_value=page)
-
- ops = AsyncBenchmarkOps(mock_async_client)
- benchmarks = await ops.list(limit=10)
-
- assert len(benchmarks) == 2
- assert isinstance(benchmarks[0], AsyncBenchmark)
- assert isinstance(benchmarks[1], AsyncBenchmark)
- assert benchmarks[0].id == "bmd_001"
- assert benchmarks[1].id == "bmd_002"
- mock_async_client.benchmarks.list.assert_awaited_once_with(limit=10)
-
- @pytest.mark.asyncio
- async def test_list_with_name_filter(self, mock_async_client: AsyncMock, benchmark_view: MockBenchmarkView) -> None:
- """Test list method with name filter."""
- page = SimpleNamespace(benchmarks=[benchmark_view])
- mock_async_client.benchmarks.list = AsyncMock(return_value=page)
-
- ops = AsyncBenchmarkOps(mock_async_client)
- benchmarks = await ops.list(name="test-benchmark", limit=10)
-
- assert len(benchmarks) == 1
- mock_async_client.benchmarks.list.assert_awaited_once_with(name="test-benchmark", limit=10)
-
-
class TestAsyncNetworkPolicyOps:
"""Tests for AsyncNetworkPolicyOps class."""
@@ -1469,12 +872,9 @@ def test_init(self) -> None:
"""Test AsyncRunloopSDK initialization."""
runloop = AsyncRunloopSDK(bearer_token="test-token")
assert runloop.api is not None
- assert isinstance(runloop.agent, AsyncAgentOps)
- assert isinstance(runloop.benchmark, AsyncBenchmarkOps)
assert isinstance(runloop.devbox, AsyncDevboxOps)
assert isinstance(runloop.gateway_config, AsyncGatewayConfigOps)
assert isinstance(runloop.network_policy, AsyncNetworkPolicyOps)
- assert isinstance(runloop.scorer, AsyncScorerOps)
assert isinstance(runloop.snapshot, AsyncSnapshotOps)
assert isinstance(runloop.blueprint, AsyncBlueprintOps)
assert isinstance(runloop.storage_object, AsyncStorageObjectOps)
diff --git a/tests/sdk/test_async_scenario.py b/tests/sdk/test_async_scenario.py
deleted file mode 100644
index cffca9b82..000000000
--- a/tests/sdk/test_async_scenario.py
+++ /dev/null
@@ -1,121 +0,0 @@
-"""Comprehensive tests for async AsyncScenario class."""
-
-from __future__ import annotations
-
-from unittest.mock import AsyncMock
-
-from tests.sdk.conftest import MockScenarioView, MockScenarioRunView
-from runloop_api_client.sdk import AsyncScenario
-
-
-class TestAsyncScenario:
- """Tests for AsyncScenario class."""
-
- def test_init(self, mock_async_client: AsyncMock) -> None:
- """Test AsyncScenario initialization."""
- scenario = AsyncScenario(mock_async_client, "scn_123")
- assert scenario.id == "scn_123"
-
- def test_repr(self, mock_async_client: AsyncMock) -> None:
- """Test AsyncScenario string representation."""
- scenario = AsyncScenario(mock_async_client, "scn_123")
- assert repr(scenario) == ""
-
- async def test_get_info(self, mock_async_client: AsyncMock, scenario_view: MockScenarioView) -> None:
- """Test get_info method."""
- mock_async_client.scenarios.retrieve = AsyncMock(return_value=scenario_view)
-
- scenario = AsyncScenario(mock_async_client, "scn_123")
- result = await scenario.get_info()
-
- assert result == scenario_view
- mock_async_client.scenarios.retrieve.assert_awaited_once_with("scn_123")
-
- async def test_update(self, mock_async_client: AsyncMock, scenario_view: MockScenarioView) -> None:
- """Test update method."""
- mock_async_client.scenarios.update = AsyncMock(return_value=scenario_view)
-
- scenario = AsyncScenario(mock_async_client, "scn_123")
- result = await scenario.update(
- name="updated-scenario",
- metadata={"env": "test"},
- environment_parameters={
- "blueprint_id": "bp_456",
- },
- input_context={
- "problem_statement": "Updated problem statement",
- },
- reference_output="--- a/main.py\n+++ b/main.py\n@@ -1 +1 @@\n-old\n+new",
- required_environment_variables=["API_KEY", "DEBUG"],
- required_secret_names=["DB_PASSWORD", "JWT_SECRET"],
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "test-scorer",
- "scorer": {
- "type": "command_scorer",
- "command": "echo 'score=1.0'",
- },
- "weight": 1.0,
- }
- ],
- },
- validation_type="FORWARD",
- )
-
- assert result == scenario_view
- mock_async_client.scenarios.update.assert_awaited_once_with(
- "scn_123",
- name="updated-scenario",
- metadata={"env": "test"},
- environment_parameters={
- "blueprint_id": "bp_456",
- },
- input_context={
- "problem_statement": "Updated problem statement",
- },
- reference_output="--- a/main.py\n+++ b/main.py\n@@ -1 +1 @@\n-old\n+new",
- required_environment_variables=["API_KEY", "DEBUG"],
- required_secret_names=["DB_PASSWORD", "JWT_SECRET"],
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "test-scorer",
- "scorer": {
- "type": "command_scorer",
- "command": "echo 'score=1.0'",
- },
- "weight": 1.0,
- }
- ],
- },
- validation_type="FORWARD",
- )
-
- async def test_run_async(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None:
- """Test run_async method returns AsyncScenarioRun wrapper."""
- mock_async_client.scenarios.start_run = AsyncMock(return_value=scenario_run_view)
-
- scenario = AsyncScenario(mock_async_client, "scn_123")
- run = await scenario.run_async(run_name="test-run")
-
- assert run.id == "scr_123"
- assert run.devbox_id == "dbx_123"
- mock_async_client.scenarios.start_run.assert_awaited_once_with(
- scenario_id="scn_123",
- run_name="test-run",
- )
-
- async def test_run(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None:
- """Test run method."""
- mock_async_client.scenarios.start_run_and_await_env_ready = AsyncMock(return_value=scenario_run_view)
-
- scenario = AsyncScenario(mock_async_client, "scn_123")
- run = await scenario.run(run_name="test-run")
-
- assert run.id == "scr_123"
- assert run.devbox_id == "dbx_123"
- mock_async_client.scenarios.start_run_and_await_env_ready.assert_awaited_once_with(
- scenario_id="scn_123",
- run_name="test-run",
- )
diff --git a/tests/sdk/test_async_scenario_builder.py b/tests/sdk/test_async_scenario_builder.py
deleted file mode 100644
index e20d99843..000000000
--- a/tests/sdk/test_async_scenario_builder.py
+++ /dev/null
@@ -1,292 +0,0 @@
-"""Unit tests for AsyncScenarioBuilder class."""
-
-from __future__ import annotations
-
-from unittest.mock import AsyncMock, MagicMock
-
-import pytest
-
-from runloop_api_client.sdk import AsyncSnapshot, AsyncBlueprint, ScenarioPreview, AsyncScenarioBuilder
-from runloop_api_client.types.scoring_function_param import ScorerTestBasedScoringFunctionTestFile
-
-
-class TestAsyncScenarioBuilder:
- """Tests for the asynchronous AsyncScenarioBuilder."""
-
- @pytest.fixture
- def mock_async_client(self) -> MagicMock:
- """Create a mock AsyncRunloop client."""
- client = MagicMock()
- client.scenarios = MagicMock()
- client.scenarios.create = AsyncMock()
- return client
-
- @pytest.fixture
- def mock_blueprint(self, mock_async_client: MagicMock) -> AsyncBlueprint:
- """Create a mock AsyncBlueprint object."""
- return AsyncBlueprint(mock_async_client, "bp-123")
-
- @pytest.fixture
- def mock_snapshot(self, mock_async_client: MagicMock) -> AsyncSnapshot:
- """Create a mock AsyncSnapshot object."""
- return AsyncSnapshot(mock_async_client, "snap-123")
-
- @pytest.fixture
- def mock_builder(self, mock_async_client: MagicMock) -> AsyncScenarioBuilder:
- """Create an AsyncScenarioBuilder instance with mock client."""
- return AsyncScenarioBuilder("test-scenario", mock_async_client)
-
- def test_instantiation(self, mock_async_client: MagicMock) -> None:
- """Test builder initialization and repr."""
- builder = AsyncScenarioBuilder("my-scenario", mock_async_client)
-
- assert builder._client is mock_async_client
- assert builder._name == "my-scenario"
- assert builder.name == "my-scenario"
- assert repr(builder) == ""
-
- def test_from_blueprint_and_snapshot(
- self, mock_builder: AsyncScenarioBuilder, mock_blueprint: AsyncBlueprint, mock_snapshot: AsyncSnapshot
- ) -> None:
- """Test blueprint/snapshot setting returns self and are mutually exclusive."""
- # from_blueprint returns self and sets blueprint
- result = mock_builder.from_blueprint(mock_blueprint)
- assert result is mock_builder
- assert mock_builder._blueprint is mock_blueprint
- assert mock_builder._snapshot is None
-
- # from_snapshot returns self, sets snapshot, and clears blueprint
- result = mock_builder.from_snapshot(mock_snapshot)
- assert result is mock_builder
- assert mock_builder._snapshot is mock_snapshot
- assert mock_builder._blueprint is None
-
- # from_blueprint clears snapshot
- mock_builder.from_blueprint(mock_blueprint)
- assert mock_builder._blueprint is mock_blueprint
- assert mock_builder._snapshot is None
-
- def test_scorers(self, mock_builder: AsyncScenarioBuilder) -> None:
- """Test all scorer types, optional params, and multiple scorers."""
- # Test scorer with test files
- test_files: list[ScorerTestBasedScoringFunctionTestFile] = [
- {"file_path": "test_main.py", "file_contents": "def test_foo(): pass"}
- ]
- result = mock_builder.add_test_command_scorer(
- "test-scorer", test_command="pytest", weight=2.0, test_files=test_files
- )
- assert result is mock_builder
- assert mock_builder._scorers[0]["name"] == "test-scorer"
- assert mock_builder._scorers[0]["weight"] == 2.0
- assert mock_builder._scorers[0]["scorer"]["type"] == "test_based_scorer"
- assert mock_builder._scorers[0]["scorer"].get("test_command") == "pytest"
- assert mock_builder._scorers[0]["scorer"].get("test_files") == test_files
-
- # Command scorer
- mock_builder.add_shell_command_scorer("cmd-scorer", command="./check.sh")
- assert mock_builder._scorers[1]["scorer"]["type"] == "command_scorer"
- assert mock_builder._scorers[1]["scorer"].get("command") == "./check.sh"
-
- # Bash scorer
- mock_builder.add_bash_script_scorer("bash-scorer", bash_script="echo 'score=1.0'")
- assert mock_builder._scorers[2]["scorer"]["type"] == "bash_script_scorer"
- assert mock_builder._scorers[2]["scorer"].get("bash_script") == "echo 'score=1.0'"
-
- # Python scorer with optional params
- mock_builder.add_python_script_scorer(
- "python-scorer",
- python_script="print('1.0')",
- python_version_constraint=">=3.10",
- requirements_contents="numpy",
- )
- assert mock_builder._scorers[3]["scorer"]["type"] == "python_script_scorer"
- assert mock_builder._scorers[3]["scorer"].get("python_version_constraint") == ">=3.10"
- assert mock_builder._scorers[3]["scorer"].get("requirements_contents") == "numpy"
-
- # AST grep scorer with optional lang
- mock_builder.add_ast_grep_scorer("ast-scorer", pattern="$A.foo()", search_directory="/src", lang="python")
- assert mock_builder._scorers[4]["scorer"]["type"] == "ast_grep_scorer"
- assert mock_builder._scorers[4]["scorer"].get("pattern") == "$A.foo()"
- assert mock_builder._scorers[4]["scorer"].get("lang") == "python"
-
- # Custom scorer with optional params
- mock_builder.add_custom_scorer(
- "custom-scorer", custom_scorer_type="my_scorer", scorer_params={"threshold": 0.5}
- )
- assert mock_builder._scorers[5]["scorer"]["type"] == "custom_scorer"
- assert mock_builder._scorers[5]["scorer"].get("custom_scorer_type") == "my_scorer"
- assert mock_builder._scorers[5]["scorer"].get("scorer_params") == {"threshold": 0.5}
-
- # Verify multiple scorers accumulated
- assert len(mock_builder._scorers) == 6
-
- def test_add_scorer_rejects_invalid_weight(self, mock_builder: AsyncScenarioBuilder) -> None:
- """Test that adding a scorer with zero or negative weight raises ValueError."""
- with pytest.raises(ValueError, match="Scorer weight must be positive"):
- mock_builder.add_bash_script_scorer("bad", bash_script="echo 1", weight=0.0)
-
- with pytest.raises(ValueError, match="Scorer weight must be positive"):
- mock_builder.add_bash_script_scorer("bad", bash_script="echo 1", weight=-1.0)
-
- def test_build_validation(self, mock_builder: AsyncScenarioBuilder) -> None:
- """Test build raises for missing required fields."""
- # Missing problem statement
- mock_builder.add_test_command_scorer("test", test_command="pytest")
- with pytest.raises(ValueError, match="Problem statement is required"):
- mock_builder.build()
-
- # Missing scorer (new builder)
- builder2 = AsyncScenarioBuilder("test2", mock_builder._client)
- builder2.with_problem_statement("Fix the bug")
- with pytest.raises(ValueError, match="At least one scorer is required"):
- builder2.build()
-
- def test_build_with_all_options(self, mock_builder: AsyncScenarioBuilder, mock_blueprint: AsyncBlueprint) -> None:
- """Test build with all optional fields set."""
- mock_builder.with_problem_statement("Fix the bug")
- mock_builder.with_additional_context({"hint": "line 42"})
- mock_builder.add_test_command_scorer("tests", test_command="pytest")
- mock_builder.from_blueprint(mock_blueprint)
- mock_builder.with_working_directory("/app")
- mock_builder.with_metadata({"team": "infra"})
- mock_builder.with_reference_output("diff content")
- mock_builder.with_required_env_vars(["API_KEY"])
- mock_builder.with_required_secrets(["db_pass"])
- mock_builder.with_validation_type("FORWARD")
-
- params = mock_builder.build()
-
- assert params["name"] == "test-scenario"
- assert params["input_context"]["problem_statement"] == "Fix the bug"
- assert params["input_context"].get("additional_context") == {"hint": "line 42"}
- env_params = params.get("environment_parameters")
- assert env_params is not None
- assert env_params.get("blueprint_id") == "bp-123"
- assert env_params.get("working_directory") == "/app"
- assert params.get("metadata") == {"team": "infra"}
- assert params.get("reference_output") == "diff content"
- assert params.get("required_environment_variables") == ["API_KEY"]
- assert params.get("required_secret_names") == ["db_pass"]
- assert params.get("validation_type") == "FORWARD"
-
- def test_build_normalizes_weights(self, mock_builder: AsyncScenarioBuilder) -> None:
- """Test that build normalizes scorer weights to sum to 1.0."""
- mock_builder.with_problem_statement("Fix the bug")
- mock_builder.add_bash_script_scorer("scorer1", bash_script="echo 1", weight=1.0)
- mock_builder.add_bash_script_scorer("scorer2", bash_script="echo 2", weight=2.0)
- mock_builder.add_bash_script_scorer("scorer3", bash_script="echo 3", weight=3.0)
-
- params = mock_builder.build()
- scorers = list(params["scoring_contract"]["scoring_function_parameters"])
-
- # Weights 1, 2, 3 should normalize to 1/6, 2/6, 3/6
- assert len(scorers) == 3
- assert abs(scorers[0]["weight"] - 1 / 6) < 0.0001
- assert abs(scorers[1]["weight"] - 2 / 6) < 0.0001
- assert abs(scorers[2]["weight"] - 3 / 6) < 0.0001
-
- # Total should be 1.0
- total = sum(s["weight"] for s in scorers)
- assert abs(total - 1.0) < 0.0001
-
- @pytest.mark.asyncio
- async def test_push_calls_api_and_returns_scenario(
- self, mock_builder: AsyncScenarioBuilder, mock_async_client: MagicMock
- ) -> None:
- """Test push() calls API with correct params and returns AsyncScenario."""
- mock_async_client.scenarios.create.return_value.id = "scn-new-123"
-
- mock_builder.with_problem_statement("Fix the bug")
- mock_builder.add_test_command_scorer("tests", test_command="pytest")
-
- scenario = await mock_builder.push()
-
- mock_async_client.scenarios.create.assert_called_once()
- call_kwargs = mock_async_client.scenarios.create.call_args.kwargs
- assert call_kwargs["name"] == "test-scenario"
- assert call_kwargs["input_context"]["problem_statement"] == "Fix the bug"
-
- assert scenario.id == "scn-new-123"
-
- def test_fluent_chaining(self, mock_builder: AsyncScenarioBuilder, mock_blueprint: AsyncBlueprint) -> None:
- """Test that all builder methods can be chained fluently."""
- result = (
- mock_builder.from_blueprint(mock_blueprint)
- .with_working_directory("/app")
- .with_problem_statement("Fix the bug")
- .with_additional_context({"hint": "check main.py"})
- .add_test_command_scorer("tests", test_command="pytest")
- .with_metadata({"team": "infra"})
- .with_reference_output("diff content")
- .with_required_env_vars(["API_KEY"])
- .with_required_secrets(["secret"])
- .with_validation_type("FORWARD")
- )
-
- assert result is mock_builder
- assert mock_builder._blueprint is mock_blueprint
- assert mock_builder._working_directory == "/app"
- assert mock_builder._problem_statement == "Fix the bug"
- assert len(mock_builder._scorers) == 1
-
- def test_preview_with_no_config(self, mock_builder: AsyncScenarioBuilder) -> None:
- """Test preview() works with no configuration (only name from constructor)."""
- preview = mock_builder.preview()
-
- assert isinstance(preview, ScenarioPreview)
- assert preview.name == "test-scenario"
- assert preview.input_context is not None
- assert preview.input_context.problem_statement is None
- assert preview.input_context.additional_context is None
- assert preview.scoring_contract is not None
- assert len(preview.scoring_contract.scoring_function_parameters) == 0
- assert preview.environment is None
- assert len(preview.metadata) == 0
- assert preview.reference_output is None
- assert preview.required_environment_variables is None
- assert preview.required_secret_names is None
- assert preview.validation_type is None
-
- def test_preview_with_full_config(self, mock_builder: AsyncScenarioBuilder, mock_blueprint: AsyncBlueprint) -> None:
- """Test preview() with all fields configured, including weight normalization."""
- mock_builder.with_problem_statement("Fix the bug")
- mock_builder.with_additional_context({"hint": "line 42"})
- mock_builder.from_blueprint(mock_blueprint)
- mock_builder.with_working_directory("/app")
- mock_builder.with_metadata({"team": "infra"})
- mock_builder.with_reference_output("diff content")
- mock_builder.with_required_env_vars(["API_KEY"])
- mock_builder.with_required_secrets(["db_pass"])
- mock_builder.with_validation_type("FORWARD")
- # Add multiple scorers with different weights to test normalization
- mock_builder.add_bash_script_scorer("scorer1", bash_script="echo 1", weight=1.0)
- mock_builder.add_bash_script_scorer("scorer2", bash_script="echo 2", weight=2.0)
- mock_builder.add_bash_script_scorer("scorer3", bash_script="echo 3", weight=3.0)
-
- preview = mock_builder.preview()
-
- # Verify it returns ScenarioPreview
- assert isinstance(preview, ScenarioPreview)
-
- # Verify all fields are populated
- assert preview.name == "test-scenario"
- assert preview.input_context is not None
- assert preview.input_context.problem_statement == "Fix the bug"
- assert preview.input_context.additional_context == {"hint": "line 42"}
- assert preview.environment is not None
- assert preview.environment.blueprint_id == "bp-123"
- assert preview.environment.working_directory == "/app"
- assert preview.metadata == {"team": "infra"}
- assert preview.reference_output == "diff content"
- assert preview.required_environment_variables == ["API_KEY"]
- assert preview.required_secret_names == ["db_pass"]
- assert preview.validation_type == "FORWARD"
-
- # Verify weights are normalized (1, 2, 3 -> 1/6, 2/6, 3/6)
- assert preview.scoring_contract is not None
- scorers = preview.scoring_contract.scoring_function_parameters
- assert len(scorers) == 3
- assert abs(scorers[0].weight - 1 / 6) < 0.0001
- assert abs(scorers[1].weight - 2 / 6) < 0.0001
- assert abs(scorers[2].weight - 3 / 6) < 0.0001
- assert abs(sum(s.weight for s in scorers) - 1.0) < 0.0001
diff --git a/tests/sdk/test_async_scenario_run.py b/tests/sdk/test_async_scenario_run.py
deleted file mode 100644
index c034524a0..000000000
--- a/tests/sdk/test_async_scenario_run.py
+++ /dev/null
@@ -1,162 +0,0 @@
-"""Comprehensive tests for async AsyncScenarioRun class."""
-
-from __future__ import annotations
-
-from types import SimpleNamespace
-from pathlib import Path
-from unittest.mock import AsyncMock
-
-from tests.sdk.conftest import MockDevboxView, MockScenarioRunView
-from runloop_api_client.sdk import AsyncScenarioRun
-
-
-class TestAsyncScenarioRun:
- """Tests for AsyncScenarioRun class."""
-
- def test_init(self, mock_async_client: AsyncMock) -> None:
- """Test AsyncScenarioRun initialization."""
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- assert run.id == "scr_123"
- assert run.devbox_id == "dbx_123"
-
- def test_repr(self, mock_async_client: AsyncMock) -> None:
- """Test AsyncScenarioRun string representation."""
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- assert repr(run) == ""
-
- def test_devbox_property(self, mock_async_client: AsyncMock) -> None:
- """Test devbox property returns AsyncDevbox wrapper."""
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- devbox = run.devbox
-
- assert devbox.id == "dbx_123"
-
- async def test_get_info(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None:
- """Test get_info method."""
- mock_async_client.scenarios.runs.retrieve = AsyncMock(return_value=scenario_run_view)
-
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- result = await run.get_info()
-
- assert result == scenario_run_view
- mock_async_client.scenarios.runs.retrieve.assert_awaited_once_with("scr_123")
-
- async def test_await_env_ready(
- self,
- mock_async_client: AsyncMock,
- scenario_run_view: MockScenarioRunView,
- devbox_view: MockDevboxView,
- ) -> None:
- """Test await_env_ready method."""
- mock_async_client.devboxes.await_running = AsyncMock(return_value=devbox_view)
- mock_async_client.scenarios.runs.retrieve = AsyncMock(return_value=scenario_run_view)
-
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- result = await run.await_env_ready()
-
- mock_async_client.devboxes.await_running.assert_awaited_once_with("dbx_123", polling_config=None)
- assert result == scenario_run_view
-
- async def test_score(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None:
- """Test score method."""
- scenario_run_view.state = "scoring"
- mock_async_client.scenarios.runs.score = AsyncMock(return_value=scenario_run_view)
-
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- result = await run.score()
-
- assert result == scenario_run_view
- mock_async_client.scenarios.runs.score.assert_awaited_once_with("scr_123")
-
- async def test_await_scored(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None:
- """Test await_scored method."""
- scenario_run_view.state = "scored"
- mock_async_client.scenarios.runs.await_scored = AsyncMock(return_value=scenario_run_view)
-
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- result = await run.await_scored()
-
- assert result == scenario_run_view
- mock_async_client.scenarios.runs.await_scored.assert_awaited_once_with("scr_123")
-
- async def test_score_and_await(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None:
- """Test score_and_await method."""
- scenario_run_view.state = "scored"
- mock_async_client.scenarios.runs.score_and_await = AsyncMock(return_value=scenario_run_view)
-
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- result = await run.score_and_await()
-
- assert result == scenario_run_view
- mock_async_client.scenarios.runs.score_and_await.assert_awaited_once_with("scr_123")
-
- async def test_score_and_complete(
- self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView
- ) -> None:
- """Test score_and_complete method."""
- scenario_run_view.state = "completed"
- mock_async_client.scenarios.runs.score_and_complete = AsyncMock(return_value=scenario_run_view)
-
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- result = await run.score_and_complete()
-
- assert result == scenario_run_view
- mock_async_client.scenarios.runs.score_and_complete.assert_awaited_once_with("scr_123")
-
- async def test_complete(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None:
- """Test complete method."""
- scenario_run_view.state = "completed"
- mock_async_client.scenarios.runs.complete = AsyncMock(return_value=scenario_run_view)
-
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- result = await run.complete()
-
- assert result == scenario_run_view
- mock_async_client.scenarios.runs.complete.assert_awaited_once_with("scr_123")
-
- async def test_cancel(self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView) -> None:
- """Test cancel method."""
- scenario_run_view.state = "canceled"
- mock_async_client.scenarios.runs.cancel = AsyncMock(return_value=scenario_run_view)
-
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- result = await run.cancel()
-
- assert result == scenario_run_view
- mock_async_client.scenarios.runs.cancel.assert_awaited_once_with("scr_123")
-
- async def test_download_logs(self, mock_async_client: AsyncMock, tmp_path: Path) -> None:
- """Test download_logs method writes to file."""
- mock_response = AsyncMock()
- mock_response.write_to_file = AsyncMock()
- mock_async_client.scenarios.runs.download_logs = AsyncMock(return_value=mock_response)
-
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- output_path = tmp_path / "logs.zip"
- await run.download_logs(output_path)
-
- mock_async_client.scenarios.runs.download_logs.assert_awaited_once_with("scr_123")
- mock_response.write_to_file.assert_awaited_once_with(output_path)
-
- async def test_get_score_when_scored(self, mock_async_client: AsyncMock) -> None:
- """Test get_score returns scoring result when scored."""
- scoring_result = SimpleNamespace(score=0.95, scoring_function_results=[])
- run_view = MockScenarioRunView(state="scored", scoring_contract_result=scoring_result)
- mock_async_client.scenarios.runs.retrieve = AsyncMock(return_value=run_view)
-
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- result = await run.get_score()
-
- assert result == scoring_result
- mock_async_client.scenarios.runs.retrieve.assert_awaited_once_with("scr_123")
-
- async def test_get_score_when_not_scored(self, mock_async_client: AsyncMock) -> None:
- """Test get_score returns None when not scored."""
- run_view = MockScenarioRunView(state="running", scoring_contract_result=None)
- mock_async_client.scenarios.runs.retrieve = AsyncMock(return_value=run_view)
-
- run = AsyncScenarioRun(mock_async_client, "scr_123", "dbx_123")
- result = await run.get_score()
-
- assert result is None
- mock_async_client.scenarios.runs.retrieve.assert_awaited_once_with("scr_123")
diff --git a/tests/sdk/test_async_scorer.py b/tests/sdk/test_async_scorer.py
deleted file mode 100644
index 23b6628c7..000000000
--- a/tests/sdk/test_async_scorer.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""Comprehensive tests for async AsyncScorer class."""
-
-from __future__ import annotations
-
-from types import SimpleNamespace
-from unittest.mock import AsyncMock
-
-import pytest
-
-from tests.sdk.conftest import MockScorerView
-from runloop_api_client.sdk import AsyncScorer
-
-
-class TestAsyncScorer:
- """Tests for AsyncScorer class."""
-
- def test_init(self, mock_async_client: AsyncMock) -> None:
- """Test AsyncScorer initialization."""
- scorer = AsyncScorer(mock_async_client, "sco_123")
- assert scorer.id == "sco_123"
-
- def test_repr(self, mock_async_client: AsyncMock) -> None:
- """Test AsyncScorer string representation."""
- scorer = AsyncScorer(mock_async_client, "sco_123")
- assert repr(scorer) == ""
-
- @pytest.mark.asyncio
- async def test_get_info(self, mock_async_client: AsyncMock, scorer_view: MockScorerView) -> None:
- """Test get_info method."""
- mock_async_client.scenarios.scorers.retrieve = AsyncMock(return_value=scorer_view)
-
- scorer = AsyncScorer(mock_async_client, "sco_123")
- result = await scorer.get_info()
-
- assert result == scorer_view
- mock_async_client.scenarios.scorers.retrieve.assert_awaited_once()
-
- @pytest.mark.asyncio
- async def test_update(self, mock_async_client: AsyncMock) -> None:
- """Test update method."""
- update_response = SimpleNamespace(id="sco_123", type="updated_scorer", bash_script="echo 'score=1.0'")
- mock_async_client.scenarios.scorers.update = AsyncMock(return_value=update_response)
-
- scorer = AsyncScorer(mock_async_client, "sco_123")
- result = await scorer.update(
- type="updated_scorer",
- bash_script="echo 'score=1.0'",
- )
-
- assert result == update_response
- mock_async_client.scenarios.scorers.update.assert_awaited_once()
diff --git a/tests/sdk/test_async_secret.py b/tests/sdk/test_async_secret.py
new file mode 100644
index 000000000..983ec0e90
--- /dev/null
+++ b/tests/sdk/test_async_secret.py
@@ -0,0 +1,47 @@
+"""HTTP routing and request forwarding that lifecycle smoke tests cannot observe."""
+
+from __future__ import annotations
+
+import json
+
+import httpx
+import pytest
+
+from runloop_api_client import NotFoundError
+from runloop_api_client.sdk import AsyncRunloopSDK, AsyncSecretById
+
+
+@pytest.mark.parametrize(
+ "operation, suffix, method", [("get_info", "", "GET"), ("update", "/update", "POST"), ("delete", "/delete", "POST")]
+)
+async def test_id_request_forwards_options_without_name_fallback(operation: str, suffix: str, method: str) -> None:
+ requests: list[httpx.Request] = []
+
+ def respond(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ return httpx.Response(404, json={"message": "Secret not found"})
+
+ async with AsyncRunloopSDK(
+ bearer_token="synthetic",
+ base_url="https://sdk.invalid",
+ max_retries=0,
+ http_client=httpx.AsyncClient(transport=httpx.MockTransport(respond)),
+ ) as sdk:
+ exact = sdk.secret.from_id("sec_missing")
+ assert isinstance(exact, AsyncSecretById)
+ assert not requests
+ with pytest.raises(NotFoundError):
+ if operation == "get_info":
+ await exact.get_info(extra_headers={"x-probe": "probe"})
+ elif operation == "update":
+ await exact.update("synthetic", extra_headers={"x-probe": "probe"}, idempotency_key="probe")
+ else:
+ await exact.delete(extra_headers={"x-probe": "probe"}, idempotency_key="probe")
+ assert len(requests) == 1 # No fallback request to a name route.
+ request = requests[0]
+ assert (request.method, request.url.path) == (method, f"/v1/secrets/id/sec_missing{suffix}")
+ assert request.headers["x-probe"] == "probe"
+ if operation != "get_info":
+ assert request.headers["x-request-id"] == "probe"
+ if operation == "update":
+ assert json.loads(request.content) == {"value": "synthetic"}
diff --git a/tests/sdk/test_async_storage_object.py b/tests/sdk/test_async_storage_object.py
index 434be5221..49880a784 100644
--- a/tests/sdk/test_async_storage_object.py
+++ b/tests/sdk/test_async_storage_object.py
@@ -162,34 +162,6 @@ async def test_delete(self, mock_async_client: AsyncMock, object_view: MockObjec
assert result == object_view
mock_async_client.objects.delete.assert_awaited_once()
- @pytest.mark.asyncio
- async def test_upload_content_string(self, mock_async_client: AsyncMock) -> None:
- """Test upload_content with string."""
- mock_response = create_mock_httpx_response()
- http_client = AsyncMock()
- http_client.put = AsyncMock(return_value=mock_response)
- mock_async_client._client = http_client
-
- obj = AsyncStorageObject(mock_async_client, "obj_123", "https://upload.example.com")
- await obj.upload_content("test content")
-
- http_client.put.assert_awaited_once_with("https://upload.example.com", content="test content")
- mock_response.raise_for_status.assert_called_once()
-
- @pytest.mark.asyncio
- async def test_upload_content_bytes(self, mock_async_client: AsyncMock) -> None:
- """Test upload_content with bytes."""
- mock_response = create_mock_httpx_response()
- http_client = AsyncMock()
- http_client.put = AsyncMock(return_value=mock_response)
- mock_async_client._client = http_client
-
- obj = AsyncStorageObject(mock_async_client, "obj_123", "https://upload.example.com")
- await obj.upload_content(b"test content")
-
- http_client.put.assert_awaited_once_with("https://upload.example.com", content=b"test content")
- mock_response.raise_for_status.assert_called_once()
-
@pytest.mark.asyncio
async def test_upload_content_no_url(self, mock_async_client: AsyncMock) -> None:
"""Test upload_content raises error when no upload URL."""
diff --git a/tests/sdk/test_benchmark.py b/tests/sdk/test_benchmark.py
deleted file mode 100644
index 6ce73ee06..000000000
--- a/tests/sdk/test_benchmark.py
+++ /dev/null
@@ -1,122 +0,0 @@
-"""Comprehensive tests for sync Benchmark class."""
-
-from __future__ import annotations
-
-from types import SimpleNamespace
-from unittest.mock import Mock
-
-from tests.sdk.conftest import MockBenchmarkView, MockBenchmarkRunView
-from runloop_api_client.sdk.benchmark import Benchmark
-from runloop_api_client.sdk.benchmark_run import BenchmarkRun
-
-
-class TestBenchmark:
- """Tests for Benchmark class."""
-
- def test_init(self, mock_client: Mock) -> None:
- """Test Benchmark initialization."""
- benchmark = Benchmark(mock_client, "bmd_123")
- assert benchmark.id == "bmd_123"
- assert repr(benchmark) == ""
-
- def test_get_info(self, mock_client: Mock, benchmark_view: MockBenchmarkView) -> None:
- """Test get_info method."""
- mock_client.benchmarks.retrieve.return_value = benchmark_view
-
- benchmark = Benchmark(mock_client, "bmd_123")
- result = benchmark.get_info()
-
- assert result == benchmark_view
- mock_client.benchmarks.retrieve.assert_called_once_with("bmd_123")
-
- def test_update(self, mock_client: Mock, benchmark_view: MockBenchmarkView) -> None:
- """Test update method."""
- benchmark_view.name = "updated-name"
- mock_client.benchmarks.update.return_value = benchmark_view
-
- benchmark = Benchmark(mock_client, "bmd_123")
- result = benchmark.update(name="updated-name")
-
- assert result == benchmark_view
- mock_client.benchmarks.update.assert_called_once_with("bmd_123", name="updated-name")
-
- def test_run(self, mock_client: Mock, benchmark_run_view: MockBenchmarkRunView) -> None:
- """Test run method."""
- mock_client.benchmarks.start_run.return_value = benchmark_run_view
-
- benchmark = Benchmark(mock_client, "bmd_123")
- result = benchmark.start_run(run_name="test-run", metadata={"key": "value"})
-
- assert isinstance(result, BenchmarkRun)
- assert result.id == benchmark_run_view.id
- assert result.benchmark_id == benchmark_run_view.benchmark_id
- mock_client.benchmarks.start_run.assert_called_once_with(
- benchmark_id="bmd_123", run_name="test-run", metadata={"key": "value"}
- )
-
- def test_add_scenarios(self, mock_client: Mock, benchmark_view: MockBenchmarkView) -> None:
- """Test add_scenarios method."""
- benchmark_view.scenario_ids = ["scn_001", "scn_002"]
- mock_client.benchmarks.update_scenarios.return_value = benchmark_view
-
- benchmark = Benchmark(mock_client, "bmd_123")
- result = benchmark.add_scenarios(["scn_001", "scn_002"])
-
- assert result == benchmark_view
- mock_client.benchmarks.update_scenarios.assert_called_once_with(
- "bmd_123", scenarios_to_add=["scn_001", "scn_002"]
- )
-
- def test_remove_scenarios(self, mock_client: Mock, benchmark_view: MockBenchmarkView) -> None:
- """Test remove_scenarios method."""
- mock_client.benchmarks.update_scenarios.return_value = benchmark_view
-
- benchmark = Benchmark(mock_client, "bmd_123")
- result = benchmark.remove_scenarios(["scn_001"])
-
- assert result == benchmark_view
- mock_client.benchmarks.update_scenarios.assert_called_once_with("bmd_123", scenarios_to_remove=["scn_001"])
-
- def test_list_runs_single(self, mock_client: Mock, benchmark_run_view: MockBenchmarkRunView) -> None:
- """Test list_runs method with single result."""
- page = SimpleNamespace(runs=[benchmark_run_view])
- mock_client.benchmark_runs.list.return_value = page
-
- benchmark = Benchmark(mock_client, "bmd_123")
- result = benchmark.list_runs()
-
- assert len(result) == 1
- assert isinstance(result[0], BenchmarkRun)
- assert result[0].id == benchmark_run_view.id
- assert result[0].benchmark_id == benchmark_run_view.benchmark_id
- mock_client.benchmark_runs.list.assert_called_once_with(benchmark_id="bmd_123")
-
- def test_list_runs_multiple(self, mock_client: Mock) -> None:
- """Test list_runs method with multiple results."""
- run_view1 = MockBenchmarkRunView(id="bmr_001")
- run_view2 = MockBenchmarkRunView(id="bmr_002")
- page = SimpleNamespace(runs=[run_view1, run_view2])
- mock_client.benchmark_runs.list.return_value = page
-
- benchmark = Benchmark(mock_client, "bmd_123")
- result = benchmark.list_runs()
-
- assert len(result) == 2
- assert isinstance(result[0], BenchmarkRun)
- assert isinstance(result[1], BenchmarkRun)
- assert result[0].id == run_view1.id
- assert result[0].benchmark_id == run_view1.benchmark_id
- assert result[1].id == run_view2.id
- assert result[1].benchmark_id == run_view2.benchmark_id
- mock_client.benchmark_runs.list.assert_called_once_with(benchmark_id="bmd_123")
-
- def test_list_runs_with_params(self, mock_client: Mock, benchmark_run_view: MockBenchmarkRunView) -> None:
- """Test list_runs method with filtering parameters."""
- page = SimpleNamespace(runs=[benchmark_run_view])
- mock_client.benchmark_runs.list.return_value = page
-
- benchmark = Benchmark(mock_client, "bmd_123")
- result = benchmark.list_runs(limit=10, name="test-run")
-
- assert len(result) == 1
- mock_client.benchmark_runs.list.assert_called_once_with(benchmark_id="bmd_123", limit=10, name="test-run")
diff --git a/tests/sdk/test_benchmark_run.py b/tests/sdk/test_benchmark_run.py
deleted file mode 100644
index 93c178674..000000000
--- a/tests/sdk/test_benchmark_run.py
+++ /dev/null
@@ -1,114 +0,0 @@
-"""Comprehensive tests for sync BenchmarkRun class."""
-
-from __future__ import annotations
-
-from types import SimpleNamespace
-from unittest.mock import Mock
-
-from tests.sdk.conftest import MockScenarioRunView, MockBenchmarkRunView
-from runloop_api_client.sdk.scenario_run import ScenarioRun
-from runloop_api_client.sdk.benchmark_run import BenchmarkRun
-
-
-class TestBenchmarkRun:
- """Tests for BenchmarkRun class."""
-
- def test_init(self, mock_client: Mock) -> None:
- """Test BenchmarkRun initialization."""
- run = BenchmarkRun(mock_client, "bmr_123", "bmd_123")
- assert run.id == "bmr_123"
- assert run.benchmark_id == "bmd_123"
-
- def test_repr(self, mock_client: Mock) -> None:
- """Test BenchmarkRun string representation."""
- run = BenchmarkRun(mock_client, "bmr_123", "bmd_123")
- assert repr(run) == ""
-
- def test_get_info(self, mock_client: Mock, benchmark_run_view: MockBenchmarkRunView) -> None:
- """Test get_info method."""
- mock_client.benchmark_runs.retrieve.return_value = benchmark_run_view
-
- run = BenchmarkRun(mock_client, "bmr_123", "bmd_123")
- result = run.get_info()
-
- assert result == benchmark_run_view
- mock_client.benchmark_runs.retrieve.assert_called_once_with("bmr_123")
-
- def test_cancel(self, mock_client: Mock, benchmark_run_view: MockBenchmarkRunView) -> None:
- """Test cancel method."""
- benchmark_run_view.state = "canceled"
- mock_client.benchmark_runs.cancel.return_value = benchmark_run_view
-
- run = BenchmarkRun(mock_client, "bmr_123", "bmd_123")
- result = run.cancel()
-
- assert result == benchmark_run_view
- assert result.state == "canceled"
- mock_client.benchmark_runs.cancel.assert_called_once_with("bmr_123")
-
- def test_complete(self, mock_client: Mock, benchmark_run_view: MockBenchmarkRunView) -> None:
- """Test complete method."""
- benchmark_run_view.state = "completed"
- mock_client.benchmark_runs.complete.return_value = benchmark_run_view
-
- run = BenchmarkRun(mock_client, "bmr_123", "bmd_123")
- result = run.complete()
-
- assert result == benchmark_run_view
- assert result.state == "completed"
- mock_client.benchmark_runs.complete.assert_called_once_with("bmr_123")
-
- def test_list_scenario_runs_empty(self, mock_client: Mock) -> None:
- """Test list_scenario_runs method with empty results."""
- page = SimpleNamespace(runs=[])
- mock_client.benchmark_runs.list_scenario_runs.return_value = page
-
- run = BenchmarkRun(mock_client, "bmr_123", "bmd_123")
- result = run.list_scenario_runs()
-
- assert len(result) == 0
- mock_client.benchmark_runs.list_scenario_runs.assert_called_once_with("bmr_123")
-
- def test_list_scenario_runs_single(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
- """Test list_scenario_runs method with single result."""
- page = SimpleNamespace(runs=[scenario_run_view])
- mock_client.benchmark_runs.list_scenario_runs.return_value = page
-
- run = BenchmarkRun(mock_client, "bmr_123", "bmd_123")
- result = run.list_scenario_runs()
-
- assert len(result) == 1
- assert isinstance(result[0], ScenarioRun)
- assert result[0].id == scenario_run_view.id
- assert result[0].devbox_id == scenario_run_view.devbox_id
- mock_client.benchmark_runs.list_scenario_runs.assert_called_once_with("bmr_123")
-
- def test_list_scenario_runs_multiple(self, mock_client: Mock) -> None:
- """Test list_scenario_runs method with multiple results."""
- scenario_run_view1 = MockScenarioRunView(id="scr_001", devbox_id="dev_001")
- scenario_run_view2 = MockScenarioRunView(id="scr_002", devbox_id="dev_002")
- page = SimpleNamespace(runs=[scenario_run_view1, scenario_run_view2])
- mock_client.benchmark_runs.list_scenario_runs.return_value = page
-
- run = BenchmarkRun(mock_client, "bmr_123", "bmd_123")
- result = run.list_scenario_runs()
-
- assert len(result) == 2
- assert isinstance(result[0], ScenarioRun)
- assert isinstance(result[1], ScenarioRun)
- assert result[0].id == "scr_001"
- assert result[1].id == "scr_002"
- mock_client.benchmark_runs.list_scenario_runs.assert_called_once_with("bmr_123")
-
- def test_list_scenario_runs_with_params(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
- """Test list_scenario_runs method with filtering parameters."""
- page = SimpleNamespace(runs=[scenario_run_view])
- mock_client.benchmark_runs.list_scenario_runs.return_value = page
-
- run = BenchmarkRun(mock_client, "bmr_123", "bmd_123")
- result = run.list_scenario_runs(limit=10, state="completed")
-
- assert len(result) == 1
- assert isinstance(result[0], ScenarioRun)
- assert result[0].id == scenario_run_view.id
- mock_client.benchmark_runs.list_scenario_runs.assert_called_once_with("bmr_123", limit=10, state="completed")
diff --git a/tests/sdk/test_execution_stdin_transport.py b/tests/sdk/test_execution_stdin_transport.py
new file mode 100644
index 000000000..f58d031da
--- /dev/null
+++ b/tests/sdk/test_execution_stdin_transport.py
@@ -0,0 +1,86 @@
+"""Stdin wrappers use generated routing and preserve request configuration."""
+
+from __future__ import annotations
+
+import json
+
+import httpx
+import pytest
+
+from runloop_api_client import RunloopError
+from runloop_api_client.sdk import Execution, RunloopSDK, AsyncExecution, AsyncRunloopSDK
+from runloop_api_client.types import DevboxAsyncExecutionDetailView
+
+
+class StdinTransport:
+ def __init__(self, success: bool) -> None:
+ self.success = success
+ self.bodies: list[object] = []
+
+ def __call__(self, request: httpx.Request) -> httpx.Response:
+ assert request.method == "POST"
+ assert request.url.path == "/v1/devboxes/dbx_test/executions/execution_test/send_std_in"
+ assert request.headers["x-request-id"] == "synthetic-idempotency-key"
+ assert request.headers["x-probe"] == "probe"
+ assert request.extensions["timeout"]["read"] == 12
+ self.bodies.append(json.loads(request.content))
+ return httpx.Response(
+ 200, json={"devbox_id": "dbx_test", "execution_id": "execution_test", "success": self.success}
+ )
+
+
+@pytest.mark.parametrize("success", [True, False])
+def test_stdin_generated_routing_and_unsuccessful_delivery(success: bool) -> None:
+ transport = StdinTransport(success)
+ with RunloopSDK(
+ bearer_token="synthetic",
+ base_url="https://api.invalid",
+ max_retries=0,
+ http_client=httpx.Client(transport=httpx.MockTransport(transport)),
+ ) as sdk:
+ execution = Execution(
+ sdk.api, "dbx_test", DevboxAsyncExecutionDetailView.model_construct(execution_id="execution_test")
+ )
+ for operation in [
+ lambda: execution.send_std_in(
+ "hello\n", extra_headers={"x-probe": "probe"}, idempotency_key="synthetic-idempotency-key", timeout=12
+ ),
+ lambda: execution.close_std_in(
+ extra_headers={"x-probe": "probe"}, idempotency_key="synthetic-idempotency-key", timeout=12
+ ),
+ ]:
+ if success:
+ assert operation() is None
+ else:
+ with pytest.raises(RunloopError, match="Failed to send stdin"):
+ operation()
+ assert transport.bodies == [{"text": "hello\n"}, {"signal": "EOF"}]
+
+
+@pytest.mark.parametrize("success", [True, False])
+@pytest.mark.asyncio
+async def test_async_stdin_generated_routing_and_unsuccessful_delivery(success: bool) -> None:
+ transport = StdinTransport(success)
+ async with AsyncRunloopSDK(
+ bearer_token="synthetic",
+ base_url="https://api.invalid",
+ max_retries=0,
+ http_client=httpx.AsyncClient(transport=httpx.MockTransport(transport)),
+ ) as sdk:
+ execution = AsyncExecution(
+ sdk.api, "dbx_test", DevboxAsyncExecutionDetailView.model_construct(execution_id="execution_test")
+ )
+ for operation in [
+ lambda: execution.send_std_in(
+ "hello\n", extra_headers={"x-probe": "probe"}, idempotency_key="synthetic-idempotency-key", timeout=12
+ ),
+ lambda: execution.close_std_in(
+ extra_headers={"x-probe": "probe"}, idempotency_key="synthetic-idempotency-key", timeout=12
+ ),
+ ]:
+ if success:
+ assert await operation() is None
+ else:
+ with pytest.raises(RunloopError, match="Failed to send stdin"):
+ await operation()
+ assert transport.bodies == [{"text": "hello\n"}, {"signal": "EOF"}]
diff --git a/tests/sdk/test_ops.py b/tests/sdk/test_ops.py
index 974c47e3d..a003022a8 100644
--- a/tests/sdk/test_ops.py
+++ b/tests/sdk/test_ops.py
@@ -12,13 +12,9 @@
from tests.sdk.conftest import (
MockAxonView,
- MockAgentView,
MockDevboxView,
MockObjectView,
- MockScorerView,
- MockScenarioView,
MockSnapshotView,
- MockBenchmarkView,
MockBlueprintView,
MockGatewayConfigView,
MockNetworkPolicyView,
@@ -26,21 +22,13 @@
)
from runloop_api_client.sdk import (
Axon,
- Agent,
Devbox,
- Scorer,
AxonOps,
- AgentOps,
- Scenario,
Snapshot,
- Benchmark,
Blueprint,
DevboxOps,
- ScorerOps,
RunloopSDK,
- ScenarioOps,
SnapshotOps,
- BenchmarkOps,
BlueprintOps,
GatewayConfig,
NetworkPolicy,
@@ -391,7 +379,7 @@ def test_upload_from_file(self, mock_client: Mock, object_view: MockObjectView,
http_client = Mock()
mock_response = create_mock_httpx_response()
- http_client.put.return_value = mock_response
+ http_client.send.return_value = mock_response
mock_client._client = http_client
ops = StorageObjectOps(mock_client)
@@ -405,53 +393,8 @@ def test_upload_from_file(self, mock_client: Mock, object_view: MockObjectView,
metadata=None,
ttl_ms=None,
)
- http_client.put.assert_called_once_with(object_view.upload_url, content=b"test content")
- mock_client.objects.complete.assert_called_once()
-
- def test_upload_from_text(self, mock_client: Mock, object_view: MockObjectView) -> None:
- """Test upload_from_text method."""
- mock_client.objects.create.return_value = object_view
-
- http_client = Mock()
- mock_response = create_mock_httpx_response()
- http_client.put.return_value = mock_response
- mock_client._client = http_client
-
- ops = StorageObjectOps(mock_client)
- obj = ops.upload_from_text("test content", name="test.txt", metadata={"key": "value"})
-
- assert isinstance(obj, StorageObject)
- assert obj.id == "obj_123"
- mock_client.objects.create.assert_called_once_with(
- name="test.txt",
- content_type="text",
- metadata={"key": "value"},
- ttl_ms=None,
- )
- http_client.put.assert_called_once_with(object_view.upload_url, content="test content")
- mock_client.objects.complete.assert_called_once()
-
- def test_upload_from_bytes(self, mock_client: Mock, object_view: MockObjectView) -> None:
- """Test upload_from_bytes method."""
- mock_client.objects.create.return_value = object_view
-
- http_client = Mock()
- mock_response = create_mock_httpx_response()
- http_client.put.return_value = mock_response
- mock_client._client = http_client
-
- ops = StorageObjectOps(mock_client)
- obj = ops.upload_from_bytes(b"test content", name="test.bin", content_type="binary")
-
- assert isinstance(obj, StorageObject)
- assert obj.id == "obj_123"
- mock_client.objects.create.assert_called_once_with(
- name="test.bin",
- content_type="binary",
- metadata=None,
- ttl_ms=None,
- )
- http_client.put.assert_called_once_with(object_view.upload_url, content=b"test content")
+ assert http_client.send.call_count == 1
+ assert http_client.send.call_args[0][0].read() == b"test content"
mock_client.objects.complete.assert_called_once()
def test_upload_from_file_missing_path(self, mock_client: Mock, tmp_path: Path) -> None:
@@ -486,7 +429,7 @@ def test_upload_from_dir(self, mock_client: Mock, object_view: MockObjectView, t
http_client = Mock()
mock_response = create_mock_httpx_response()
- http_client.put.return_value = mock_response
+ http_client.send.return_value = mock_response
mock_client._client = http_client
ops = StorageObjectOps(mock_client)
@@ -500,11 +443,11 @@ def test_upload_from_dir(self, mock_client: Mock, object_view: MockObjectView, t
metadata={"key": "value"},
ttl_ms=None,
)
- # Verify that put was called with tarball content
- http_client.put.assert_called_once()
- call_args = http_client.put.call_args
- assert call_args[0][0] == object_view.upload_url
- uploaded_content = call_args[1]["content"]
+ # Verify that a request was sent with tarball content
+ http_client.send.assert_called_once()
+ call_args = http_client.send.call_args
+ assert str(call_args[0][0].url) == object_view.upload_url
+ uploaded_content = call_args[0][0].read()
# Verify it is bytes representing a gzipped tar archive
assert isinstance(uploaded_content, (bytes, bytearray))
mock_client.objects.complete.assert_called_once()
@@ -519,7 +462,7 @@ def test_upload_from_dir_default_name(self, mock_client: Mock, object_view: Mock
http_client = Mock()
mock_response = create_mock_httpx_response()
- http_client.put.return_value = mock_response
+ http_client.send.return_value = mock_response
mock_client._client = http_client
ops = StorageObjectOps(mock_client)
@@ -546,7 +489,7 @@ def test_upload_from_dir_with_ttl(self, mock_client: Mock, object_view: MockObje
http_client = Mock()
mock_response = create_mock_httpx_response()
- http_client.put.return_value = mock_response
+ http_client.send.return_value = mock_response
mock_client._client = http_client
ops = StorageObjectOps(mock_client)
@@ -571,7 +514,7 @@ def test_upload_from_dir_empty_directory(
http_client = Mock()
mock_response = create_mock_httpx_response()
- http_client.put.return_value = mock_response
+ http_client.send.return_value = mock_response
mock_client._client = http_client
ops = StorageObjectOps(mock_client)
@@ -585,7 +528,7 @@ def test_upload_from_dir_empty_directory(
metadata=None,
ttl_ms=None,
)
- http_client.put.assert_called_once()
+ http_client.send.assert_called_once()
mock_client.objects.complete.assert_called_once()
def test_upload_from_dir_with_string_path(
@@ -600,7 +543,7 @@ def test_upload_from_dir_with_string_path(
http_client = Mock()
mock_response = create_mock_httpx_response()
- http_client.put.return_value = mock_response
+ http_client.send.return_value = mock_response
mock_client._client = http_client
ops = StorageObjectOps(mock_client)
@@ -615,7 +558,7 @@ def test_upload_from_dir_with_string_path(
metadata=None,
ttl_ms=None,
)
- http_client.put.assert_called_once()
+ http_client.send.assert_called_once()
mock_client.objects.complete.assert_called_once()
def test_upload_from_dir_respects_filter(
@@ -634,7 +577,7 @@ def test_upload_from_dir_respects_filter(
http_client = Mock()
mock_response = create_mock_httpx_response()
- http_client.put.return_value = mock_response
+ http_client.send.return_value = mock_response
mock_client._client = http_client
client = StorageObjectOps(mock_client)
@@ -648,7 +591,7 @@ def ignore_logs_and_build(ti: tarfile.TarInfo) -> tarfile.TarInfo | None:
obj = client.upload_from_dir(test_dir, ignore=ignore_logs_and_build)
assert isinstance(obj, StorageObject)
- uploaded_content = http_client.put.call_args[1]["content"]
+ uploaded_content = http_client.send.call_args[0][0].read()
with tarfile.open(fileobj=io.BytesIO(uploaded_content), mode="r:gz") as tar:
names = {m.name for m in tar.getmembers()}
@@ -658,76 +601,6 @@ def ignore_logs_and_build(ti: tarfile.TarInfo) -> tarfile.TarInfo | None:
assert not any(name.startswith("build/") for name in names)
-class TestScorerOps:
- """Tests for ScorerOps class."""
-
- def test_create(self, mock_client: Mock, scorer_view: MockScorerView) -> None:
- """Test create method."""
- mock_client.scenarios.scorers.create.return_value = scorer_view
-
- ops = ScorerOps(mock_client)
- scorer = ops.create(
- bash_script="echo 'score=1.0'",
- type="test_scorer",
- )
-
- assert isinstance(scorer, Scorer)
- assert scorer.id == "sco_123"
- mock_client.scenarios.scorers.create.assert_called_once()
-
- def test_from_id(self, mock_client: Mock) -> None:
- """Test from_id method."""
- ops = ScorerOps(mock_client)
- scorer = ops.from_id("sco_123")
-
- assert isinstance(scorer, Scorer)
- assert scorer.id == "sco_123"
-
- def test_list_empty(self, mock_client: Mock) -> None:
- """Test list method with empty results."""
- page = SimpleNamespace(scorers=[])
- mock_client.scenarios.scorers.list.return_value = page
-
- ops = ScorerOps(mock_client)
- scorers = ops.list(limit=10)
-
- assert len(scorers) == 0
- mock_client.scenarios.scorers.list.assert_called_once()
-
- def test_list_single(self, mock_client: Mock, scorer_view: MockScorerView) -> None:
- """Test list method with single result."""
- page = SimpleNamespace(scorers=[scorer_view])
- mock_client.scenarios.scorers.list.return_value = page
-
- ops = ScorerOps(mock_client)
- scorers = ops.list(
- limit=10,
- starting_after="scorer_000",
- )
-
- assert len(scorers) == 1
- assert isinstance(scorers[0], Scorer)
- assert scorers[0].id == "sco_123"
- mock_client.scenarios.scorers.list.assert_called_once()
-
- def test_list_multiple(self, mock_client: Mock) -> None:
- """Test list method with multiple results."""
- scorer_view1 = MockScorerView(id="scorer_001", type="scorer-1")
- scorer_view2 = MockScorerView(id="scorer_002", type="scorer-2")
- page = SimpleNamespace(scorers=[scorer_view1, scorer_view2])
- mock_client.scenarios.scorers.list.return_value = page
-
- ops = ScorerOps(mock_client)
- scorers = ops.list(limit=10)
-
- assert len(scorers) == 2
- assert isinstance(scorers[0], Scorer)
- assert isinstance(scorers[1], Scorer)
- assert scorers[0].id == "scorer_001"
- assert scorers[1].id == "scorer_002"
- mock_client.scenarios.scorers.list.assert_called_once()
-
-
class TestAxonOps:
"""Tests for AxonOps class."""
@@ -792,442 +665,6 @@ def test_list_multiple(self, mock_client: Mock) -> None:
mock_client.axons.list.assert_called_once()
-class TestAgentClient:
- """Tests for AgentClient class."""
-
- def test_create(self, mock_client: Mock, agent_view: MockAgentView) -> None:
- """Test create method."""
- mock_client.agents.create.return_value = agent_view
-
- client = AgentOps(mock_client)
- agent = client.create(
- name="test-agent",
- version="1.2.3",
- )
-
- assert isinstance(agent, Agent)
- assert agent.id == "agt_123"
- mock_client.agents.create.assert_called_once()
-
- def test_from_id(self, mock_client: Mock) -> None:
- """Test from_id method."""
- client = AgentOps(mock_client)
- agent = client.from_id("agt_123")
-
- assert isinstance(agent, Agent)
- assert agent.id == "agt_123"
-
- def test_list(self, mock_client: Mock) -> None:
- """Test list method."""
- # Create three agent views with different data
- agent_view_1 = MockAgentView(
- id="agent_001",
- name="first-agent",
- create_time_ms=1234567890000,
- is_public=False,
- source=None,
- )
- agent_view_2 = MockAgentView(
- id="agent_002",
- name="second-agent",
- create_time_ms=1234567891000,
- is_public=True,
- source={"type": "git", "git": {"repository": "https://github.com/example/repo"}},
- )
- agent_view_3 = MockAgentView(
- id="agent_003",
- name="third-agent",
- create_time_ms=1234567892000,
- is_public=False,
- source={"type": "npm", "npm": {"package_name": "example-package"}},
- )
-
- page = SimpleNamespace(agents=[agent_view_1, agent_view_2, agent_view_3])
- mock_client.agents.list.return_value = page
-
- # Mock retrieve to return the corresponding agent_view when called
- def mock_retrieve(agent_id: str, **_kwargs: object) -> MockAgentView:
- agent_views = {
- "agent_001": agent_view_1,
- "agent_002": agent_view_2,
- "agent_003": agent_view_3,
- }
- return agent_views[agent_id]
-
- mock_client.agents.retrieve.side_effect = mock_retrieve
-
- client = AgentOps(mock_client)
- agents = client.list(
- limit=10,
- starting_after="agent_000",
- )
-
- # Verify we got three agents
- assert len(agents) == 3
- assert all(isinstance(agent, Agent) for agent in agents)
-
- # Verify the agent IDs
- assert agents[0].id == "agent_001"
- assert agents[1].id == "agent_002"
- assert agents[2].id == "agent_003"
-
- # Test that get_info() retrieves the AgentView for the first agent
- info = agents[0].get_info()
- assert info.id == "agent_001"
- assert info.name == "first-agent"
- assert info.create_time_ms == 1234567890000
- assert info.is_public is False
- assert info.source is None
-
- # Test that get_info() retrieves the AgentView for the second agent
- info = agents[1].get_info()
- assert info.id == "agent_002"
- assert info.name == "second-agent"
- assert info.create_time_ms == 1234567891000
- assert info.is_public is True
- assert info.source == {"type": "git", "git": {"repository": "https://github.com/example/repo"}}
-
- # Test that get_info() retrieves the AgentView for the third agent
- info = agents[2].get_info()
- assert info.id == "agent_003"
- assert info.name == "third-agent"
- assert info.create_time_ms == 1234567892000
- assert info.is_public is False
- assert info.source == {"type": "npm", "npm": {"package_name": "example-package"}}
-
- # Verify that agents.retrieve was called for each agent
- assert mock_client.agents.retrieve.call_count == 3
-
- mock_client.agents.list.assert_called_once()
-
- def test_create_from_npm(self, mock_client: Mock, agent_view: MockAgentView) -> None:
- """Test create_from_npm factory method."""
- mock_client.agents.create.return_value = agent_view
-
- client = AgentOps(mock_client)
- agent = client.create_from_npm(
- name="test-agent",
- package_name="@runloop/example-agent",
- version="1.2.3",
- )
-
- assert isinstance(agent, Agent)
- assert agent.id == "agt_123"
- mock_client.agents.create.assert_called_once_with(
- source={
- "type": "npm",
- "npm": {
- "package_name": "@runloop/example-agent",
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
- def test_create_from_npm_with_all_options(self, mock_client: Mock, agent_view: MockAgentView) -> None:
- """Test create_from_npm factory method with all optional parameters."""
- mock_client.agents.create.return_value = agent_view
-
- client = AgentOps(mock_client)
- agent = client.create_from_npm(
- package_name="@runloop/example-agent",
- registry_url="https://registry.example.com",
- agent_setup=["npm install", "npm run setup"],
- name="test-agent",
- version="1.2.3",
- extra_headers={"X-Custom": "header"},
- )
-
- assert isinstance(agent, Agent)
- assert agent.id == "agt_123"
- mock_client.agents.create.assert_called_once_with(
- source={
- "type": "npm",
- "npm": {
- "package_name": "@runloop/example-agent",
- "registry_url": "https://registry.example.com",
- "agent_setup": ["npm install", "npm run setup"],
- },
- },
- name="test-agent",
- version="1.2.3",
- extra_headers={"X-Custom": "header"},
- )
-
- def test_create_from_npm_raises_when_source_provided(self, mock_client: Mock) -> None:
- """Test create_from_npm raises ValueError when source is provided in params."""
- client = AgentOps(mock_client)
-
- with pytest.raises(ValueError, match="Cannot specify 'source' when using create_from_npm"):
- client.create_from_npm(
- package_name="@runloop/example-agent",
- name="test-agent",
- version="1.2.3",
- source={"type": "git", "git": {"repository": "https://github.com/example/repo"}},
- )
-
- def test_create_from_pip(self, mock_client: Mock, agent_view: MockAgentView) -> None:
- """Test create_from_pip factory method."""
- mock_client.agents.create.return_value = agent_view
-
- client = AgentOps(mock_client)
- agent = client.create_from_pip(
- package_name="runloop-example-agent",
- name="test-agent",
- version="1.2.3",
- )
-
- assert isinstance(agent, Agent)
- assert agent.id == "agt_123"
- mock_client.agents.create.assert_called_once_with(
- source={
- "type": "pip",
- "pip": {
- "package_name": "runloop-example-agent",
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
- def test_create_from_pip_with_all_options(self, mock_client: Mock, agent_view: MockAgentView) -> None:
- """Test create_from_pip factory method with all optional parameters."""
- mock_client.agents.create.return_value = agent_view
-
- client = AgentOps(mock_client)
- agent = client.create_from_pip(
- package_name="runloop-example-agent",
- registry_url="https://pypi.example.com",
- agent_setup=["pip install extra-deps"],
- name="test-agent",
- version="1.2.3",
- )
-
- assert isinstance(agent, Agent)
- assert agent.id == "agt_123"
- mock_client.agents.create.assert_called_once_with(
- source={
- "type": "pip",
- "pip": {
- "package_name": "runloop-example-agent",
- "registry_url": "https://pypi.example.com",
- "agent_setup": ["pip install extra-deps"],
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
- def test_create_from_git(self, mock_client: Mock, agent_view: MockAgentView) -> None:
- """Test create_from_git factory method."""
- mock_client.agents.create.return_value = agent_view
-
- client = AgentOps(mock_client)
- agent = client.create_from_git(
- repository="https://github.com/example/agent-repo",
- name="test-agent",
- version="1.2.3",
- )
-
- assert isinstance(agent, Agent)
- assert agent.id == "agt_123"
- mock_client.agents.create.assert_called_once_with(
- source={
- "type": "git",
- "git": {
- "repository": "https://github.com/example/agent-repo",
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
- def test_create_from_git_with_all_options(self, mock_client: Mock, agent_view: MockAgentView) -> None:
- """Test create_from_git factory method with all optional parameters."""
- mock_client.agents.create.return_value = agent_view
-
- client = AgentOps(mock_client)
- agent = client.create_from_git(
- repository="https://github.com/example/agent-repo",
- ref="develop",
- agent_setup=["npm install", "npm run build"],
- name="test-agent",
- version="1.2.3",
- )
-
- assert isinstance(agent, Agent)
- assert agent.id == "agt_123"
- mock_client.agents.create.assert_called_once_with(
- source={
- "type": "git",
- "git": {
- "repository": "https://github.com/example/agent-repo",
- "ref": "develop",
- "agent_setup": ["npm install", "npm run build"],
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
- def test_create_from_object(self, mock_client: Mock, agent_view: MockAgentView) -> None:
- """Test create_from_object factory method."""
- mock_client.agents.create.return_value = agent_view
-
- client = AgentOps(mock_client)
- agent = client.create_from_object(
- object_id="obj_123",
- name="test-agent",
- version="1.2.3",
- )
-
- assert isinstance(agent, Agent)
- assert agent.id == "agt_123"
- mock_client.agents.create.assert_called_once_with(
- source={
- "type": "object",
- "object": {
- "object_id": "obj_123",
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
- def test_create_from_object_with_agent_setup(self, mock_client: Mock, agent_view: MockAgentView) -> None:
- """Test create_from_object factory method with agent_setup."""
- mock_client.agents.create.return_value = agent_view
-
- client = AgentOps(mock_client)
- agent = client.create_from_object(
- object_id="obj_123",
- agent_setup=["chmod +x setup.sh", "./setup.sh"],
- name="test-agent",
- version="1.2.3",
- )
-
- assert isinstance(agent, Agent)
- assert agent.id == "agt_123"
- mock_client.agents.create.assert_called_once_with(
- source={
- "type": "object",
- "object": {
- "object_id": "obj_123",
- "agent_setup": ["chmod +x setup.sh", "./setup.sh"],
- },
- },
- name="test-agent",
- version="1.2.3",
- )
-
-
-class TestScenarioOps:
- """Tests for ScenarioOps class."""
-
- def test_from_id(self, mock_client: Mock) -> None:
- """Test from_id method."""
-
- ops = ScenarioOps(mock_client)
- scenario = ops.from_id("scn_123")
-
- assert isinstance(scenario, Scenario)
- assert scenario.id == "scn_123"
-
- def test_list_empty(self, mock_client: Mock) -> None:
- """Test list method with empty results."""
- page = SimpleNamespace(scenarios=[])
- mock_client.scenarios.list.return_value = page
-
- ops = ScenarioOps(mock_client)
- scenarios = ops.list(limit=10)
-
- assert len(scenarios) == 0
- mock_client.scenarios.list.assert_called_once()
-
- def test_list_single(self, mock_client: Mock, scenario_view: MockScenarioView) -> None:
- """Test list method with single result."""
- page = SimpleNamespace(scenarios=[scenario_view])
- mock_client.scenarios.list.return_value = page
-
- ops = ScenarioOps(mock_client)
- scenarios = ops.list(limit=10)
-
- assert len(scenarios) == 1
- assert isinstance(scenarios[0], Scenario)
- assert scenarios[0].id == "scn_123"
- mock_client.scenarios.list.assert_called_once()
-
- def test_list_multiple(self, mock_client: Mock) -> None:
- """Test list method with multiple results."""
- scenario_view1 = MockScenarioView(id="scn_001", name="scenario-1")
- scenario_view2 = MockScenarioView(id="scn_002", name="scenario-2")
- page = SimpleNamespace(scenarios=[scenario_view1, scenario_view2])
- mock_client.scenarios.list.return_value = page
-
- ops = ScenarioOps(mock_client)
- scenarios = ops.list(limit=10)
-
- assert len(scenarios) == 2
- assert isinstance(scenarios[0], Scenario)
- assert isinstance(scenarios[1], Scenario)
- assert scenarios[0].id == "scn_001"
- assert scenarios[1].id == "scn_002"
- mock_client.scenarios.list.assert_called_once()
-
-
-class TestBenchmarkOps:
- """Tests for BenchmarkOps class."""
-
- def test_create(self, mock_client: Mock, benchmark_view: MockBenchmarkView) -> None:
- """Test create method."""
- mock_client.benchmarks.create.return_value = benchmark_view
-
- ops = BenchmarkOps(mock_client)
- benchmark = ops.create(name="test-benchmark", scenario_ids=["scn_001", "scn_002"])
-
- assert isinstance(benchmark, Benchmark)
- assert benchmark.id == "bmd_123"
- mock_client.benchmarks.create.assert_called_once_with(
- name="test-benchmark", scenario_ids=["scn_001", "scn_002"]
- )
-
- def test_from_id(self, mock_client: Mock) -> None:
- """Test from_id method."""
- ops = BenchmarkOps(mock_client)
- benchmark = ops.from_id("bmd_123")
-
- assert isinstance(benchmark, Benchmark)
- assert benchmark.id == "bmd_123"
-
- def test_list_multiple(self, mock_client: Mock) -> None:
- """Test list method with multiple results."""
- benchmark_view1 = MockBenchmarkView(id="bmd_001", name="benchmark-1")
- benchmark_view2 = MockBenchmarkView(id="bmd_002", name="benchmark-2")
- page = SimpleNamespace(benchmarks=[benchmark_view1, benchmark_view2])
- mock_client.benchmarks.list.return_value = page
-
- ops = BenchmarkOps(mock_client)
- benchmarks = ops.list(limit=10)
-
- assert len(benchmarks) == 2
- assert isinstance(benchmarks[0], Benchmark)
- assert isinstance(benchmarks[1], Benchmark)
- assert benchmarks[0].id == "bmd_001"
- assert benchmarks[1].id == "bmd_002"
- mock_client.benchmarks.list.assert_called_once_with(limit=10)
-
- def test_list_with_name_filter(self, mock_client: Mock, benchmark_view: MockBenchmarkView) -> None:
- """Test list method with name filter."""
- page = SimpleNamespace(benchmarks=[benchmark_view])
- mock_client.benchmarks.list.return_value = page
-
- ops = BenchmarkOps(mock_client)
- benchmarks = ops.list(name="test-benchmark", limit=10)
-
- assert len(benchmarks) == 1
- mock_client.benchmarks.list.assert_called_once_with(name="test-benchmark", limit=10)
-
-
class TestNetworkPolicyOps:
"""Tests for NetworkPolicyOps class."""
@@ -1376,12 +813,9 @@ def test_init(self) -> None:
"""Test RunloopSDK initialization."""
runloop = RunloopSDK(bearer_token="test-token")
assert runloop.api is not None
- assert isinstance(runloop.agent, AgentOps)
- assert isinstance(runloop.benchmark, BenchmarkOps)
assert isinstance(runloop.devbox, DevboxOps)
assert isinstance(runloop.gateway_config, GatewayConfigOps)
assert isinstance(runloop.network_policy, NetworkPolicyOps)
- assert isinstance(runloop.scorer, ScorerOps)
assert isinstance(runloop.snapshot, SnapshotOps)
assert isinstance(runloop.blueprint, BlueprintOps)
assert isinstance(runloop.storage_object, StorageObjectOps)
diff --git a/tests/sdk/test_scenario.py b/tests/sdk/test_scenario.py
deleted file mode 100644
index e3aa5f1c8..000000000
--- a/tests/sdk/test_scenario.py
+++ /dev/null
@@ -1,121 +0,0 @@
-"""Comprehensive tests for sync Scenario class."""
-
-from __future__ import annotations
-
-from unittest.mock import Mock
-
-from tests.sdk.conftest import MockScenarioView, MockScenarioRunView
-from runloop_api_client.sdk import Scenario
-
-
-class TestScenario:
- """Tests for Scenario class."""
-
- def test_init(self, mock_client: Mock) -> None:
- """Test Scenario initialization."""
- scenario = Scenario(mock_client, "scn_123")
- assert scenario.id == "scn_123"
-
- def test_repr(self, mock_client: Mock) -> None:
- """Test Scenario string representation."""
- scenario = Scenario(mock_client, "scn_123")
- assert repr(scenario) == ""
-
- def test_get_info(self, mock_client: Mock, scenario_view: MockScenarioView) -> None:
- """Test get_info method."""
- mock_client.scenarios.retrieve.return_value = scenario_view
-
- scenario = Scenario(mock_client, "scn_123")
- result = scenario.get_info()
-
- assert result == scenario_view
- mock_client.scenarios.retrieve.assert_called_once_with("scn_123")
-
- def test_update(self, mock_client: Mock, scenario_view: MockScenarioView) -> None:
- """Test update method."""
- mock_client.scenarios.update.return_value = scenario_view
-
- scenario = Scenario(mock_client, "scn_123")
- result = scenario.update(
- name="updated-scenario",
- metadata={"env": "test"},
- environment_parameters={
- "blueprint_id": "bp_456",
- },
- input_context={
- "problem_statement": "Updated problem statement",
- },
- reference_output="--- a/main.py\n+++ b/main.py\n@@ -1 +1 @@\n-old\n+new",
- required_environment_variables=["API_KEY", "DEBUG"],
- required_secret_names=["DB_PASSWORD", "JWT_SECRET"],
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "test-scorer",
- "scorer": {
- "type": "command_scorer",
- "command": "echo 'score=1.0'",
- },
- "weight": 1.0,
- }
- ],
- },
- validation_type="FORWARD",
- )
-
- assert result == scenario_view
- mock_client.scenarios.update.assert_called_once_with(
- "scn_123",
- name="updated-scenario",
- metadata={"env": "test"},
- environment_parameters={
- "blueprint_id": "bp_456",
- },
- input_context={
- "problem_statement": "Updated problem statement",
- },
- reference_output="--- a/main.py\n+++ b/main.py\n@@ -1 +1 @@\n-old\n+new",
- required_environment_variables=["API_KEY", "DEBUG"],
- required_secret_names=["DB_PASSWORD", "JWT_SECRET"],
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "test-scorer",
- "scorer": {
- "type": "command_scorer",
- "command": "echo 'score=1.0'",
- },
- "weight": 1.0,
- }
- ],
- },
- validation_type="FORWARD",
- )
-
- def test_run_async(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
- """Test run_async method returns ScenarioRun wrapper."""
- mock_client.scenarios.start_run.return_value = scenario_run_view
-
- scenario = Scenario(mock_client, "scn_123")
- run = scenario.run_async(run_name="test-run")
-
- assert run.id == "scr_123"
- assert run.devbox_id == "dbx_123"
- mock_client.scenarios.start_run.assert_called_once_with(
- scenario_id="scn_123",
- run_name="test-run",
- )
-
- def test_run(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
- """Test run method."""
- mock_client.scenarios.start_run_and_await_env_ready.return_value = scenario_run_view
-
- scenario = Scenario(mock_client, "scn_123")
- run = scenario.run(run_name="test-run")
-
- assert run.id == "scr_123"
- assert run.devbox_id == "dbx_123"
- mock_client.scenarios.start_run_and_await_env_ready.assert_called_once_with(
- scenario_id="scn_123",
- run_name="test-run",
- )
diff --git a/tests/sdk/test_scenario_builder.py b/tests/sdk/test_scenario_builder.py
deleted file mode 100644
index 75597d5f3..000000000
--- a/tests/sdk/test_scenario_builder.py
+++ /dev/null
@@ -1,287 +0,0 @@
-"""Unit tests for ScenarioBuilder class."""
-
-from __future__ import annotations
-
-from unittest.mock import MagicMock
-
-import pytest
-
-from runloop_api_client.sdk import Snapshot, Blueprint, ScenarioBuilder, ScenarioPreview
-from runloop_api_client.types.scoring_function_param import ScorerTestBasedScoringFunctionTestFile
-
-
-class TestScenarioBuilder:
- """Tests for the synchronous ScenarioBuilder."""
-
- @pytest.fixture
- def mock_client(self) -> MagicMock:
- """Create a mock Runloop client."""
- client = MagicMock()
- return client
-
- @pytest.fixture
- def mock_blueprint(self, mock_client: MagicMock) -> Blueprint:
- """Create a mock Blueprint object."""
- return Blueprint(mock_client, "bp-123")
-
- @pytest.fixture
- def mock_snapshot(self, mock_client: MagicMock) -> Snapshot:
- """Create a mock Snapshot object."""
- return Snapshot(mock_client, "snap-123")
-
- @pytest.fixture
- def mock_builder(self, mock_client: MagicMock) -> ScenarioBuilder:
- """Create a ScenarioBuilder instance with mock client."""
- return ScenarioBuilder("test-scenario", mock_client)
-
- def test_instantiation(self, mock_client: MagicMock) -> None:
- """Test builder initialization and repr."""
- builder = ScenarioBuilder("my-scenario", mock_client)
-
- assert builder._client is mock_client
- assert builder._name == "my-scenario"
- assert builder.name == "my-scenario"
- assert repr(builder) == ""
-
- def test_from_blueprint_and_snapshot(
- self, mock_builder: ScenarioBuilder, mock_blueprint: Blueprint, mock_snapshot: Snapshot
- ) -> None:
- """Test blueprint/snapshot setting returns self and are mutually exclusive."""
- # from_blueprint returns self and sets blueprint
- result = mock_builder.from_blueprint(mock_blueprint)
- assert result is mock_builder
- assert mock_builder._blueprint is mock_blueprint
- assert mock_builder._snapshot is None
-
- # from_snapshot returns self, sets snapshot, and clears blueprint
- result = mock_builder.from_snapshot(mock_snapshot)
- assert result is mock_builder
- assert mock_builder._snapshot is mock_snapshot
- assert mock_builder._blueprint is None
-
- # from_blueprint clears snapshot
- mock_builder.from_blueprint(mock_blueprint)
- assert mock_builder._blueprint is mock_blueprint
- assert mock_builder._snapshot is None
-
- def test_scorers(self, mock_builder: ScenarioBuilder) -> None:
- """Test all scorer types, optional params, and multiple scorers."""
- # Test scorer with test files
- test_files: list[ScorerTestBasedScoringFunctionTestFile] = [
- {"file_path": "test_main.py", "file_contents": "def test_foo(): pass"}
- ]
- result = mock_builder.add_test_command_scorer(
- "test-scorer", test_command="pytest", weight=2.0, test_files=test_files
- )
- assert result is mock_builder
- assert mock_builder._scorers[0]["name"] == "test-scorer"
- assert mock_builder._scorers[0]["weight"] == 2.0
- assert mock_builder._scorers[0]["scorer"]["type"] == "test_based_scorer"
- assert mock_builder._scorers[0]["scorer"].get("test_command") == "pytest"
- assert mock_builder._scorers[0]["scorer"].get("test_files") == test_files
-
- # Command scorer
- mock_builder.add_shell_command_scorer("cmd-scorer", command="./check.sh")
- assert mock_builder._scorers[1]["scorer"]["type"] == "command_scorer"
- assert mock_builder._scorers[1]["scorer"].get("command") == "./check.sh"
-
- # Bash scorer
- mock_builder.add_bash_script_scorer("bash-scorer", bash_script="echo 'score=1.0'")
- assert mock_builder._scorers[2]["scorer"]["type"] == "bash_script_scorer"
- assert mock_builder._scorers[2]["scorer"].get("bash_script") == "echo 'score=1.0'"
-
- # Python scorer with optional params
- mock_builder.add_python_script_scorer(
- "python-scorer",
- python_script="print('1.0')",
- python_version_constraint=">=3.10",
- requirements_contents="numpy",
- )
- assert mock_builder._scorers[3]["scorer"]["type"] == "python_script_scorer"
- assert mock_builder._scorers[3]["scorer"].get("python_version_constraint") == ">=3.10"
- assert mock_builder._scorers[3]["scorer"].get("requirements_contents") == "numpy"
-
- # AST grep scorer with optional lang
- mock_builder.add_ast_grep_scorer("ast-scorer", pattern="$A.foo()", search_directory="/src", lang="python")
- assert mock_builder._scorers[4]["scorer"]["type"] == "ast_grep_scorer"
- assert mock_builder._scorers[4]["scorer"].get("pattern") == "$A.foo()"
- assert mock_builder._scorers[4]["scorer"].get("lang") == "python"
-
- # Custom scorer with optional params
- mock_builder.add_custom_scorer(
- "custom-scorer", custom_scorer_type="my_scorer", scorer_params={"threshold": 0.5}
- )
- assert mock_builder._scorers[5]["scorer"]["type"] == "custom_scorer"
- assert mock_builder._scorers[5]["scorer"].get("custom_scorer_type") == "my_scorer"
- assert mock_builder._scorers[5]["scorer"].get("scorer_params") == {"threshold": 0.5}
-
- # Verify multiple scorers accumulated
- assert len(mock_builder._scorers) == 6
-
- def test_add_scorer_rejects_invalid_weight(self, mock_builder: ScenarioBuilder) -> None:
- """Test that adding a scorer with zero or negative weight raises ValueError."""
- with pytest.raises(ValueError, match="Scorer weight must be positive"):
- mock_builder.add_bash_script_scorer("bad", bash_script="echo 1", weight=0.0)
-
- with pytest.raises(ValueError, match="Scorer weight must be positive"):
- mock_builder.add_bash_script_scorer("bad", bash_script="echo 1", weight=-1.0)
-
- def test_build_validation(self, mock_builder: ScenarioBuilder) -> None:
- """Test build raises for missing required fields."""
- # Missing problem statement
- mock_builder.add_test_command_scorer("test", test_command="pytest")
- with pytest.raises(ValueError, match="Problem statement is required"):
- mock_builder.build()
-
- # Missing scorer (new builder)
- builder2 = ScenarioBuilder("test2", mock_builder._client)
- builder2.with_problem_statement("Fix the bug")
- with pytest.raises(ValueError, match="At least one scorer is required"):
- builder2.build()
-
- def test_build_with_all_options(self, mock_builder: ScenarioBuilder, mock_blueprint: Blueprint) -> None:
- """Test build with all optional fields set."""
- mock_builder.with_problem_statement("Fix the bug")
- mock_builder.with_additional_context({"hint": "line 42"})
- mock_builder.add_test_command_scorer("tests", test_command="pytest")
- mock_builder.from_blueprint(mock_blueprint)
- mock_builder.with_working_directory("/app")
- mock_builder.with_metadata({"team": "infra"})
- mock_builder.with_reference_output("diff content")
- mock_builder.with_required_env_vars(["API_KEY"])
- mock_builder.with_required_secrets(["db_pass"])
- mock_builder.with_validation_type("FORWARD")
-
- params = mock_builder.build()
-
- assert params["name"] == "test-scenario"
- assert params["input_context"]["problem_statement"] == "Fix the bug"
- assert params["input_context"].get("additional_context") == {"hint": "line 42"}
- env_params = params.get("environment_parameters")
- assert env_params is not None
- assert env_params.get("blueprint_id") == "bp-123"
- assert env_params.get("working_directory") == "/app"
- assert params.get("metadata") == {"team": "infra"}
- assert params.get("reference_output") == "diff content"
- assert params.get("required_environment_variables") == ["API_KEY"]
- assert params.get("required_secret_names") == ["db_pass"]
- assert params.get("validation_type") == "FORWARD"
-
- def test_build_normalizes_weights(self, mock_builder: ScenarioBuilder) -> None:
- """Test that build normalizes scorer weights to sum to 1.0."""
- mock_builder.with_problem_statement("Fix the bug")
- mock_builder.add_bash_script_scorer("scorer1", bash_script="echo 1", weight=1.0)
- mock_builder.add_bash_script_scorer("scorer2", bash_script="echo 2", weight=2.0)
- mock_builder.add_bash_script_scorer("scorer3", bash_script="echo 3", weight=3.0)
-
- params = mock_builder.build()
- scorers = list(params["scoring_contract"]["scoring_function_parameters"])
-
- # Weights 1, 2, 3 should normalize to 1/6, 2/6, 3/6
- assert len(scorers) == 3
- assert abs(scorers[0]["weight"] - 1 / 6) < 0.0001
- assert abs(scorers[1]["weight"] - 2 / 6) < 0.0001
- assert abs(scorers[2]["weight"] - 3 / 6) < 0.0001
-
- # Total should be 1.0
- total = sum(s["weight"] for s in scorers)
- assert abs(total - 1.0) < 0.0001
-
- def test_push_calls_api_and_returns_scenario(self, mock_builder: ScenarioBuilder, mock_client: MagicMock) -> None:
- """Test push() calls API with correct params and returns Scenario."""
- mock_client.scenarios.create.return_value.id = "scn-new-123"
-
- mock_builder.with_problem_statement("Fix the bug")
- mock_builder.add_test_command_scorer("tests", test_command="pytest")
-
- scenario = mock_builder.push()
-
- mock_client.scenarios.create.assert_called_once()
- call_kwargs = mock_client.scenarios.create.call_args.kwargs
- assert call_kwargs["name"] == "test-scenario"
- assert call_kwargs["input_context"]["problem_statement"] == "Fix the bug"
-
- assert scenario.id == "scn-new-123"
-
- def test_fluent_chaining(self, mock_builder: ScenarioBuilder, mock_blueprint: Blueprint) -> None:
- """Test that all builder methods can be chained fluently."""
- result = (
- mock_builder.from_blueprint(mock_blueprint)
- .with_working_directory("/app")
- .with_problem_statement("Fix the bug")
- .with_additional_context({"hint": "check main.py"})
- .add_test_command_scorer("tests", test_command="pytest")
- .with_metadata({"team": "infra"})
- .with_reference_output("diff content")
- .with_required_env_vars(["API_KEY"])
- .with_required_secrets(["secret"])
- .with_validation_type("FORWARD")
- )
-
- assert result is mock_builder
- assert mock_builder._blueprint is mock_blueprint
- assert mock_builder._working_directory == "/app"
- assert mock_builder._problem_statement == "Fix the bug"
- assert len(mock_builder._scorers) == 1
-
- def test_preview_with_no_config(self, mock_builder: ScenarioBuilder) -> None:
- """Test preview() works with no configuration (only name from constructor)."""
- preview = mock_builder.preview()
-
- assert isinstance(preview, ScenarioPreview)
- assert preview.name == "test-scenario"
- assert preview.input_context is not None
- assert preview.input_context.problem_statement is None
- assert preview.input_context.additional_context is None
- assert preview.scoring_contract is not None
- assert len(preview.scoring_contract.scoring_function_parameters) == 0
- assert preview.environment is None
- assert len(preview.metadata) == 0
- assert preview.reference_output is None
- assert preview.required_environment_variables is None
- assert preview.required_secret_names is None
- assert preview.validation_type is None
-
- def test_preview_with_full_config(self, mock_builder: ScenarioBuilder, mock_blueprint: Blueprint) -> None:
- """Test preview() with all fields configured, including weight normalization."""
- mock_builder.with_problem_statement("Fix the bug")
- mock_builder.with_additional_context({"hint": "line 42"})
- mock_builder.from_blueprint(mock_blueprint)
- mock_builder.with_working_directory("/app")
- mock_builder.with_metadata({"team": "infra"})
- mock_builder.with_reference_output("diff content")
- mock_builder.with_required_env_vars(["API_KEY"])
- mock_builder.with_required_secrets(["db_pass"])
- mock_builder.with_validation_type("FORWARD")
- # Add multiple scorers with different weights to test normalization
- mock_builder.add_bash_script_scorer("scorer1", bash_script="echo 1", weight=1.0)
- mock_builder.add_bash_script_scorer("scorer2", bash_script="echo 2", weight=2.0)
- mock_builder.add_bash_script_scorer("scorer3", bash_script="echo 3", weight=3.0)
-
- preview = mock_builder.preview()
-
- # Verify it returns ScenarioPreview
- assert isinstance(preview, ScenarioPreview)
-
- # Verify all fields are populated
- assert preview.name == "test-scenario"
- assert preview.input_context is not None
- assert preview.input_context.problem_statement == "Fix the bug"
- assert preview.input_context.additional_context == {"hint": "line 42"}
- assert preview.environment is not None
- assert preview.environment.blueprint_id == "bp-123"
- assert preview.environment.working_directory == "/app"
- assert preview.metadata == {"team": "infra"}
- assert preview.reference_output == "diff content"
- assert preview.required_environment_variables == ["API_KEY"]
- assert preview.required_secret_names == ["db_pass"]
- assert preview.validation_type == "FORWARD"
-
- # Verify weights are normalized (1, 2, 3 -> 1/6, 2/6, 3/6)
- assert preview.scoring_contract is not None
- scorers = preview.scoring_contract.scoring_function_parameters
- assert len(scorers) == 3
- assert abs(scorers[0].weight - 1 / 6) < 0.0001
- assert abs(scorers[1].weight - 2 / 6) < 0.0001
- assert abs(scorers[2].weight - 3 / 6) < 0.0001
- assert abs(sum(s.weight for s in scorers) - 1.0) < 0.0001
diff --git a/tests/sdk/test_scenario_run.py b/tests/sdk/test_scenario_run.py
deleted file mode 100644
index 339e365f8..000000000
--- a/tests/sdk/test_scenario_run.py
+++ /dev/null
@@ -1,157 +0,0 @@
-"""Comprehensive tests for sync ScenarioRun class."""
-
-from __future__ import annotations
-
-from types import SimpleNamespace
-from pathlib import Path
-from unittest.mock import Mock
-
-from tests.sdk.conftest import MockDevboxView, MockScenarioRunView
-from runloop_api_client.sdk import ScenarioRun
-
-
-class TestScenarioRun:
- """Tests for ScenarioRun class."""
-
- def test_init(self, mock_client: Mock) -> None:
- """Test ScenarioRun initialization."""
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- assert run.id == "scr_123"
- assert run.devbox_id == "dbx_123"
-
- def test_repr(self, mock_client: Mock) -> None:
- """Test ScenarioRun string representation."""
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- assert repr(run) == ""
-
- def test_devbox_property(self, mock_client: Mock) -> None:
- """Test devbox property returns Devbox wrapper."""
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- devbox = run.devbox
-
- assert devbox.id == "dbx_123"
-
- def test_get_info(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
- """Test get_info method."""
- mock_client.scenarios.runs.retrieve.return_value = scenario_run_view
-
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- result = run.get_info()
-
- assert result == scenario_run_view
- mock_client.scenarios.runs.retrieve.assert_called_once_with("scr_123")
-
- def test_await_env_ready(
- self, mock_client: Mock, scenario_run_view: MockScenarioRunView, devbox_view: MockDevboxView
- ) -> None:
- """Test await_env_ready method."""
- mock_client.devboxes.await_running.return_value = devbox_view
- mock_client.scenarios.runs.retrieve.return_value = scenario_run_view
-
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- result = run.await_env_ready()
-
- mock_client.devboxes.await_running.assert_called_once_with("dbx_123", polling_config=None)
- assert result == scenario_run_view
-
- def test_score(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
- """Test score method."""
- scenario_run_view.state = "scoring"
- mock_client.scenarios.runs.score.return_value = scenario_run_view
-
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- result = run.score()
-
- assert result == scenario_run_view
- mock_client.scenarios.runs.score.assert_called_once_with("scr_123")
-
- def test_await_scored(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
- """Test await_scored method."""
- scenario_run_view.state = "scored"
- mock_client.scenarios.runs.await_scored.return_value = scenario_run_view
-
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- result = run.await_scored()
-
- assert result == scenario_run_view
- mock_client.scenarios.runs.await_scored.assert_called_once_with("scr_123")
-
- def test_score_and_await(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
- """Test score_and_await method."""
- scenario_run_view.state = "scored"
- mock_client.scenarios.runs.score_and_await.return_value = scenario_run_view
-
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- result = run.score_and_await()
-
- assert result == scenario_run_view
- mock_client.scenarios.runs.score_and_await.assert_called_once_with("scr_123")
-
- def test_score_and_complete(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
- """Test score_and_complete method."""
- scenario_run_view.state = "completed"
- mock_client.scenarios.runs.score_and_complete.return_value = scenario_run_view
-
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- result = run.score_and_complete()
-
- assert result == scenario_run_view
- mock_client.scenarios.runs.score_and_complete.assert_called_once_with("scr_123")
-
- def test_complete(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
- """Test complete method."""
- scenario_run_view.state = "completed"
- mock_client.scenarios.runs.complete.return_value = scenario_run_view
-
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- result = run.complete()
-
- assert result == scenario_run_view
- mock_client.scenarios.runs.complete.assert_called_once_with("scr_123")
-
- def test_cancel(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
- """Test cancel method."""
- scenario_run_view.state = "canceled"
- mock_client.scenarios.runs.cancel.return_value = scenario_run_view
-
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- result = run.cancel()
-
- assert result == scenario_run_view
- mock_client.scenarios.runs.cancel.assert_called_once_with("scr_123")
-
- def test_download_logs(self, mock_client: Mock, tmp_path: Path) -> None:
- """Test download_logs method writes to file."""
- mock_response = Mock()
- mock_response.write_to_file = Mock()
- mock_client.scenarios.runs.download_logs.return_value = mock_response
-
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- output_path = tmp_path / "logs.zip"
- run.download_logs(output_path)
-
- mock_client.scenarios.runs.download_logs.assert_called_once_with("scr_123")
- mock_response.write_to_file.assert_called_once_with(output_path)
-
- def test_get_score_when_scored(self, mock_client: Mock) -> None:
- """Test get_score returns scoring result when scored."""
- scoring_result = SimpleNamespace(score=0.95, scoring_function_results=[])
- run_view = MockScenarioRunView(state="scored", scoring_contract_result=scoring_result)
- mock_client.scenarios.runs.retrieve.return_value = run_view
-
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- result = run.get_score()
-
- assert result == scoring_result
- mock_client.scenarios.runs.retrieve.assert_called_once_with("scr_123")
-
- def test_get_score_when_not_scored(self, mock_client: Mock) -> None:
- """Test get_score returns None when not scored."""
- run_view = MockScenarioRunView(state="running", scoring_contract_result=None)
- mock_client.scenarios.runs.retrieve.return_value = run_view
-
- run = ScenarioRun(mock_client, "scr_123", "dbx_123")
- result = run.get_score()
-
- assert result is None
- mock_client.scenarios.runs.retrieve.assert_called_once_with("scr_123")
diff --git a/tests/sdk/test_scorer.py b/tests/sdk/test_scorer.py
deleted file mode 100644
index e3363c6dc..000000000
--- a/tests/sdk/test_scorer.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""Comprehensive tests for sync Scorer class."""
-
-from __future__ import annotations
-
-from types import SimpleNamespace
-from unittest.mock import Mock
-
-from tests.sdk.conftest import MockScorerView
-from runloop_api_client.sdk import Scorer
-
-
-class TestScorer:
- """Tests for Scorer class."""
-
- def test_init(self, mock_client: Mock) -> None:
- """Test Scorer initialization."""
- scorer = Scorer(mock_client, "sco_123")
- assert scorer.id == "sco_123"
-
- def test_repr(self, mock_client: Mock) -> None:
- """Test Scorer string representation."""
- scorer = Scorer(mock_client, "sco_123")
- assert repr(scorer) == ""
-
- def test_get_info(self, mock_client: Mock, scorer_view: MockScorerView) -> None:
- """Test get_info method."""
- mock_client.scenarios.scorers.retrieve.return_value = scorer_view
-
- scorer = Scorer(mock_client, "sco_123")
- result = scorer.get_info()
-
- assert result == scorer_view
- mock_client.scenarios.scorers.retrieve.assert_called_once_with("sco_123")
-
- def test_update(self, mock_client: Mock) -> None:
- """Test update method."""
- update_response = SimpleNamespace(id="sco_123", type="updated_scorer", bash_script="echo 'score=1.0'")
- mock_client.scenarios.scorers.update.return_value = update_response
-
- scorer = Scorer(mock_client, "sco_123")
- result = scorer.update(
- type="updated_scorer",
- bash_script="echo 'score=1.0'",
- )
-
- assert result == update_response
- mock_client.scenarios.scorers.update.assert_called_once_with(
- "sco_123",
- type="updated_scorer",
- bash_script="echo 'score=1.0'",
- )
diff --git a/tests/sdk/test_secret.py b/tests/sdk/test_secret.py
new file mode 100644
index 000000000..09e84e2ec
--- /dev/null
+++ b/tests/sdk/test_secret.py
@@ -0,0 +1,47 @@
+"""HTTP routing and request forwarding that lifecycle smoke tests cannot observe."""
+
+from __future__ import annotations
+
+import json
+
+import httpx
+import pytest
+
+from runloop_api_client import NotFoundError
+from runloop_api_client.sdk import RunloopSDK, SecretById
+
+
+@pytest.mark.parametrize(
+ "operation, suffix, method", [("get_info", "", "GET"), ("update", "/update", "POST"), ("delete", "/delete", "POST")]
+)
+def test_id_request_forwards_options_without_name_fallback(operation: str, suffix: str, method: str) -> None:
+ requests: list[httpx.Request] = []
+
+ def respond(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ return httpx.Response(404, json={"message": "Secret not found"})
+
+ with RunloopSDK(
+ bearer_token="synthetic",
+ base_url="https://sdk.invalid",
+ max_retries=0,
+ http_client=httpx.Client(transport=httpx.MockTransport(respond)),
+ ) as sdk:
+ exact = sdk.secret.from_id("sec_missing")
+ assert isinstance(exact, SecretById)
+ assert not requests
+ with pytest.raises(NotFoundError):
+ if operation == "get_info":
+ exact.get_info(extra_headers={"x-probe": "probe"})
+ elif operation == "update":
+ exact.update("synthetic", extra_headers={"x-probe": "probe"}, idempotency_key="probe")
+ else:
+ exact.delete(extra_headers={"x-probe": "probe"}, idempotency_key="probe")
+ assert len(requests) == 1 # No fallback request to a name route.
+ request = requests[0]
+ assert (request.method, request.url.path) == (method, f"/v1/secrets/id/sec_missing{suffix}")
+ assert request.headers["x-probe"] == "probe"
+ if operation != "get_info":
+ assert request.headers["x-request-id"] == "probe"
+ if operation == "update":
+ assert json.loads(request.content) == {"value": "synthetic"}
diff --git a/tests/sdk/test_storage_object.py b/tests/sdk/test_storage_object.py
index ed2a90477..3f4642743 100644
--- a/tests/sdk/test_storage_object.py
+++ b/tests/sdk/test_storage_object.py
@@ -188,32 +188,6 @@ def test_delete(self, mock_client: Mock, object_view: MockObjectView) -> None:
idempotency_key="key-123",
)
- def test_upload_content_string(self, mock_client: Mock) -> None:
- """Test upload_content with string."""
- mock_response = create_mock_httpx_response()
- http_client = Mock()
- http_client.put.return_value = mock_response
- mock_client._client = http_client
-
- obj = StorageObject(mock_client, "obj_123", "https://upload.example.com")
- obj.upload_content("test content")
-
- http_client.put.assert_called_once_with("https://upload.example.com", content="test content")
- mock_response.raise_for_status.assert_called_once()
-
- def test_upload_content_bytes(self, mock_client: Mock) -> None:
- """Test upload_content with bytes."""
- mock_response = create_mock_httpx_response()
- http_client = Mock()
- http_client.put.return_value = mock_response
- mock_client._client = http_client
-
- obj = StorageObject(mock_client, "obj_123", "https://upload.example.com")
- obj.upload_content(b"test content")
-
- http_client.put.assert_called_once_with("https://upload.example.com", content=b"test content")
- mock_response.raise_for_status.assert_called_once()
-
def test_upload_content_no_url(self, mock_client: Mock) -> None:
"""Test upload_content raises error when no upload URL."""
obj = StorageObject(mock_client, "obj_123", None)
@@ -233,46 +207,3 @@ def test_ensure_upload_url_no_url(self, mock_client: Mock) -> None:
with pytest.raises(RuntimeError, match="No upload URL available"):
obj._ensure_upload_url()
-
-
-class TestStorageObjectEdgeCases:
- """Tests for StorageObject edge cases."""
-
- def test_large_file_upload(self, mock_client: Mock) -> None:
- """Test handling of large file uploads."""
- LARGE_FILE_SIZE = 10 * 1024 * 1024 # 10MB
-
- object_view = SimpleNamespace(id="obj_123", upload_url="https://upload.example.com")
- mock_client.objects.create.return_value = object_view
-
- http_client = Mock()
- mock_response = create_mock_httpx_response()
- http_client.put.return_value = mock_response
- mock_client._client = http_client
-
- obj = StorageObject(mock_client, "obj_123", "https://upload.example.com")
- large_content = b"x" * LARGE_FILE_SIZE # 10MB
- obj.upload_content(large_content)
-
- http_client.put.assert_called_once_with("https://upload.example.com", content=large_content)
-
-
-class TestStorageObjectPythonSpecific:
- """Tests for Python-specific StorageObject behavior."""
-
- def test_upload_data_types(self, mock_client: Mock) -> None:
- """Test Python supports more upload data types."""
- http_client = Mock()
- mock_response = create_mock_httpx_response()
- http_client.put.return_value = mock_response
- mock_client._client = http_client
-
- obj = StorageObject(mock_client, "obj_123", "https://upload.example.com")
-
- # String
- obj.upload_content("string content")
-
- # Bytes
- obj.upload_content(b"bytes content")
-
- assert http_client.put.call_count == 2
diff --git a/tests/sdk/test_storage_upload_transport.py b/tests/sdk/test_storage_upload_transport.py
new file mode 100644
index 000000000..91c92ed2e
--- /dev/null
+++ b/tests/sdk/test_storage_upload_transport.py
@@ -0,0 +1,204 @@
+"""Signed storage HTTP contract, independent of cloud credentials."""
+
+from __future__ import annotations
+
+import json
+from typing import Any, Generator
+from typing_extensions import override
+
+import httpx
+import pytest
+
+from runloop_api_client.sdk import RunloopSDK, AsyncRunloopSDK
+
+
+class StorageTransport:
+ def __init__(self, headers: dict[str, str] | None, status: int = 200, *, binary: bool = False) -> None:
+ self.headers = headers
+ self.status = status
+ self.binary = binary
+ self.uploads: list[httpx.Request] = []
+ self.completions = 0
+
+ def __call__(self, request: httpx.Request) -> httpx.Response:
+ if request.url.host == "storage.invalid":
+ self.uploads.append(request)
+ assert request.method == "PUT"
+ assert request.content == b"hello"
+ assert request.headers.get("x-ms-blob-type") == (self.headers or {}).get("x-ms-blob-type")
+ assert not {"authorization", "cookie", "x-api-default", "x-api-option"}.intersection(request.headers)
+ return httpx.Response(
+ self.status, text="synthetic-storage-secret", headers={"location": "https://elsewhere.invalid"}
+ )
+ assert request.headers["authorization"] == "Bearer synthetic-api-secret"
+ if request.url.path.endswith("/complete"):
+ self.completions += 1
+ else:
+ assert request.headers["x-api-option"] == "api-only"
+ body = json.loads(request.content)
+ assert body["name"] == "upload"
+ assert body["content_type"] == ("binary" if self.binary else "text")
+ assert body["metadata"] == {"key": "value"}
+ assert body.get("ttl_ms") is None
+ data: dict[str, Any] = {
+ "id": "obj_upload",
+ "upload_url": "https://storage.invalid/blob?sig=synthetic-signed-secret",
+ }
+ if self.headers is not None:
+ data["upload_headers"] = self.headers
+ return httpx.Response(200, json=data)
+
+
+class APIOnlyAuth(httpx.Auth):
+ @override
+ def auth_flow(self, request: httpx.Request) -> Generator[httpx.Request, httpx.Response, None]:
+ assert request.url.host == "api.invalid", "Client authentication must not run for signed storage requests"
+ yield request
+
+
+@pytest.mark.parametrize("headers", [None, {}, {"x-ms-blob-type": "BlockBlob"}])
+@pytest.mark.parametrize("binary", [False, True], ids=["text", "bytes"])
+def test_signed_upload_lifecycle(headers: dict[str, str] | None, binary: bool) -> None:
+ transport = StorageTransport(headers, binary=binary)
+ with RunloopSDK(
+ bearer_token="synthetic-api-secret",
+ base_url="https://api.invalid",
+ max_retries=0,
+ http_client=httpx.Client(
+ transport=httpx.MockTransport(transport),
+ headers={"x-api-default": "private", "Authorization": "Bearer private-default"},
+ cookies={"session": "private"},
+ auth=APIOnlyAuth(),
+ follow_redirects=True,
+ ),
+ ) as sdk:
+ if binary:
+ obj = sdk.storage_object.upload_from_bytes(
+ b"hello",
+ name="upload",
+ content_type="binary",
+ metadata={"key": "value"},
+ extra_headers={"x-api-option": "api-only"},
+ )
+ else:
+ obj = sdk.storage_object.upload_from_text(
+ "hello",
+ name="upload",
+ metadata={"key": "value"},
+ extra_headers={"x-api-option": "api-only"},
+ )
+ assert obj.id == "obj_upload"
+ assert obj.upload_url is None
+ assert len(transport.uploads) == transport.completions == 1
+
+
+@pytest.mark.parametrize("headers", [None, {}, {"x-ms-blob-type": "BlockBlob"}])
+@pytest.mark.parametrize("binary", [False, True], ids=["text", "bytes"])
+@pytest.mark.asyncio
+async def test_async_signed_upload_lifecycle(headers: dict[str, str] | None, binary: bool) -> None:
+ transport = StorageTransport(headers, binary=binary)
+ async with AsyncRunloopSDK(
+ bearer_token="synthetic-api-secret",
+ base_url="https://api.invalid",
+ max_retries=0,
+ http_client=httpx.AsyncClient(
+ transport=httpx.MockTransport(transport),
+ headers={"x-api-default": "private", "Authorization": "Bearer private-default"},
+ cookies={"session": "private"},
+ auth=APIOnlyAuth(),
+ follow_redirects=True,
+ ),
+ ) as sdk:
+ if binary:
+ obj = await sdk.storage_object.upload_from_bytes(
+ b"hello",
+ name="upload",
+ content_type="binary",
+ metadata={"key": "value"},
+ extra_headers={"x-api-option": "api-only"},
+ )
+ else:
+ obj = await sdk.storage_object.upload_from_text(
+ "hello",
+ name="upload",
+ metadata={"key": "value"},
+ extra_headers={"x-api-option": "api-only"},
+ )
+ assert obj.id == "obj_upload"
+ assert obj.upload_url is None
+ assert len(transport.uploads) == transport.completions == 1
+
+
+@pytest.mark.parametrize("status", [307, 403, 500])
+def test_failed_upload_does_not_complete_retry_redirect_or_expose_credentials(status: int) -> None:
+ transport = StorageTransport({"x-ms-blob-type": "BlockBlob"}, status)
+ with RunloopSDK(
+ bearer_token="synthetic-api-secret",
+ base_url="https://api.invalid",
+ max_retries=2,
+ http_client=httpx.Client(transport=httpx.MockTransport(transport), follow_redirects=True),
+ ) as sdk:
+ with pytest.raises(httpx.HTTPStatusError) as error:
+ sdk.storage_object.upload_from_text(
+ "hello", name="upload", metadata={"key": "value"}, extra_headers={"x-api-option": "api-only"}
+ )
+ assert str(status) in str(error.value)
+ assert "synthetic" not in str(error.value)
+ assert len(transport.uploads) == 1
+ assert transport.completions == 0
+
+
+def test_upload_instructions_are_copied_per_object() -> None:
+ from runloop_api_client.sdk import StorageObject
+
+ requests: list[httpx.Request] = []
+
+ def respond(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ return httpx.Response(200)
+
+ headers = {"x-upload-object": "first"}
+ with RunloopSDK(bearer_token="synthetic", http_client=httpx.Client(transport=httpx.MockTransport(respond))) as sdk:
+ first = StorageObject(sdk.api, "first", "https://storage.invalid/first", headers)
+ headers["x-upload-object"] = "second"
+ second = StorageObject(sdk.api, "second", "https://storage.invalid/second", headers)
+ headers.clear()
+ first.upload_content(iter([b"one", b"two"]))
+ second.upload_content(b"three")
+ assert [(r.url.path, r.headers["x-upload-object"], r.content) for r in requests] == [
+ ("/first", "first", b"onetwo"),
+ ("/second", "second", b"three"),
+ ]
+
+
+@pytest.mark.asyncio
+async def test_async_distinct_uploads_keep_independent_credentials() -> None:
+ import asyncio
+
+ from runloop_api_client.sdk import AsyncStorageObject
+
+ requests: list[httpx.Request] = []
+ both_started = asyncio.Event()
+
+ async def respond(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ if len(requests) == 2:
+ both_started.set()
+ await both_started.wait()
+ return httpx.Response(200)
+
+ headers = {"x-upload-object": "first"}
+ async with AsyncRunloopSDK(
+ bearer_token="synthetic", http_client=httpx.AsyncClient(transport=httpx.MockTransport(respond))
+ ) as sdk:
+ first = AsyncStorageObject(sdk.api, "first", "https://storage.invalid/first", headers)
+ headers["x-upload-object"] = "second"
+ second = AsyncStorageObject(sdk.api, "second", "https://storage.invalid/second", headers)
+ headers.clear()
+ await asyncio.wait_for(
+ asyncio.gather(first.upload_content("first"), second.upload_content("second")), timeout=10
+ )
+ assert len(requests) == 2
+ for request in requests:
+ assert request.url.path == "/" + request.headers["x-upload-object"]
+ assert request.content.decode() == request.headers["x-upload-object"]
diff --git a/tests/smoketests/README.md b/tests/smoketests/README.md
index f2aeb982b..541c3caec 100644
--- a/tests/smoketests/README.md
+++ b/tests/smoketests/README.md
@@ -1,6 +1,6 @@
# Smoke tests
-End-to-end smoke tests run against the real API to validate critical flows (devboxes, snapshots, blueprints, executions/log tailing, scenarios/benchmarks). Theses smoketests run both the
+End-to-end smoke tests run against the real API to validate critical flows (devboxes, snapshots, blueprints, executions/log tailing). Theses smoketests run both the
async and sync clients.
- Local run (requires `RUNLOOP_API_KEY`):
diff --git a/tests/smoketests/sdk/README.md b/tests/smoketests/sdk/README.md
index 65e42d5e9..e8af93904 100644
--- a/tests/smoketests/sdk/README.md
+++ b/tests/smoketests/sdk/README.md
@@ -6,7 +6,7 @@ Comprehensive end-to-end tests for the object-oriented Python SDK (`runloop_api_
The Python SDK provides both synchronous and asynchronous interfaces:
- **Synchronous SDK**: `RunloopSDK` with `Agent`, `Devbox`, `Blueprint`, `Snapshot`, `StorageObject`
-- **Asynchronous SDK**: `AsyncRunloopSDK` with `AsyncAgent`, `AsyncDevbox`, `AsyncBlueprint`, `AsyncSnapshot`, `AsyncStorageObject`
+- **Asynchronous SDK**: `AsyncRunloopSDK` with `AsyncDevbox`, `AsyncBlueprint`, `AsyncSnapshot`, `AsyncStorageObject`
These tests ensure both interfaces work correctly in real-world scenarios.
diff --git a/tests/smoketests/sdk/test_agent.py b/tests/smoketests/sdk/test_agent.py
deleted file mode 100644
index 7ddfb6f70..000000000
--- a/tests/smoketests/sdk/test_agent.py
+++ /dev/null
@@ -1,210 +0,0 @@
-"""Synchronous SDK smoke tests for Agent operations."""
-
-from __future__ import annotations
-
-import pytest
-
-from runloop_api_client.sdk import RunloopSDK
-from tests.smoketests.utils import unique_name
-from runloop_api_client.types.shared_params import AgentSource
-
-pytestmark = [pytest.mark.smoketest]
-
-THIRTY_SECOND_TIMEOUT = 30
-TWO_MINUTE_TIMEOUT = 120
-AGENT_VERSION = "1.2.3"
-
-
-class TestAgentLifecycle:
- """Test basic agent lifecycle operations."""
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- def test_agent_create_basic(self, sdk_client: RunloopSDK) -> None:
- """Test creating a basic agent."""
- name = unique_name("sdk-agent-test-basic")
- agent = sdk_client.agent.create(
- name=name,
- version=AGENT_VERSION,
- source={
- "type": "npm",
- "npm": {
- "package_name": "@runloop/hello-world-agent",
- },
- },
- )
-
- try:
- assert agent is not None
- assert agent.id is not None
- assert len(agent.id) > 0
-
- # Verify agent information
- info = agent.get_info()
- assert info.id == agent.id
- assert info.name == name
- finally:
- # TODO: Add agent cleanup once delete endpoint is implemented
- # Currently agents don't have a delete method - they persist after tests
- # Once implemented, add: agent.delete()
- pass
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- def test_agent_get_info(self, sdk_client: RunloopSDK) -> None:
- """Test retrieving agent information."""
- name = unique_name("sdk-agent-test-info")
- agent = sdk_client.agent.create(
- name=name,
- version=AGENT_VERSION,
- source={
- "type": "npm",
- "npm": {
- "package_name": "@runloop/hello-world-agent",
- },
- },
- )
-
- try:
- info = agent.get_info()
-
- assert info.id == agent.id
- assert info.name == name
- assert info.create_time_ms > 0
- assert isinstance(info.is_public, bool)
- finally:
- # TODO: Add agent cleanup once delete endpoint is implemented
- pass
-
-
-class TestAgentListing:
- """Test agent listing and retrieval operations."""
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- def test_list_agents(self, sdk_client: RunloopSDK) -> None:
- """Test listing agents."""
- agents = sdk_client.agent.list(limit=10)
-
- assert isinstance(agents, list)
- # List might be empty, that's okay
- assert len(agents) >= 0
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- def test_get_agent_by_id(self, sdk_client: RunloopSDK) -> None:
- """Test retrieving agent by ID."""
- # Create an agent
- created = sdk_client.agent.create(
- name=unique_name("sdk-agent-test-retrieve"),
- version=AGENT_VERSION,
- source={
- "type": "npm",
- "npm": {
- "package_name": "@runloop/hello-world-agent",
- },
- },
- )
-
- try:
- # Retrieve it by ID
- retrieved = sdk_client.agent.from_id(created.id)
- assert retrieved.id == created.id
-
- # Verify it's the same agent
- info = retrieved.get_info()
- assert info.id == created.id
- finally:
- # TODO: Add agent cleanup once delete endpoint is implemented
- pass
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- def test_list_multiple_agents(self, sdk_client: RunloopSDK) -> None:
- """Test listing multiple agents after creation."""
- source_config: AgentSource = {
- "type": "npm",
- "npm": {
- "package_name": "@runloop/hello-world-agent",
- },
- }
-
- # Create multiple agents
- agent1 = sdk_client.agent.create(
- name=unique_name("sdk-agent-test-list-1"), source=source_config, version=AGENT_VERSION
- )
- agent2 = sdk_client.agent.create(
- name=unique_name("sdk-agent-test-list-2"), source=source_config, version=AGENT_VERSION
- )
- agent3 = sdk_client.agent.create(
- name=unique_name("sdk-agent-test-list-3"), source=source_config, version=AGENT_VERSION
- )
-
- try:
- # List agents
- agents = sdk_client.agent.list(limit=100)
-
- assert isinstance(agents, list)
- assert len(agents) >= 3
-
- # Verify our agents are in the list
- agent_ids = [a.id for a in agents]
- assert agent1.id in agent_ids
- assert agent2.id in agent_ids
- assert agent3.id in agent_ids
- finally:
- # TODO: Add agent cleanup once delete endpoint is implemented
- # Should delete: agent1, agent2, agent3
- pass
-
-
-class TestAgentCreationVariations:
- """Test different agent creation scenarios."""
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- def test_agent_with_source_npm(self, sdk_client: RunloopSDK) -> None:
- """Test creating an agent with npm source."""
- name = unique_name("sdk-agent-test-npm")
-
- agent = sdk_client.agent.create(
- name=name,
- version=AGENT_VERSION,
- source={
- "type": "npm",
- "npm": {
- "package_name": "@runloop/example-agent",
- },
- },
- )
-
- try:
- assert agent.id is not None
- info = agent.get_info()
- assert info.name == name
- assert info.source is not None
- assert info.source.type == "npm"
- finally:
- # TODO: Add agent cleanup once delete endpoint is implemented
- pass
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- def test_agent_with_source_git(self, sdk_client: RunloopSDK) -> None:
- """Test creating an agent with git source."""
- name = unique_name("sdk-agent-test-git")
-
- agent = sdk_client.agent.create(
- name=name,
- version=AGENT_VERSION,
- source={
- "type": "git",
- "git": {
- "repository": "https://github.com/runloop/example-agent",
- "ref": "main",
- },
- },
- )
-
- try:
- assert agent.id is not None
- info = agent.get_info()
- assert info.name == name
- assert info.source is not None
- assert info.source.type == "git"
- finally:
- # TODO: Add agent cleanup once delete endpoint is implemented
- pass
diff --git a/tests/smoketests/sdk/test_async_agent.py b/tests/smoketests/sdk/test_async_agent.py
deleted file mode 100644
index 36129605f..000000000
--- a/tests/smoketests/sdk/test_async_agent.py
+++ /dev/null
@@ -1,210 +0,0 @@
-"""Asynchronous SDK smoke tests for Agent operations."""
-
-from __future__ import annotations
-
-import pytest
-
-from runloop_api_client.sdk import AsyncRunloopSDK
-from tests.smoketests.utils import unique_name
-from runloop_api_client.types.shared_params import AgentSource
-
-pytestmark = [pytest.mark.smoketest, pytest.mark.asyncio]
-
-THIRTY_SECOND_TIMEOUT = 30
-TWO_MINUTE_TIMEOUT = 120
-AGENT_VERSION = "1.2.3"
-
-
-class TestAsyncAgentLifecycle:
- """Test basic async agent lifecycle operations."""
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- async def test_agent_create_basic(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test creating a basic agent."""
- name = unique_name("sdk-async-agent-test-basic")
- agent = await async_sdk_client.agent.create(
- name=name,
- version=AGENT_VERSION,
- source={
- "type": "npm",
- "npm": {
- "package_name": "@runloop/hello-world-agent",
- },
- },
- )
-
- try:
- assert agent is not None
- assert agent.id is not None
- assert len(agent.id) > 0
-
- # Verify agent information
- info = await agent.get_info()
- assert info.id == agent.id
- assert info.name == name
- finally:
- # TODO: Add agent cleanup once delete endpoint is implemented
- # Currently agents don't have a delete method - they persist after tests
- # Once implemented, add: await agent.delete()
- pass
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- async def test_agent_get_info(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test retrieving agent information."""
- name = unique_name("sdk-async-agent-test-info")
- agent = await async_sdk_client.agent.create(
- name=name,
- version=AGENT_VERSION,
- source={
- "type": "npm",
- "npm": {
- "package_name": "@runloop/hello-world-agent",
- },
- },
- )
-
- try:
- info = await agent.get_info()
-
- assert info.id == agent.id
- assert info.name == name
- assert info.create_time_ms > 0
- assert isinstance(info.is_public, bool)
- finally:
- # TODO: Add agent cleanup once delete endpoint is implemented
- pass
-
-
-class TestAsyncAgentListing:
- """Test async agent listing and retrieval operations."""
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- async def test_list_agents(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test listing agents."""
- agents = await async_sdk_client.agent.list(limit=10)
-
- assert isinstance(agents, list)
- # List might be empty, that's okay
- assert len(agents) >= 0
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- async def test_get_agent_by_id(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test retrieving agent by ID."""
- # Create an agent
- created = await async_sdk_client.agent.create(
- name=unique_name("sdk-async-agent-test-retrieve"),
- version=AGENT_VERSION,
- source={
- "type": "npm",
- "npm": {
- "package_name": "@runloop/hello-world-agent",
- },
- },
- )
-
- try:
- # Retrieve it by ID
- retrieved = async_sdk_client.agent.from_id(created.id)
- assert retrieved.id == created.id
-
- # Verify it's the same agent
- info = await retrieved.get_info()
- assert info.id == created.id
- finally:
- # TODO: Add agent cleanup once delete endpoint is implemented
- pass
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- async def test_list_multiple_agents(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test listing multiple agents after creation."""
- source_config: AgentSource = {
- "type": "npm",
- "npm": {
- "package_name": "@runloop/hello-world-agent",
- },
- }
-
- # Create multiple agents
- agent1 = await async_sdk_client.agent.create(
- name=unique_name("sdk-async-agent-test-list-1"), source=source_config, version=AGENT_VERSION
- )
- agent2 = await async_sdk_client.agent.create(
- name=unique_name("sdk-async-agent-test-list-2"), source=source_config, version=AGENT_VERSION
- )
- agent3 = await async_sdk_client.agent.create(
- name=unique_name("sdk-async-agent-test-list-3"), source=source_config, version=AGENT_VERSION
- )
-
- try:
- # List agents
- agents = await async_sdk_client.agent.list(limit=100)
-
- assert isinstance(agents, list)
- assert len(agents) >= 3
-
- # Verify our agents are in the list
- agent_ids = [a.id for a in agents]
- assert agent1.id in agent_ids
- assert agent2.id in agent_ids
- assert agent3.id in agent_ids
- finally:
- # TODO: Add agent cleanup once delete endpoint is implemented
- # Should delete: agent1, agent2, agent3
- pass
-
-
-class TestAsyncAgentCreationVariations:
- """Test different async agent creation scenarios."""
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- async def test_agent_with_source_npm(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test creating an agent with npm source."""
- name = unique_name("sdk-async-agent-test-npm")
-
- agent = await async_sdk_client.agent.create(
- name=name,
- version=AGENT_VERSION,
- source={
- "type": "npm",
- "npm": {
- "package_name": "@runloop/example-agent",
- },
- },
- )
-
- try:
- assert agent.id is not None
- info = await agent.get_info()
- assert info.name == name
- assert info.source is not None
- assert info.source.type == "npm"
- finally:
- # TODO: Add agent cleanup once delete endpoint is implemented
- pass
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- async def test_agent_with_source_git(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test creating an agent with git source."""
- name = unique_name("sdk-async-agent-test-git")
-
- agent = await async_sdk_client.agent.create(
- name=name,
- version=AGENT_VERSION,
- source={
- "type": "git",
- "git": {
- "repository": "https://github.com/runloop/example-agent",
- "ref": "main",
- },
- },
- )
-
- try:
- assert agent.id is not None
- info = await agent.get_info()
- assert info.name == name
- assert info.source is not None
- assert info.source.type == "git"
- finally:
- # TODO: Add agent cleanup once delete endpoint is implemented
- pass
diff --git a/tests/smoketests/sdk/test_async_benchmark.py b/tests/smoketests/sdk/test_async_benchmark.py
deleted file mode 100644
index 7316355a6..000000000
--- a/tests/smoketests/sdk/test_async_benchmark.py
+++ /dev/null
@@ -1,192 +0,0 @@
-"""Asynchronous SDK smoke tests for AsyncBenchmark operations.
-
-These tests validate the AsyncBenchmark class against the real API.
-We create a dedicated smoketest benchmark and scenarios with consistent names
-so that resources are reused across test runs (since there's no delete endpoint).
-"""
-
-from __future__ import annotations
-
-from typing import List, Tuple
-
-import pytest
-
-from runloop_api_client import AsyncRunloopSDK
-from runloop_api_client.sdk import AsyncScenario, AsyncBenchmark, AsyncScenarioRun, AsyncBenchmarkRun
-
-pytestmark = [pytest.mark.smoketest]
-
-TWO_MINUTE_TIMEOUT = 120
-
-# Consistent names for smoketest resources
-SMOKETEST_BENCHMARK_NAME = "sdk-smoketest-benchmark"
-SMOKETEST_SCENARIO_1_NAME = "sdk-smoketest-scenario-1"
-SMOKETEST_SCENARIO_2_NAME = "sdk-smoketest-scenario-2"
-
-
-async def get_or_create_scenario(
- async_sdk_client: AsyncRunloopSDK,
- name: str,
- problem_statement: str,
-) -> AsyncScenario:
- """Get an existing scenario by name or create a new one."""
- # Check if scenario already exists
- scenarios = await async_sdk_client.scenario.list(name=name, limit=1)
- for scenario in scenarios:
- # Return the first matching scenario
- return scenario
-
- # Create a new scenario using the SDK builder
- return await (
- async_sdk_client.scenario.builder(name)
- .with_problem_statement(problem_statement)
- .add_shell_command_scorer("pass-scorer", command="exit 0")
- .push()
- )
-
-
-async def get_or_create_benchmark(
- async_sdk_client: AsyncRunloopSDK,
- name: str,
- scenario_ids: List[str],
-) -> AsyncBenchmark:
- """Get an existing benchmark by name or create a new one."""
- # Check if benchmark already exists
- benchmarks = await async_sdk_client.benchmark.list(name=name, limit=1)
- for benchmark in benchmarks:
- # Return the first matching benchmark
- return benchmark
-
- # Create a new benchmark
- return await async_sdk_client.benchmark.create(
- name=name,
- scenario_ids=scenario_ids,
- description="Smoketest benchmark for SDK testing",
- )
-
-
-@pytest.fixture(scope="module")
-async def smoketest_benchmark(
- async_sdk_client: AsyncRunloopSDK,
-) -> Tuple[AsyncBenchmark, List[str]]:
- """Create or retrieve the smoketest benchmark and scenario IDs."""
- # Create or get scenarios
- scenario_1 = await get_or_create_scenario(
- async_sdk_client,
- SMOKETEST_SCENARIO_1_NAME,
- "Smoketest scenario 1 - basic validation",
- )
- scenario_2 = await get_or_create_scenario(
- async_sdk_client,
- SMOKETEST_SCENARIO_2_NAME,
- "Smoketest scenario 2 - basic validation",
- )
-
- scenario_ids = [scenario_1.id, scenario_2.id]
-
- # Create or get benchmark
- benchmark = await get_or_create_benchmark(
- async_sdk_client,
- SMOKETEST_BENCHMARK_NAME,
- scenario_ids,
- )
-
- return benchmark, scenario_ids
-
-
-class TestAsyncBenchmarkRun:
- """Test AsyncBenchmark run operations."""
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_benchmark_run_and_cancel(
- self,
- async_sdk_client: AsyncRunloopSDK,
- smoketest_benchmark: Tuple[AsyncBenchmark, List[str]],
- ) -> None:
- """Test starting and canceling a benchmark run.
-
- This test:
- 1. Uses the smoketest benchmark fixture
- 2. Starts a new benchmark run via the AsyncBenchmark class
- 3. Validates the run object
- 4. Cancels the run
- """
- benchmark, scenario_ids = smoketest_benchmark
-
- # Start a run
- run = await benchmark.start_run(run_name="sdk-smoketest-async-benchmark-run")
- scenario_runs: List[AsyncScenarioRun] = []
-
- try:
- assert isinstance(run, AsyncBenchmarkRun)
- assert run.id is not None
- assert run.benchmark_id == benchmark.id
-
- # Get run info
- info = await run.get_info()
- assert info.id == run.id
- assert info.state == "running"
-
- # Run the scenarios
- for scenario_id in scenario_ids:
- scenario = async_sdk_client.scenario.from_id(scenario_id)
- scenario_runs.append(
- await scenario.run_async(
- benchmark_run_id=run.id, run_name="sdk-smoketest-async-benchmark-run-scenario"
- )
- )
-
- benchmark_scenario_runs = await run.list_scenario_runs()
- assert isinstance(benchmark_scenario_runs, list)
- assert len(benchmark_scenario_runs) == len(scenario_runs)
- for scenario_run in benchmark_scenario_runs:
- assert isinstance(scenario_run, AsyncScenarioRun)
- assert any(
- scenario_run.id == scenario_run.id and scenario_run.devbox_id == scenario_run.devbox_id
- for scenario_run in scenario_runs
- )
-
- # Cancel the scenario run
- for scenario_run in scenario_runs:
- scenario_result = await scenario_run.cancel()
- assert scenario_result.state in ["canceled", "completed"]
-
- # Cancel the benchmark run
- result = await run.cancel()
- assert result.state in ["canceled", "completed"]
-
- except Exception:
- # Ensure cleanup on any error
- for scenario_run in scenario_runs:
- await scenario_run.cancel()
- await run.cancel()
- raise
-
-
-class TestAsyncBenchmarkListRuns:
- """Test AsyncBenchmark list_runs operations."""
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_list_runs(
- self,
- smoketest_benchmark: Tuple[AsyncBenchmark, List[str]],
- ) -> None:
- """Test listing benchmark runs.
-
- This test:
- 1. Uses the smoketest benchmark fixture
- 2. Lists its runs
- 3. Validates returned objects are AsyncBenchmarkRun instances
- """
- benchmark, _ = smoketest_benchmark
-
- runs = await benchmark.list_runs()
- assert isinstance(runs, list)
- if not runs:
- pytest.skip("No runs available to test")
-
- # Verify returned items are AsyncBenchmarkRun objects
- for run in runs:
- assert isinstance(run, AsyncBenchmarkRun)
- assert run.id is not None
- assert run.benchmark_id == benchmark.id
diff --git a/tests/smoketests/sdk/test_async_devbox.py b/tests/smoketests/sdk/test_async_devbox.py
index 68af2b508..49e18094a 100644
--- a/tests/smoketests/sdk/test_async_devbox.py
+++ b/tests/smoketests/sdk/test_async_devbox.py
@@ -125,6 +125,22 @@ async def test_exec_with_exit_code(self, shared_devbox: AsyncDevbox) -> None:
assert result.success is False
assert await result.stdout(num_lines=1) == ""
+ @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
+ async def test_stdin_ordered_roundtrip(self, shared_devbox: AsyncDevbox) -> None:
+ execution = await shared_devbox.cmd.exec_async("cat", attach_stdin=True)
+ completed = False
+ try:
+ await execution.send_std_in("first\n")
+ await execution.send_std_in("second\n")
+ await execution.close_std_in()
+ result = await execution.result()
+ completed = True
+ assert result.exit_code == 0
+ assert await result.stdout() == "first\nsecond\n"
+ finally:
+ if not completed:
+ await execution.kill()
+
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
async def test_exec_async_command(self, shared_devbox: AsyncDevbox) -> None:
"""Test executing a command asynchronously with exec_async."""
@@ -423,21 +439,6 @@ async def test_keep_alive(self, shared_devbox: AsyncDevbox) -> None:
class TestAsyncDevboxNetworking:
"""Test async devbox networking operations."""
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_create_ssh_key(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test creating SSH key for devbox."""
- devbox = await async_sdk_client.devbox.create(
- name=unique_name("sdk-async-devbox-ssh"),
- launch_parameters={"resource_size_request": "SMALL", "keep_alive_time_seconds": 60 * 5},
- )
-
- try:
- ssh_key = await devbox.net.create_ssh_key()
- assert ssh_key is not None
- assert ssh_key.ssh_private_key is not None
- finally:
- await devbox.shutdown()
-
@pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
async def test_create_with_tunnel_param(self, async_sdk_client: AsyncRunloopSDK) -> None:
"""Test creating a devbox with tunnel configuration in create params."""
@@ -748,296 +749,6 @@ async def test_exec_with_truncated_stdout_num_lines(self, shared_devbox: AsyncDe
# both Python and TypeScript SDKs and needs to be fixed together.
-class TestAsyncDevboxNamedShell:
- """Test named shell functionality for stateful command execution."""
-
- @pytest.fixture(scope="class")
- async def devbox(self, async_sdk_client: AsyncRunloopSDK) -> AsyncIterator[AsyncDevbox]:
- """Create a devbox for shell tests."""
- devbox = await async_sdk_client.devbox.create(
- name=unique_name("sdk-async-devbox-named-shell"),
- launch_parameters={"resource_size_request": "SMALL", "keep_alive_time_seconds": 60 * 5},
- )
- try:
- yield devbox
- finally:
- try:
- await devbox.shutdown()
- except Exception:
- pass
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_shell_exec_basic(self, devbox: AsyncDevbox) -> None:
- """Test basic shell execution."""
- shell = devbox.shell("test-shell-1")
- result = await shell.exec('echo "Hello from named shell!"')
- assert result.exit_code == 0
- output = await result.stdout()
- assert "Hello from named shell!" in output
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_shell_exec_cwd_persistence(self, devbox: AsyncDevbox) -> None:
- """Test that CWD persists across commands."""
- shell = devbox.shell("test-shell-2")
-
- # Create a directory and change to it
- await shell.exec("mkdir -p /tmp/test-shell-dir")
- await shell.exec("cd /tmp/test-shell-dir")
-
- # Verify we're in the new directory
- pwd_result = await shell.exec("pwd")
- pwd = (await pwd_result.stdout()).strip()
- assert pwd == "/tmp/test-shell-dir"
-
- # Create a file in the current directory
- await shell.exec('echo "test content" > testfile.txt')
-
- # Verify the file exists and has the correct content in the current directory
- cat_result = await shell.exec("cat testfile.txt")
- assert cat_result.exit_code == 0
- cat_output = await cat_result.stdout()
- assert "test content" in cat_output
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_shell_exec_env_persistence(self, devbox: AsyncDevbox) -> None:
- """Test that environment variables persist across commands."""
- shell = devbox.shell("test-shell-3")
-
- # Set an environment variable
- await shell.exec('export TEST_VAR="test-value-123"')
-
- # Verify the variable persists in the next command
- echo_result = await shell.exec("echo $TEST_VAR")
- output = (await echo_result.stdout()).strip()
- assert output == "test-value-123"
-
- # Set another variable and verify both persist
- await shell.exec('export ANOTHER_VAR="another-value"')
- both_result = await shell.exec('echo "$TEST_VAR:$ANOTHER_VAR"')
- both_output = (await both_result.stdout()).strip()
- assert both_output == "test-value-123:another-value"
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_shell_exec_combined_cwd_and_env(self, devbox: AsyncDevbox) -> None:
- """Test combined CWD and environment persistence."""
- shell = devbox.shell("test-shell-4")
-
- # Set environment and change directory
- await shell.exec('export PROJECT_DIR="/tmp/my-project"')
- await shell.exec("mkdir -p $PROJECT_DIR")
- await shell.exec("cd $PROJECT_DIR")
-
- # Verify both persist
- pwd_result = await shell.exec("pwd")
- pwd = (await pwd_result.stdout()).strip()
- assert pwd == "/tmp/my-project"
-
- # Create a file using the environment variable
- await shell.exec('echo "project file" > $PROJECT_DIR/file.txt')
-
- # Verify file exists
- ls_result = await shell.exec("ls file.txt")
- assert ls_result.exit_code == 0
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_shell_exec_async_basic(self, devbox: AsyncDevbox) -> None:
- """Test basic async shell execution."""
- shell = devbox.shell("test-shell-5")
- execution = await shell.exec_async('sleep 1 && echo "Async command completed"')
- assert execution is not None
- assert execution.execution_id is not None
-
- # Wait for completion
- result = await execution.result()
- assert result.exit_code == 0
- output = await result.stdout()
- assert "Async command completed" in output
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_shell_exec_async_stateful(self, devbox: AsyncDevbox) -> None:
- """Test stateful async execution."""
- shell = devbox.shell("test-shell-6")
-
- # Set state in first command
- await shell.exec('export ASYNC_VAR="async-value"')
- await shell.exec("cd /tmp")
-
- # Start async command that uses the state
- execution = await shell.exec_async('echo "CWD: $(pwd), VAR: $ASYNC_VAR"')
- result = await execution.result()
-
- assert result.exit_code == 0
- output = await result.stdout()
- assert "CWD: /tmp" in output
- assert "VAR: async-value" in output
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_shell_exec_sequential(self, devbox: AsyncDevbox) -> None:
- """Test sequential execution (queuing)."""
- shell = devbox.shell("test-shell-7")
-
- # Start multiple commands - they should execute sequentially
- import time
-
- start_time = time.time()
- await shell.exec('sleep 1 && echo "first"')
- await shell.exec('sleep 1 && echo "second"')
- await shell.exec('sleep 1 && echo "third"')
- end_time = time.time()
-
- # Verify they took at least 3 seconds (sequential execution)
- duration = end_time - start_time
- assert duration >= 2.9 # Allow some margin for overhead
-
- # Verify all commands executed in order
- final_result = await shell.exec('echo "done"')
- output = await final_result.stdout()
- assert "done" in output
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_shell_exec_async_sequential(self, devbox: AsyncDevbox) -> None:
- """Test sequential async execution with queuing."""
- shell = devbox.shell("test-shell-8")
-
- # Start multiple async commands - they should queue and execute sequentially
- exec1 = shell.exec_async('sleep 1 && echo "async-first"')
- exec2 = shell.exec_async('sleep 1 && echo "async-second"')
- exec3 = shell.exec_async('sleep 1 && echo "async-third"')
-
- # Wait for all to complete
- result1 = await (await exec1).result()
- result2 = await (await exec2).result()
- result3 = await (await exec3).result()
-
- # Verify all completed successfully
- assert result1.exit_code == 0
- assert result2.exit_code == 0
- assert result3.exit_code == 0
-
- # Verify outputs
- assert "async-first" in await result1.stdout()
- assert "async-second" in await result2.stdout()
- assert "async-third" in await result3.stdout()
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_shell_exec_with_streaming(self, devbox: AsyncDevbox) -> None:
- """Test shell exec with streaming callbacks."""
- shell = devbox.shell("test-shell-9")
- stdout_lines: list[str] = []
-
- result = await shell.exec(
- 'echo "line1" && echo "line2" && echo "line3"', stdout=lambda line: stdout_lines.append(line)
- )
-
- assert result.success is True
- assert result.exit_code == 0
- assert len(stdout_lines) > 0
- stdout_combined = "".join(stdout_lines)
- assert "line1" in stdout_combined
- assert "line2" in stdout_combined
- assert "line3" in stdout_combined
- # Verify streaming captured same data as result
- assert stdout_combined == await result.stdout()
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_shell_exec_async_with_streaming(self, devbox: AsyncDevbox) -> None:
- """Test shell exec_async with streaming callbacks."""
- shell = devbox.shell("test-shell-10")
- stdout_lines: list[str] = []
-
- execution = await shell.exec_async(
- 'echo "async-line1" && sleep 0.5 && echo "async-line2"', stdout=lambda line: stdout_lines.append(line)
- )
-
- result = await execution.result()
- assert result.success is True
- assert result.exit_code == 0
-
- stdout_combined = "".join(stdout_lines)
- assert "async-line1" in stdout_combined
- assert "async-line2" in stdout_combined
- # Verify streaming captured same data as result
- assert stdout_combined == await result.stdout()
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_multiple_named_shells_independent(self, devbox: AsyncDevbox) -> None:
- """Test that multiple named shells maintain independent state."""
- shell1 = devbox.shell("independent-shell-1")
- shell2 = devbox.shell("independent-shell-2")
-
- # Set different state in each shell
- await shell1.exec('export VAR="shell1-value"')
- await shell1.exec("cd /tmp")
- await shell2.exec('export VAR="shell2-value"')
- await shell2.exec("cd /home")
-
- # Verify each shell maintains its own state
- result1 = await shell1.exec('echo "$VAR:$(pwd)"')
- output1 = (await result1.stdout()).strip()
- assert "shell1-value" in output1
- assert "/tmp" in output1
-
- result2 = await shell2.exec('echo "$VAR:$(pwd)"')
- output2 = (await result2.stdout()).strip()
- assert "shell2-value" in output2
- assert "/home" in output2
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_shell_auto_generated_name(self, devbox: AsyncDevbox) -> None:
- """Test auto-generated shell name."""
- # Create shell without providing a name - should auto-generate UUID
- shell = devbox.shell()
- assert shell is not None
-
- result = await shell.exec('echo "test"')
- assert result.exit_code == 0
- output = await result.stdout()
- assert "test" in output
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_shell_exec_with_stderr_streaming(self, devbox: AsyncDevbox) -> None:
- """Test shell exec with stderr streaming callback."""
- shell = devbox.shell("test-shell-stderr")
- stderr_lines: list[str] = []
-
- result = await shell.exec('echo "error output" >&2', stderr=lambda line: stderr_lines.append(line))
-
- assert result.success is True
- assert result.exit_code == 0
- assert len(stderr_lines) > 0
- stderr_combined = "".join(stderr_lines)
- assert "error output" in stderr_combined
- # Verify streaming captured same data as result
- assert stderr_combined == await result.stderr()
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_shell_exec_async_with_both_streams(self, devbox: AsyncDevbox) -> None:
- """Test shell exec_async with both stdout and stderr streaming callbacks."""
- shell = devbox.shell("test-shell-both-streams")
- stdout_lines: list[str] = []
- stderr_lines: list[str] = []
-
- execution = await shell.exec_async(
- 'echo "to stdout" && echo "to stderr" >&2',
- stdout=lambda line: stdout_lines.append(line),
- stderr=lambda line: stderr_lines.append(line),
- )
-
- result = await execution.result()
- assert result.success is True
- assert result.exit_code == 0
-
- stdout_combined = "".join(stdout_lines)
- stderr_combined = "".join(stderr_lines)
-
- assert "to stdout" in stdout_combined
- assert "to stderr" in stderr_combined
-
- # Verify streaming captured same data as result
- assert stdout_combined == await result.stdout()
- assert stderr_combined == await result.stderr()
-
-
class TestAsyncDevboxLogs:
"""Test async devbox logs retrieval functionality."""
@@ -1071,20 +782,3 @@ async def test_logs_with_execution_filter(self, shared_devbox: AsyncDevbox) -> N
config=PollingConfig(timeout_seconds=10, interval_seconds=1),
)
assert any(test_message in (log.message or "") for log in logs.logs)
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- async def test_logs_with_shell_name_filter(self, shared_devbox: AsyncDevbox) -> None:
- """Test retrieving devbox logs filtered by shell name."""
- shell_name = "async-test-logs-shell"
- shell = shared_devbox.shell(shell_name)
-
- test_message = "async shell log test"
- result = await shell.exec(f'echo "{test_message}"')
- assert result.exit_code == 0
-
- logs = await async_poll_until(
- retriever=lambda: shared_devbox.logs(shell_name=shell_name),
- is_terminal=lambda l: any(test_message in (log.message or "") for log in l.logs),
- config=PollingConfig(timeout_seconds=10, interval_seconds=1),
- )
- assert any(test_message in (log.message or "") for log in logs.logs)
diff --git a/tests/smoketests/sdk/test_async_scenario.py b/tests/smoketests/sdk/test_async_scenario.py
deleted file mode 100644
index 4faeae958..000000000
--- a/tests/smoketests/sdk/test_async_scenario.py
+++ /dev/null
@@ -1,260 +0,0 @@
-"""Asynchronous SDK smoke tests for Scenario operations."""
-
-from __future__ import annotations
-
-import pytest
-
-from runloop_api_client.sdk import AsyncRunloopSDK, AsyncScenarioBuilder
-from tests.smoketests.utils import unique_name
-from runloop_api_client.types import ScenarioView
-from runloop_api_client.sdk._types import SDKScenarioUpdateParams
-from runloop_api_client.sdk._helpers import filter_params
-
-pytestmark = [pytest.mark.smoketest]
-
-TWO_MINUTE_TIMEOUT = 120
-FIVE_MINUTE_TIMEOUT = 300
-TEN_MINUTE_TIMEOUT = 600
-
-# Metadata tag for all smoketest scenarios (for easy identification/cleanup)
-SMOKETEST_METADATA = {"smoketest": "true"}
-
-
-async def push_or_update_scenario(sdk_client: AsyncRunloopSDK, builder: AsyncScenarioBuilder) -> ScenarioView:
- """Push a new scenario or update existing one with the same name.
-
- This is a workaround until scenario delete endpoint is available.
- Uses fixed scenario names to avoid littering the platform with test scenarios.
-
- When updating an existing scenario, this function will delete the OLD blueprint/snapshot
- that's no longer needed (if different from the new one). The NEW blueprint/snapshot
- is kept so the scenario remains runnable.
- """
- # Check if scenario already exists
- scenarios = await sdk_client.scenario.list(name=builder.name, limit=1)
-
- if scenarios:
- # Get old scenario info to find old blueprint/snapshot IDs
- scenario = scenarios[0]
- old_scenario_info = await scenario.get_info()
- old_env = old_scenario_info.environment
- old_blueprint_id = old_env.blueprint_id if old_env else None
- old_snapshot_id = old_env.snapshot_id if old_env else None
-
- # Get new blueprint/snapshot IDs from builder
- new_blueprint_id = builder._blueprint.id if builder._blueprint else None
- new_snapshot_id = builder._snapshot.id if builder._snapshot else None
-
- # Update existing scenario with builder's params
- params = builder.build()
- result = await scenario.update(**filter_params(params, SDKScenarioUpdateParams))
-
- # Delete OLD blueprint/snapshot if they're being replaced
- if old_blueprint_id and old_blueprint_id != new_blueprint_id:
- await sdk_client.blueprint.from_id(old_blueprint_id).delete()
-
- if old_snapshot_id and old_snapshot_id != new_snapshot_id:
- await sdk_client.snapshot.from_id(old_snapshot_id).delete()
-
- return result
- else:
- # Create new scenario - keep the blueprint/snapshot (scenario needs them)
- scenario = await builder.push()
- return await scenario.get_info()
-
-
-class TestAsyncScenarioRetrieval:
- """Test async scenario retrieval operations."""
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_list_scenarios(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test listing scenarios."""
- scenarios = await async_sdk_client.scenario.list(limit=10)
-
- assert isinstance(scenarios, list)
- # List might be empty, that's okay
- assert len(scenarios) >= 0
-
-
-class TestAsyncScenarioFromId:
- """Test async scenario from_id operations."""
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_from_id_get_info(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test from_id followed by get_info on an existing scenario."""
- # First list to get an existing scenario
- scenarios = await async_sdk_client.scenario.list(limit=1)
- if not scenarios:
- pytest.skip("No scenarios available to test from_id")
-
- scenario_id = scenarios[0].id
-
- # Get scenario by ID
- scenario = async_sdk_client.scenario.from_id(scenario_id)
- assert scenario.id == scenario_id
-
- # Verify we can get info
- info = await scenario.get_info()
- assert info.id == scenario_id
- assert info.name is not None
-
-
-class TestAsyncScenarioRun:
- """Test async scenario run operations."""
-
- @pytest.mark.timeout(FIVE_MINUTE_TIMEOUT)
- async def test_scenario_run_async_lifecycle(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test running a scenario and accessing the devbox.
-
- This test:
- 1. Lists scenarios to find one to run
- 2. Starts a scenario run
- 3. Waits for environment to be ready
- 4. Accesses the devbox
- 5. Cancels the run
- """
- # Find a scenario to run
- scenarios = await async_sdk_client.scenario.list(limit=1)
- if not scenarios:
- pytest.skip("No scenarios available to test run")
-
- # Pick the first scenario
- scenario = scenarios[0]
-
- # Start a run
- run = await scenario.run_async(run_name="sdk-smoketest-async-run")
- devbox = None
-
- try:
- assert run.id is not None
- assert run.devbox_id is not None
-
- # Wait for environment to be ready
- await run.await_env_ready()
-
- # Access devbox
- devbox = run.devbox
- info = await devbox.get_info()
- assert info.status == "running"
-
- # Get run info
- info = await run.get_info()
- assert info.id == run.id
- assert info.state in ["running", "scoring", "scored", "completed"]
-
- finally:
- # Clean up - cancel the run
- try:
- await run.cancel()
- except Exception:
- if devbox:
- await devbox.shutdown()
-
- @pytest.mark.timeout(FIVE_MINUTE_TIMEOUT)
- async def test_scenario_run(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test run convenience method."""
- # Find a scenario to run
- scenarios = await async_sdk_client.scenario.list(limit=1)
- if not scenarios:
- pytest.skip("No scenarios available to test run")
-
- scenario = scenarios[0]
-
- # Start a run and wait for environment in one call
- run = await scenario.run(run_name="sdk-smoketest-async-await")
- devbox = None
-
- try:
- assert run.id is not None
- assert run.devbox_id is not None
-
- # Devbox should be ready
- devbox = run.devbox
- info = await devbox.get_info()
- assert info.status == "running"
-
- finally:
- # Clean up
- try:
- await run.cancel()
- except Exception:
- if devbox:
- await devbox.shutdown()
-
-
-class TestAsyncScenarioBuilder:
- """Test AsyncScenarioBuilder operations."""
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- async def test_scenario_builder_minimal(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test creating/updating a minimal scenario with just problem statement and scorer."""
- builder = (
- async_sdk_client.scenario.builder("sdk-smoketest-async-builder-minimal")
- .with_problem_statement("Async minimal test problem statement")
- .with_metadata(SMOKETEST_METADATA)
- .add_shell_command_scorer("async-minimal-scorer", command="echo 1.0")
- )
-
- info = await push_or_update_scenario(async_sdk_client, builder)
-
- assert info.name == "sdk-smoketest-async-builder-minimal"
- assert info.input_context.problem_statement == "Async minimal test problem statement"
- assert len(info.scoring_contract.scoring_function_parameters) == 1
- assert info.scoring_contract.scoring_function_parameters[0].name == "async-minimal-scorer"
-
- @pytest.mark.timeout(FIVE_MINUTE_TIMEOUT)
- async def test_scenario_builder_with_blueprint(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test creating/updating a scenario from a blueprint."""
- blueprint = await async_sdk_client.blueprint.create(
- name=unique_name("sdk-smoketest-async-scenario-bp"),
- dockerfile="FROM ubuntu:22.04",
- )
-
- builder = (
- async_sdk_client.scenario.builder("sdk-smoketest-async-builder-blueprint")
- .from_blueprint(blueprint)
- .with_working_directory("/home/user")
- .with_problem_statement("Async blueprint test problem")
- .with_metadata(SMOKETEST_METADATA)
- .add_shell_command_scorer("async-blueprint-scorer", command="echo 1.0")
- )
-
- info = await push_or_update_scenario(async_sdk_client, builder)
-
- assert info.name == "sdk-smoketest-async-builder-blueprint"
- assert info.input_context.problem_statement == "Async blueprint test problem"
- assert info.environment is not None
- assert info.environment.blueprint_id == blueprint.id
- assert info.environment.working_directory == "/home/user"
-
- @pytest.mark.timeout(TEN_MINUTE_TIMEOUT)
- async def test_scenario_builder_with_snapshot(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test creating/updating a scenario from a snapshot."""
- # Create blueprint -> devbox -> snapshot chain
- blueprint = await async_sdk_client.blueprint.create(
- name=unique_name("sdk-smoketest-async-scenario-snap-bp"),
- dockerfile="FROM ubuntu:22.04",
- )
- devbox = await async_sdk_client.devbox.create(blueprint_id=blueprint.id)
- snapshot = await devbox.snapshot_disk(name=unique_name("sdk-smoketest-async-scenario-snap"))
-
- # Shut down the devbox - it's not needed after creating the snapshot
- try:
- await devbox.shutdown()
- except Exception:
- pass
-
- builder = (
- async_sdk_client.scenario.builder("sdk-smoketest-async-builder-snapshot")
- .from_snapshot(snapshot)
- .with_problem_statement("Async snapshot test problem")
- .with_metadata(SMOKETEST_METADATA)
- .add_shell_command_scorer("async-snapshot-scorer", command="echo 1.0")
- )
-
- info = await push_or_update_scenario(async_sdk_client, builder)
-
- assert info.name == "sdk-smoketest-async-builder-snapshot"
- assert info.input_context.problem_statement == "Async snapshot test problem"
- assert info.environment is not None
- assert info.environment.snapshot_id == snapshot.id
diff --git a/tests/smoketests/sdk/test_async_scorer.py b/tests/smoketests/sdk/test_async_scorer.py
deleted file mode 100644
index a98ae677f..000000000
--- a/tests/smoketests/sdk/test_async_scorer.py
+++ /dev/null
@@ -1,103 +0,0 @@
-"""Asynchronous SDK smoke tests for Scorer operations."""
-
-from __future__ import annotations
-
-import pytest
-
-from runloop_api_client.sdk import AsyncRunloopSDK
-from tests.smoketests.utils import unique_name
-
-pytestmark = [pytest.mark.smoketest, pytest.mark.asyncio]
-
-THIRTY_SECOND_TIMEOUT = 30
-ONE_MINUTE_TIMEOUT = 60
-
-
-class TestAsyncScorerLifecycle:
- """Test basic async scorer lifecycle operations."""
-
- @pytest.mark.timeout(ONE_MINUTE_TIMEOUT)
- async def test_scorer_create_basic(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test creating a basic scorer."""
- scorer_type = unique_name("sdk-async-scorer-basic")
- scorer = await async_sdk_client.scorer.create(
- type=scorer_type,
- bash_script="echo 'score=1.0'",
- )
-
- assert scorer is not None
- assert scorer.id is not None
- assert len(scorer.id) > 0
-
- # Verify it's created successfully
- info = await scorer.get_info()
- assert info.type == scorer_type
- assert info.bash_script == "echo 'score=1.0'"
-
- @pytest.mark.timeout(ONE_MINUTE_TIMEOUT)
- async def test_scorer_get_info(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test retrieving scorer information."""
- scorer_type = unique_name("sdk-async-scorer-info")
- scorer = await async_sdk_client.scorer.create(
- type=scorer_type,
- bash_script="echo 'score=0.5'",
- )
-
- info = await scorer.get_info()
-
- assert info.id == scorer.id
- assert info.type == scorer_type
-
- @pytest.mark.timeout(ONE_MINUTE_TIMEOUT)
- async def test_scorer_update(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test updating a scorer."""
- scorer_type = unique_name("sdk-async-scorer-update")
- scorer = await async_sdk_client.scorer.create(
- type=scorer_type,
- bash_script="echo 'score=0.0'",
- )
-
- updated_type = unique_name("sdk-async-scorer-updated")
- result = await scorer.update(
- type=updated_type,
- bash_script="echo 'score=1.0'",
- )
-
- assert result is not None
-
- # Verify the update
- info = await scorer.get_info()
- assert info.type == updated_type
- assert info.bash_script == "echo 'score=1.0'"
-
-
-class TestAsyncScorerListing:
- """Test async scorer listing and retrieval operations."""
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- async def test_list_scorers(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test listing scorers."""
- scorers = await async_sdk_client.scorer.list(limit=10)
-
- assert isinstance(scorers, list)
- # List might be empty, that's okay
- assert len(scorers) >= 0
-
- @pytest.mark.timeout(ONE_MINUTE_TIMEOUT)
- async def test_get_scorer_by_id(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test retrieving scorer by ID."""
- # Create a scorer
- scorer_type = unique_name("sdk-async-scorer-retrieve")
- created = await async_sdk_client.scorer.create(
- type=scorer_type,
- bash_script="echo 'score=1.0'",
- )
-
- # Retrieve it by ID
- retrieved = async_sdk_client.scorer.from_id(created.id)
- assert retrieved.id == created.id
-
- # Verify it's the same scorer
- info = await retrieved.get_info()
- assert info.id == created.id
- assert info.type == scorer_type
diff --git a/tests/smoketests/sdk/test_async_sdk.py b/tests/smoketests/sdk/test_async_sdk.py
index fd8c03ca8..634405d03 100644
--- a/tests/smoketests/sdk/test_async_sdk.py
+++ b/tests/smoketests/sdk/test_async_sdk.py
@@ -22,9 +22,6 @@ async def test_sdk_instance_creation(self, async_sdk_client: AsyncRunloopSDK) ->
assert async_sdk_client.blueprint is not None
assert async_sdk_client.snapshot is not None
assert async_sdk_client.storage_object is not None
- assert async_sdk_client.scorer is not None
- assert async_sdk_client.agent is not None
- assert async_sdk_client.scenario is not None
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
async def test_legacy_api_access(self, async_sdk_client: AsyncRunloopSDK) -> None:
diff --git a/tests/smoketests/sdk/test_async_secret.py b/tests/smoketests/sdk/test_async_secret.py
index a8d3bfb86..5c73ef85f 100644
--- a/tests/smoketests/sdk/test_async_secret.py
+++ b/tests/smoketests/sdk/test_async_secret.py
@@ -4,6 +4,7 @@
import pytest
+from runloop_api_client import NotFoundError
from runloop_api_client.sdk import AsyncRunloopSDK
from tests.smoketests.utils import unique_name
@@ -16,57 +17,51 @@
class TestAsyncSecretLifecycle:
"""Test async secret lifecycle operations: create, get_info, update, list, delete."""
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
+ @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
async def test_secret_full_lifecycle(self, async_sdk_client: AsyncRunloopSDK) -> None:
- """Test complete async secret lifecycle: create, get_info, update (both ways), list, from_name, delete."""
- secret_name = unique_name("SDK_ASYNC_TEST_SECRET").upper().replace("-", "_")
-
- # Create
- secret = await async_sdk_client.secret.create(name=secret_name, value="initial-value")
- assert secret is not None
- assert secret.name == secret_name
-
+ """Exercise name and ID operations in one lifecycle, including name reuse."""
+ name = "sec_" + unique_name("SDK_TEST_SECRET").replace("-", "_")
+ secret = await async_sdk_client.secret.create(name=name, value="synthetic-initial")
+ assert secret.name == name
+ assert secret.id is not None and secret.id.startswith("sec_")
+ ids = [secret.id]
try:
- # get_info uses GET /v1/secrets/{name}
- info = await secret.get_info()
- assert info.id.startswith("sec_")
- assert info.name == secret_name
+ named = async_sdk_client.secret.from_name(name)
+ assert named.id is None
+ info = await named.get_info()
+ assert info.id == secret.id
assert info.create_time_ms > 0
- create_time_ms = info.create_time_ms
-
- # Update via AsyncSecretOps
- updated = await async_sdk_client.secret.update(secret, "updated-via-ops")
- assert updated.name == secret_name
- updated_info = await updated.get_info()
- assert updated_info.update_time_ms >= create_time_ms
-
- # Update via instance method
- instance_updated_info = await secret.update("updated-via-instance")
- assert instance_updated_info.name == secret_name
-
- # List
- secrets = await async_sdk_client.secret.list()
- assert isinstance(secrets, list)
- assert len(secrets) > 0
- found = next((s for s in secrets if s.name == secret_name), None)
- assert found is not None
- assert found.name == secret_name
-
- # from_name (no API call)
- by_name = async_sdk_client.secret.from_name(secret_name)
- assert by_name.name == secret_name
- by_name_info = await by_name.get_info()
- assert by_name_info.id == info.id
-
+ updated = await async_sdk_client.secret.update(secret, "synthetic-ops")
+ assert updated.name == name
+ assert (await updated.get_info()).update_time_ms >= info.create_time_ms
+ assert (await secret.update("synthetic-name")).name == name
+ assert any(item.id == secret.id for item in (await async_sdk_client.secret.list()))
+
+ exact = async_sdk_client.secret.from_id(secret.id)
+ assert (await exact.get_info()).name == name
+ assert (await exact.update("synthetic-id")).id == exact.id
+ assert (await exact.delete()).id == exact.id
+ replacement = await async_sdk_client.secret.create(name=name, value="synthetic-replacement")
+ assert replacement.id is not None
+ ids.append(replacement.id)
+ assert replacement.id != exact.id
+ # The original wrapper still follows its name, not its captured ID.
+ assert (await secret.get_info()).id == replacement.id
+ assert secret.id == exact.id
+ with pytest.raises(NotFoundError):
+ await exact.get_info()
+ with pytest.raises(NotFoundError):
+ await exact.update("must-not-update-replacement")
+ with pytest.raises(NotFoundError):
+ await exact.delete()
+ assert (await secret.delete()).id == replacement.id
+ assert all(item.name != name for item in (await async_sdk_client.secret.list()))
finally:
- # Delete
- deleted = await secret.delete()
- assert deleted is not None
- assert deleted.name == secret_name
-
- # Verify deleted
- remaining = await async_sdk_client.secret.list()
- assert all(s.name != secret_name for s in remaining)
+ for id in ids:
+ try:
+ await async_sdk_client.secret.from_id(id).delete()
+ except NotFoundError:
+ pass
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
async def test_secret_delete_via_ops(self, async_sdk_client: AsyncRunloopSDK) -> None:
diff --git a/tests/smoketests/sdk/test_benchmark.py b/tests/smoketests/sdk/test_benchmark.py
deleted file mode 100644
index 2dfe5bb6c..000000000
--- a/tests/smoketests/sdk/test_benchmark.py
+++ /dev/null
@@ -1,190 +0,0 @@
-"""Synchronous SDK smoke tests for Benchmark operations.
-
-These tests validate the Benchmark class against the real API.
-We create a dedicated smoketest benchmark and scenarios with consistent names
-so that resources are reused across test runs (since there's no delete endpoint).
-"""
-
-from __future__ import annotations
-
-from typing import List, Tuple
-
-import pytest
-
-from runloop_api_client import RunloopSDK
-from runloop_api_client.sdk import Scenario, Benchmark, ScenarioRun, BenchmarkRun
-
-pytestmark = [pytest.mark.smoketest]
-
-TWO_MINUTE_TIMEOUT = 120
-
-# Consistent names for smoketest resources
-SMOKETEST_BENCHMARK_NAME = "sdk-smoketest-benchmark"
-SMOKETEST_SCENARIO_1_NAME = "sdk-smoketest-scenario-1"
-SMOKETEST_SCENARIO_2_NAME = "sdk-smoketest-scenario-2"
-
-
-def get_or_create_scenario(
- sdk_client: RunloopSDK,
- name: str,
- problem_statement: str,
-) -> Scenario:
- """Get an existing scenario by name or create a new one."""
- # Check if scenario already exists
- scenarios = sdk_client.scenario.list(name=name, limit=1)
- for scenario in scenarios:
- # Return the first matching scenario
- return scenario
-
- # Create a new scenario using the SDK builder
- return (
- sdk_client.scenario.builder(name)
- .with_problem_statement(problem_statement)
- .add_shell_command_scorer("pass-scorer", command="exit 0")
- .push()
- )
-
-
-def get_or_create_benchmark(
- sdk_client: RunloopSDK,
- name: str,
- scenario_ids: List[str],
-) -> Benchmark:
- """Get an existing benchmark by name or create a new one."""
- # Check if benchmark already exists
- benchmarks = sdk_client.benchmark.list(name=name, limit=1)
- for benchmark in benchmarks:
- # Return the first matching benchmark
- return benchmark
-
- # Create a new benchmark
- return sdk_client.benchmark.create(
- name=name,
- scenario_ids=scenario_ids,
- description="Smoketest benchmark for SDK testing",
- )
-
-
-@pytest.fixture(scope="module")
-def smoketest_benchmark(
- sdk_client: RunloopSDK,
-) -> Tuple[Benchmark, List[str]]:
- """Create or retrieve the smoketest benchmark and scenarios."""
- # Create or get scenarios
- scenario_1 = get_or_create_scenario(
- sdk_client,
- SMOKETEST_SCENARIO_1_NAME,
- "Smoketest scenario 1 - basic validation",
- )
- scenario_2 = get_or_create_scenario(
- sdk_client,
- SMOKETEST_SCENARIO_2_NAME,
- "Smoketest scenario 2 - basic validation",
- )
-
- scenario_ids = [scenario_1.id, scenario_2.id]
-
- # Create or get benchmark
- benchmark = get_or_create_benchmark(
- sdk_client,
- SMOKETEST_BENCHMARK_NAME,
- scenario_ids,
- )
-
- return benchmark, scenario_ids
-
-
-class TestBenchmarkRun:
- """Test Benchmark run operations."""
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_benchmark_run_lifecycle(
- self,
- sdk_client: RunloopSDK,
- smoketest_benchmark: Tuple[Benchmark, List[str]],
- ) -> None:
- """Test starting and canceling a benchmark run.
-
- This test:
- 1. Uses the smoketest benchmark fixture
- 2. Starts a new benchmark run via the Benchmark class
- 3. Validates the run object
- 4. Cancels the run
- """
- benchmark, scenario_ids = smoketest_benchmark
-
- # Start a run
- run = benchmark.start_run(run_name="sdk-smoketest-benchmark-run")
- scenario_runs: List[ScenarioRun] = []
-
- try:
- assert isinstance(run, BenchmarkRun)
- assert run.id is not None
- assert run.benchmark_id == benchmark.id
-
- # Get run info
- info = run.get_info()
- assert info.id == run.id
- assert info.state == "running"
-
- # Start a scenario run
- for scenario_id in scenario_ids:
- scenario = sdk_client.scenario.from_id(scenario_id)
- scenario_runs.append(
- scenario.run(benchmark_run_id=run.id, run_name="sdk-smoketest-benchmark-run-scenario")
- )
-
- benchmark_scenario_runs = run.list_scenario_runs()
- assert isinstance(benchmark_scenario_runs, list)
- assert len(benchmark_scenario_runs) == len(scenario_runs)
- for scenario_run in benchmark_scenario_runs:
- assert isinstance(scenario_run, ScenarioRun)
- assert any(
- scenario_run.id == scenario_run.id and scenario_run.devbox_id == scenario_run.devbox_id
- for scenario_run in scenario_runs
- )
-
- # Cancel the scenario runs
- for scenario_run in scenario_runs:
- scenario_result = scenario_run.cancel()
- assert scenario_result.state in ["canceled", "completed"]
-
- # Cancel the benchmark run
- result = run.cancel()
- assert result.state in ["canceled", "completed"]
-
- except Exception:
- # Ensure cleanup on any error
- for scenario_run in scenario_runs:
- scenario_run.cancel()
- run.cancel()
- raise
-
-
-class TestBenchmarkListRuns:
- """Test Benchmark list_runs operations."""
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_list_runs(
- self,
- smoketest_benchmark: Tuple[Benchmark, List[str]],
- ) -> None:
- """Test listing benchmark runs.
-
- This test:
- 1. Uses the smoketest benchmark fixture
- 2. Lists its runs
- 3. Validates returned objects are BenchmarkRun instances
- """
- benchmark, _ = smoketest_benchmark
-
- runs = benchmark.list_runs()
- assert isinstance(runs, list)
- if not runs:
- pytest.skip("No runs available to test")
-
- # Verify returned items are BenchmarkRun objects
- for run in runs:
- assert isinstance(run, BenchmarkRun)
- assert run.id is not None
- assert run.benchmark_id == benchmark.id
diff --git a/tests/smoketests/sdk/test_devbox.py b/tests/smoketests/sdk/test_devbox.py
index cedd1bc75..64a232119 100644
--- a/tests/smoketests/sdk/test_devbox.py
+++ b/tests/smoketests/sdk/test_devbox.py
@@ -125,6 +125,22 @@ def test_exec_with_exit_code(self, shared_devbox: Devbox) -> None:
assert result.success is False
assert "" == result.stdout(num_lines=1)
+ @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
+ def test_stdin_ordered_roundtrip(self, shared_devbox: Devbox) -> None:
+ execution = shared_devbox.cmd.exec_async("cat", attach_stdin=True)
+ completed = False
+ try:
+ execution.send_std_in("first\n")
+ execution.send_std_in("second\n")
+ execution.close_std_in()
+ result = execution.result()
+ completed = True
+ assert result.exit_code == 0
+ assert result.stdout() == "first\nsecond\n"
+ finally:
+ if not completed:
+ execution.kill()
+
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
def test_exec_async_command(self, shared_devbox: Devbox) -> None:
"""Test executing a command asynchronously."""
@@ -419,21 +435,6 @@ def test_keep_alive(self, shared_devbox: Devbox) -> None:
class TestDevboxNetworking:
"""Test devbox networking operations."""
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_create_ssh_key(self, sdk_client: RunloopSDK) -> None:
- """Test creating SSH key for devbox."""
- devbox = sdk_client.devbox.create(
- name=unique_name("sdk-devbox-ssh"),
- launch_parameters={"resource_size_request": "SMALL", "keep_alive_time_seconds": 60 * 5},
- )
-
- try:
- ssh_key = devbox.net.create_ssh_key()
- assert ssh_key is not None
- assert ssh_key.ssh_private_key is not None
- finally:
- devbox.shutdown()
-
@pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
def test_create_with_tunnel_param(self, sdk_client: RunloopSDK) -> None:
"""Test creating a devbox with tunnel configuration in create params."""
@@ -740,289 +741,6 @@ def test_exec_with_truncated_stdout_num_lines(self, shared_devbox: Devbox) -> No
# both Python and TypeScript SDKs and needs to be fixed together.
-class TestDevboxNamedShell:
- """Test named shell functionality for stateful command execution."""
-
- @pytest.fixture(scope="class")
- def devbox(self, sdk_client: RunloopSDK) -> Devbox:
- """Create a devbox for shell tests."""
- return sdk_client.devbox.create(
- name=unique_name("sdk-devbox-named-shell"),
- launch_parameters={"resource_size_request": "SMALL", "keep_alive_time_seconds": 60 * 5},
- )
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_shell_exec_basic(self, devbox: Devbox) -> None:
- """Test basic shell execution."""
- shell = devbox.shell("test-shell-1")
- result = shell.exec('echo "Hello from named shell!"')
- assert result.exit_code == 0
- output = result.stdout()
- assert "Hello from named shell!" in output
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_shell_exec_cwd_persistence(self, devbox: Devbox) -> None:
- """Test that CWD persists across commands."""
- shell = devbox.shell("test-shell-2")
-
- # Create a directory and change to it
- shell.exec("mkdir -p /tmp/test-shell-dir")
- shell.exec("cd /tmp/test-shell-dir")
-
- # Verify we're in the new directory
- pwd_result = shell.exec("pwd")
- pwd = pwd_result.stdout().strip()
- assert pwd == "/tmp/test-shell-dir"
-
- # Create a file in the current directory
- shell.exec('echo "test content" > testfile.txt')
-
- # Verify the file exists in the current directory
- ls_result = shell.exec("ls testfile.txt")
- assert ls_result.exit_code == 0
- ls_output = ls_result.stdout()
- assert "testfile.txt" in ls_output
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_shell_exec_env_persistence(self, devbox: Devbox) -> None:
- """Test that environment variables persist across commands."""
- shell = devbox.shell("test-shell-3")
-
- # Set an environment variable
- shell.exec('export TEST_VAR="test-value-123"')
-
- # Verify the variable persists in the next command
- echo_result = shell.exec("echo $TEST_VAR")
- output = echo_result.stdout().strip()
- assert output == "test-value-123"
-
- # Set another variable and verify both persist
- shell.exec('export ANOTHER_VAR="another-value"')
- both_result = shell.exec('echo "$TEST_VAR:$ANOTHER_VAR"')
- both_output = both_result.stdout().strip()
- assert both_output == "test-value-123:another-value"
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_shell_exec_combined_cwd_and_env(self, devbox: Devbox) -> None:
- """Test combined CWD and environment persistence."""
- shell = devbox.shell("test-shell-4")
-
- # Set environment and change directory
- shell.exec('export PROJECT_DIR="/tmp/my-project"')
- shell.exec("mkdir -p $PROJECT_DIR")
- shell.exec("cd $PROJECT_DIR")
-
- # Verify both persist
- pwd_result = shell.exec("pwd")
- pwd = pwd_result.stdout().strip()
- assert pwd == "/tmp/my-project"
-
- # Create a file using the environment variable
- shell.exec('echo "project file" > $PROJECT_DIR/file.txt')
-
- # Verify file exists
- ls_result = shell.exec("ls file.txt")
- assert ls_result.exit_code == 0
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_shell_exec_async_basic(self, devbox: Devbox) -> None:
- """Test basic async shell execution."""
- shell = devbox.shell("test-shell-5")
- execution = shell.exec_async('sleep 1 && echo "Async command completed"')
- assert execution is not None
- assert execution.execution_id is not None
-
- # Wait for completion
- result = execution.result()
- assert result.exit_code == 0
- output = result.stdout()
- assert "Async command completed" in output
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_shell_exec_async_stateful(self, devbox: Devbox) -> None:
- """Test stateful async execution."""
- shell = devbox.shell("test-shell-6")
-
- # Set state in first command
- shell.exec('export ASYNC_VAR="async-value"')
- shell.exec("cd /tmp")
-
- # Start async command that uses the state
- execution = shell.exec_async('echo "CWD: $(pwd), VAR: $ASYNC_VAR"')
- result = execution.result()
-
- assert result.exit_code == 0
- output = result.stdout()
- assert "CWD: /tmp" in output
- assert "VAR: async-value" in output
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_shell_exec_sequential(self, devbox: Devbox) -> None:
- """Test sequential execution (queuing)."""
- shell = devbox.shell("test-shell-7")
-
- # Start multiple commands - they should execute sequentially
- import time
-
- start_time = time.time()
- shell.exec('sleep 1 && echo "first"')
- shell.exec('sleep 1 && echo "second"')
- shell.exec('sleep 1 && echo "third"')
- end_time = time.time()
-
- # Verify they took at least 3 seconds (sequential execution)
- duration = end_time - start_time
- assert duration >= 2.9 # Allow some margin for overhead
-
- # Verify all commands executed in order
- final_result = shell.exec('echo "done"')
- output = final_result.stdout()
- assert "done" in output
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_shell_exec_async_sequential(self, devbox: Devbox) -> None:
- """Test sequential async execution with queuing."""
- shell = devbox.shell("test-shell-8")
-
- # Start multiple async commands - they should queue and execute sequentially
- exec1 = shell.exec_async('sleep 1 && echo "async-first"')
- exec2 = shell.exec_async('sleep 1 && echo "async-second"')
- exec3 = shell.exec_async('sleep 1 && echo "async-third"')
-
- # Wait for all to complete
- result1 = exec1.result()
- result2 = exec2.result()
- result3 = exec3.result()
-
- # Verify all completed successfully
- assert result1.exit_code == 0
- assert result2.exit_code == 0
- assert result3.exit_code == 0
-
- # Verify outputs
- assert "async-first" in result1.stdout()
- assert "async-second" in result2.stdout()
- assert "async-third" in result3.stdout()
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_shell_exec_with_streaming(self, devbox: Devbox) -> None:
- """Test shell exec with streaming callbacks."""
- shell = devbox.shell("test-shell-9")
- stdout_lines: list[str] = []
-
- result = shell.exec(
- 'echo "line1" && echo "line2" && echo "line3"', stdout=lambda line: stdout_lines.append(line)
- )
-
- assert result.success is True
- assert result.exit_code == 0
- assert len(stdout_lines) > 0
- stdout_combined = "".join(stdout_lines)
- assert "line1" in stdout_combined
- assert "line2" in stdout_combined
- assert "line3" in stdout_combined
- # Verify streaming captured same data as result
- assert stdout_combined == result.stdout()
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_shell_exec_async_with_streaming(self, devbox: Devbox) -> None:
- """Test shell exec_async with streaming callbacks."""
- shell = devbox.shell("test-shell-10")
- stdout_lines: list[str] = []
-
- execution = shell.exec_async(
- 'echo "async-line1" && sleep 0.5 && echo "async-line2"', stdout=lambda line: stdout_lines.append(line)
- )
-
- result = execution.result()
- assert result.success is True
- assert result.exit_code == 0
-
- stdout_combined = "".join(stdout_lines)
- assert "async-line1" in stdout_combined
- assert "async-line2" in stdout_combined
- # Verify streaming captured same data as result
- assert stdout_combined == result.stdout()
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_multiple_named_shells_independent(self, devbox: Devbox) -> None:
- """Test that multiple named shells maintain independent state."""
- shell1 = devbox.shell("independent-shell-1")
- shell2 = devbox.shell("independent-shell-2")
-
- # Set different state in each shell
- shell1.exec('export VAR="shell1-value"')
- shell1.exec("cd /tmp")
- shell2.exec('export VAR="shell2-value"')
- shell2.exec("cd /home")
-
- # Verify each shell maintains its own state
- result1 = shell1.exec('echo "$VAR:$(pwd)"')
- output1 = result1.stdout().strip()
- assert "shell1-value" in output1
- assert "/tmp" in output1
-
- result2 = shell2.exec('echo "$VAR:$(pwd)"')
- output2 = result2.stdout().strip()
- assert "shell2-value" in output2
- assert "/home" in output2
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_shell_auto_generated_name(self, devbox: Devbox) -> None:
- """Test auto-generated shell name."""
- # Create shell without providing a name - should auto-generate UUID
- shell = devbox.shell()
- assert shell is not None
-
- result = shell.exec('echo "test"')
- assert result.exit_code == 0
- output = result.stdout()
- assert "test" in output
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_shell_exec_with_stderr_streaming(self, devbox: Devbox) -> None:
- """Test shell exec with stderr streaming callback."""
- shell = devbox.shell("test-shell-stderr")
- stderr_lines: list[str] = []
-
- result = shell.exec('echo "error output" >&2', stderr=lambda line: stderr_lines.append(line))
-
- assert result.success is True
- assert result.exit_code == 0
- assert len(stderr_lines) > 0
- stderr_combined = "".join(stderr_lines)
- assert "error output" in stderr_combined
- # Verify streaming captured same data as result
- assert stderr_combined == result.stderr()
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_shell_exec_async_with_both_streams(self, devbox: Devbox) -> None:
- """Test shell exec_async with both stdout and stderr streaming callbacks."""
- shell = devbox.shell("test-shell-both-streams")
- stdout_lines: list[str] = []
- stderr_lines: list[str] = []
-
- execution = shell.exec_async(
- 'echo "to stdout" && echo "to stderr" >&2',
- stdout=lambda line: stdout_lines.append(line),
- stderr=lambda line: stderr_lines.append(line),
- )
-
- result = execution.result()
- assert result.success is True
- assert result.exit_code == 0
-
- stdout_combined = "".join(stdout_lines)
- stderr_combined = "".join(stderr_lines)
-
- assert "to stdout" in stdout_combined
- assert "to stderr" in stderr_combined
-
- # Verify streaming captured same data as result
- assert stdout_combined == result.stdout()
- assert stderr_combined == result.stderr()
-
-
class TestDevboxLogs:
"""Test devbox logs retrieval functionality."""
@@ -1056,20 +774,3 @@ def test_logs_with_execution_filter(self, shared_devbox: Devbox) -> None:
config=PollingConfig(timeout_seconds=10, interval_seconds=1),
)
assert any(test_message in (log.message or "") for log in logs.logs)
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- def test_logs_with_shell_name_filter(self, shared_devbox: Devbox) -> None:
- """Test retrieving devbox logs filtered by shell name."""
- shell_name = "test-logs-shell"
- shell = shared_devbox.shell(shell_name)
-
- test_message = "shell log test"
- result = shell.exec(f'echo "{test_message}"')
- assert result.exit_code == 0
-
- logs = poll_until(
- retriever=lambda: shared_devbox.logs(shell_name=shell_name),
- is_terminal=lambda l: any(test_message in (log.message or "") for log in l.logs),
- config=PollingConfig(timeout_seconds=10, interval_seconds=1),
- )
- assert any(test_message in (log.message or "") for log in logs.logs)
diff --git a/tests/smoketests/sdk/test_scenario.py b/tests/smoketests/sdk/test_scenario.py
deleted file mode 100644
index 694f5800a..000000000
--- a/tests/smoketests/sdk/test_scenario.py
+++ /dev/null
@@ -1,259 +0,0 @@
-"""Synchronous SDK smoke tests for Scenario operations."""
-
-from __future__ import annotations
-
-import pytest
-
-from runloop_api_client.sdk import RunloopSDK, ScenarioBuilder
-from tests.smoketests.utils import unique_name
-from runloop_api_client.types import ScenarioView
-from runloop_api_client.sdk._types import SDKScenarioUpdateParams
-from runloop_api_client.sdk._helpers import filter_params
-
-pytestmark = [pytest.mark.smoketest]
-
-TWO_MINUTE_TIMEOUT = 120
-FIVE_MINUTE_TIMEOUT = 300
-TEN_MINUTE_TIMEOUT = 600
-
-# Metadata tag for all smoketest scenarios (for easy identification/cleanup)
-SMOKETEST_METADATA = {"smoketest": "true"}
-
-
-def push_or_update_scenario(sdk_client: RunloopSDK, builder: ScenarioBuilder) -> ScenarioView:
- """Push a new scenario or update existing one with the same name.
-
- This is a workaround until scenario delete endpoint is available.
- Uses fixed scenario names to avoid littering the platform with test scenarios.
-
- When updating an existing scenario, this function will delete the OLD blueprint/snapshot
- that's no longer needed (if different from the new one). The NEW blueprint/snapshot
- is kept so the scenario remains runnable.
- """
- # Check if scenario already exists
- scenarios = sdk_client.scenario.list(name=builder.name, limit=1)
-
- if scenarios:
- # Get old scenario info to find old blueprint/snapshot IDs
- scenario = scenarios[0]
- env = scenario.get_info().environment
- old_blueprint_id = env.blueprint_id if env else None
- old_snapshot_id = env.snapshot_id if env else None
-
- # Get new blueprint/snapshot IDs from builder
- new_blueprint_id = builder._blueprint.id if builder._blueprint else None
- new_snapshot_id = builder._snapshot.id if builder._snapshot else None
-
- # Update existing scenario with builder's params
- params = builder.build()
- result = scenario.update(**filter_params(params, SDKScenarioUpdateParams))
-
- # Delete OLD blueprint/snapshot if they're being replaced
- if old_blueprint_id and old_blueprint_id != new_blueprint_id:
- sdk_client.blueprint.from_id(old_blueprint_id).delete()
-
- if old_snapshot_id and old_snapshot_id != new_snapshot_id:
- sdk_client.snapshot.from_id(old_snapshot_id).delete()
-
- return result
- else:
- # Create new scenario - keep the blueprint/snapshot (scenario needs them)
- scenario = builder.push()
- return scenario.get_info()
-
-
-class TestScenarioRetrieval:
- """Test scenario retrieval operations."""
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_list_scenarios(self, sdk_client: RunloopSDK) -> None:
- """Test listing scenarios."""
- scenarios = sdk_client.scenario.list(limit=10)
-
- assert isinstance(scenarios, list)
- # List might be empty, that's okay
- assert len(scenarios) >= 0
-
-
-class TestScenarioFromId:
- """Test scenario from_id operations."""
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_from_id_get_info(self, sdk_client: RunloopSDK) -> None:
- """Test from_id followed by get_info on an existing scenario."""
- # First list to get an existing scenario
- scenarios = sdk_client.scenario.list(limit=1)
- if not scenarios:
- pytest.skip("No scenarios available to test from_id")
-
- scenario_id = scenarios[0].id
-
- # Get scenario by ID
- scenario = sdk_client.scenario.from_id(scenario_id)
- assert scenario.id == scenario_id
-
- # Verify we can get info
- info = scenario.get_info()
- assert info.id == scenario_id
- assert info.name is not None
-
-
-class TestScenarioRun:
- """Test scenario run operations."""
-
- @pytest.mark.timeout(FIVE_MINUTE_TIMEOUT)
- def test_scenario_run_async_lifecycle(self, sdk_client: RunloopSDK) -> None:
- """Test running a scenario and accessing the devbox.
-
- This test:
- 1. Lists scenarios to find one to run
- 2. Starts a scenario run
- 3. Waits for environment to be ready
- 4. Accesses the devbox
- 5. Cancels the run
- """
- # Find a scenario to run
- scenarios = sdk_client.scenario.list(limit=1)
- if not scenarios:
- pytest.skip("No scenarios available to test run")
-
- # Pick the first scenario
- scenario = scenarios[0]
-
- # Start a run
- run = scenario.run_async(run_name="sdk-smoketest-run")
- devbox = None
-
- try:
- assert run.id is not None
- assert run.devbox_id is not None
-
- # Wait for environment to be ready
- run.await_env_ready()
-
- # Access devbox
- devbox = run.devbox
- info = devbox.get_info()
- assert info.status == "running"
-
- # Get run info
- info = run.get_info()
- assert info.id == run.id
- assert info.state in ["running", "scoring", "scored", "completed"]
-
- finally:
- # Clean up - cancel the run
- try:
- run.cancel()
- except Exception:
- if devbox:
- devbox.shutdown()
-
- @pytest.mark.timeout(FIVE_MINUTE_TIMEOUT)
- def test_scenario_run(self, sdk_client: RunloopSDK) -> None:
- """Test run convenience method."""
- # Find a scenario to run
- scenarios = sdk_client.scenario.list(limit=1)
- if not scenarios:
- pytest.skip("No scenarios available to test run")
-
- scenario = scenarios[0]
-
- # Start a run and wait for environment in one call
- run = scenario.run(run_name="sdk-smoketest-await")
- devbox = None
-
- try:
- assert run.id is not None
- assert run.devbox_id is not None
-
- # Devbox should be ready
- devbox = run.devbox
- info = devbox.get_info()
- assert info.status == "running"
-
- finally:
- # Clean up
- try:
- run.cancel()
- except Exception:
- if devbox:
- devbox.shutdown()
-
-
-class TestScenarioBuilder:
- """Test ScenarioBuilder operations."""
-
- @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
- def test_scenario_builder_minimal(self, sdk_client: RunloopSDK) -> None:
- """Test creating/updating a minimal scenario with just problem statement and scorer."""
- builder = (
- sdk_client.scenario.builder("sdk-smoketest-builder-minimal")
- .with_problem_statement("Minimal test problem statement")
- .with_metadata(SMOKETEST_METADATA)
- .add_shell_command_scorer("minimal-scorer", command="echo 1.0")
- )
-
- info = push_or_update_scenario(sdk_client, builder)
-
- assert info.name == "sdk-smoketest-builder-minimal"
- assert info.input_context.problem_statement == "Minimal test problem statement"
- assert len(info.scoring_contract.scoring_function_parameters) == 1
- assert info.scoring_contract.scoring_function_parameters[0].name == "minimal-scorer"
-
- @pytest.mark.timeout(FIVE_MINUTE_TIMEOUT)
- def test_scenario_builder_with_blueprint(self, sdk_client: RunloopSDK) -> None:
- """Test creating/updating a scenario from a blueprint."""
- blueprint = sdk_client.blueprint.create(
- name=unique_name("sdk-smoketest-scenario-bp"),
- dockerfile="FROM ubuntu:22.04",
- )
-
- builder = (
- sdk_client.scenario.builder("sdk-smoketest-builder-blueprint")
- .from_blueprint(blueprint)
- .with_working_directory("/home/user")
- .with_problem_statement("Blueprint test problem")
- .with_metadata(SMOKETEST_METADATA)
- .add_shell_command_scorer("blueprint-scorer", command="echo 1.0")
- )
-
- info = push_or_update_scenario(sdk_client, builder)
-
- assert info.name == "sdk-smoketest-builder-blueprint"
- assert info.input_context.problem_statement == "Blueprint test problem"
- assert info.environment is not None
- assert info.environment.blueprint_id == blueprint.id
- assert info.environment.working_directory == "/home/user"
-
- @pytest.mark.timeout(TEN_MINUTE_TIMEOUT)
- def test_scenario_builder_with_snapshot(self, sdk_client: RunloopSDK) -> None:
- """Test creating/updating a scenario from a snapshot."""
- # Create blueprint -> devbox -> snapshot chain
- blueprint = sdk_client.blueprint.create(
- name=unique_name("sdk-smoketest-scenario-snap-bp"),
- dockerfile="FROM ubuntu:22.04",
- )
- devbox = sdk_client.devbox.create(blueprint_id=blueprint.id)
- snapshot = devbox.snapshot_disk(name=unique_name("sdk-smoketest-scenario-snap"))
-
- # Shut down the devbox - it's not needed after creating the snapshot
- try:
- devbox.shutdown()
- except Exception:
- pass
-
- builder = (
- sdk_client.scenario.builder("sdk-smoketest-builder-snapshot")
- .from_snapshot(snapshot)
- .with_problem_statement("Snapshot test problem")
- .with_metadata(SMOKETEST_METADATA)
- .add_shell_command_scorer("snapshot-scorer", command="echo 1.0")
- )
-
- info = push_or_update_scenario(sdk_client, builder)
-
- assert info.name == "sdk-smoketest-builder-snapshot"
- assert info.input_context.problem_statement == "Snapshot test problem"
- assert info.environment is not None
- assert info.environment.snapshot_id == snapshot.id
diff --git a/tests/smoketests/sdk/test_scorer.py b/tests/smoketests/sdk/test_scorer.py
deleted file mode 100644
index 700cfb1f7..000000000
--- a/tests/smoketests/sdk/test_scorer.py
+++ /dev/null
@@ -1,103 +0,0 @@
-"""Synchronous SDK smoke tests for Scorer operations."""
-
-from __future__ import annotations
-
-import pytest
-
-from runloop_api_client.sdk import RunloopSDK
-from tests.smoketests.utils import unique_name
-
-pytestmark = [pytest.mark.smoketest]
-
-THIRTY_SECOND_TIMEOUT = 30
-ONE_MINUTE_TIMEOUT = 60
-
-
-class TestScorerLifecycle:
- """Test basic scorer lifecycle operations."""
-
- @pytest.mark.timeout(ONE_MINUTE_TIMEOUT)
- def test_scorer_create_basic(self, sdk_client: RunloopSDK) -> None:
- """Test creating a basic scorer."""
- scorer_type = unique_name("sdk-scorer-basic")
- scorer = sdk_client.scorer.create(
- type=scorer_type,
- bash_script="echo 'score=1.0'",
- )
-
- assert scorer is not None
- assert scorer.id is not None
- assert len(scorer.id) > 0
-
- # Verify it's created successfully
- info = scorer.get_info()
- assert info.type == scorer_type
- assert info.bash_script == "echo 'score=1.0'"
-
- @pytest.mark.timeout(ONE_MINUTE_TIMEOUT)
- def test_scorer_get_info(self, sdk_client: RunloopSDK) -> None:
- """Test retrieving scorer information."""
- scorer_type = unique_name("sdk-scorer-info")
- scorer = sdk_client.scorer.create(
- type=scorer_type,
- bash_script="echo 'score=0.5'",
- )
-
- info = scorer.get_info()
-
- assert info.id == scorer.id
- assert info.type == scorer_type
-
- @pytest.mark.timeout(ONE_MINUTE_TIMEOUT)
- def test_scorer_update(self, sdk_client: RunloopSDK) -> None:
- """Test updating a scorer."""
- scorer_type = unique_name("sdk-scorer-update")
- scorer = sdk_client.scorer.create(
- type=scorer_type,
- bash_script="echo 'score=0.0'",
- )
-
- updated_type = unique_name("sdk-scorer-updated")
- result = scorer.update(
- type=updated_type,
- bash_script="echo 'score=1.0'",
- )
-
- assert result is not None
-
- # Verify the update
- info = scorer.get_info()
- assert info.type == updated_type
- assert info.bash_script == "echo 'score=1.0'"
-
-
-class TestScorerListing:
- """Test scorer listing and retrieval operations."""
-
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- def test_list_scorers(self, sdk_client: RunloopSDK) -> None:
- """Test listing scorers."""
- scorers = sdk_client.scorer.list(limit=10)
-
- assert isinstance(scorers, list)
- # List might be empty, that's okay
- assert len(scorers) >= 0
-
- @pytest.mark.timeout(ONE_MINUTE_TIMEOUT)
- def test_get_scorer_by_id(self, sdk_client: RunloopSDK) -> None:
- """Test retrieving scorer by ID."""
- # Create a scorer
- scorer_type = unique_name("sdk-scorer-retrieve")
- created = sdk_client.scorer.create(
- type=scorer_type,
- bash_script="echo 'score=1.0'",
- )
-
- # Retrieve it by ID
- retrieved = sdk_client.scorer.from_id(created.id)
- assert retrieved.id == created.id
-
- # Verify it's the same scorer
- info = retrieved.get_info()
- assert info.id == created.id
- assert info.type == scorer_type
diff --git a/tests/smoketests/sdk/test_sdk.py b/tests/smoketests/sdk/test_sdk.py
index f79b88d43..8d8b70bfa 100644
--- a/tests/smoketests/sdk/test_sdk.py
+++ b/tests/smoketests/sdk/test_sdk.py
@@ -22,9 +22,6 @@ def test_sdk_instance_creation(self, sdk_client: RunloopSDK) -> None:
assert sdk_client.blueprint is not None
assert sdk_client.snapshot is not None
assert sdk_client.storage_object is not None
- assert sdk_client.scorer is not None
- assert sdk_client.agent is not None
- assert sdk_client.scenario is not None
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
def test_legacy_api_access(self, sdk_client: RunloopSDK) -> None:
diff --git a/tests/smoketests/sdk/test_secret.py b/tests/smoketests/sdk/test_secret.py
index 33f460090..93c895f67 100644
--- a/tests/smoketests/sdk/test_secret.py
+++ b/tests/smoketests/sdk/test_secret.py
@@ -4,6 +4,7 @@
import pytest
+from runloop_api_client import NotFoundError
from runloop_api_client.sdk import RunloopSDK
from tests.smoketests.utils import unique_name
@@ -16,66 +17,61 @@
class TestSecretLifecycle:
"""Test secret lifecycle operations: create, get_info, update, list, delete."""
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
+ @pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
def test_secret_full_lifecycle(self, sdk_client: RunloopSDK) -> None:
- """Test complete secret lifecycle: create, get_info, update (both ways), list, from_name, delete."""
- secret_name = unique_name("SDK_TEST_SECRET").upper().replace("-", "_")
-
- # Create
- secret = sdk_client.secret.create(name=secret_name, value="initial-value")
- assert secret is not None
- assert secret.name == secret_name
-
+ """Exercise name and ID operations in one lifecycle, including name reuse."""
+ name = "sec_" + unique_name("SDK_TEST_SECRET").replace("-", "_")
+ secret = sdk_client.secret.create(name=name, value="synthetic-initial")
+ assert secret.name == name
+ assert secret.id is not None and secret.id.startswith("sec_")
+ ids = [secret.id]
try:
- # get_info uses GET /v1/secrets/{name}
- info = secret.get_info()
- assert info.id.startswith("sec_")
- assert info.name == secret_name
+ named = sdk_client.secret.from_name(name)
+ assert named.id is None
+ info = named.get_info()
+ assert info.id == secret.id
assert info.create_time_ms > 0
- create_time_ms = info.create_time_ms
-
- # Update via SecretOps
- updated = sdk_client.secret.update(secret, "updated-via-ops")
- assert updated.name == secret_name
- updated_info = updated.get_info()
- assert updated_info.update_time_ms >= create_time_ms
-
- # Update via instance method
- instance_updated_info = secret.update("updated-via-instance")
- assert instance_updated_info.name == secret_name
-
- # List
- secrets = sdk_client.secret.list()
- assert isinstance(secrets, list)
- assert len(secrets) > 0
- found = next((s for s in secrets if s.name == secret_name), None)
- assert found is not None
- assert found.name == secret_name
-
- # from_name (no API call)
- by_name = sdk_client.secret.from_name(secret_name)
- assert by_name.name == secret_name
- by_name_info = by_name.get_info()
- assert by_name_info.id == info.id
-
+ updated = sdk_client.secret.update(secret, "synthetic-ops")
+ assert updated.name == name
+ assert updated.get_info().update_time_ms >= info.create_time_ms
+ assert secret.update("synthetic-name").name == name
+ assert any(item.id == secret.id for item in sdk_client.secret.list())
+
+ exact = sdk_client.secret.from_id(secret.id)
+ assert exact.get_info().name == name
+ assert exact.update("synthetic-id").id == exact.id
+ assert exact.delete().id == exact.id
+ replacement = sdk_client.secret.create(name=name, value="synthetic-replacement")
+ assert replacement.id is not None
+ ids.append(replacement.id)
+ assert replacement.id != exact.id
+ # The original wrapper still follows its name, not its captured ID.
+ assert secret.get_info().id == replacement.id
+ assert secret.id == exact.id
+ with pytest.raises(NotFoundError):
+ exact.get_info()
+ with pytest.raises(NotFoundError):
+ exact.update("must-not-update-replacement")
+ with pytest.raises(NotFoundError):
+ exact.delete()
+ assert secret.delete().id == replacement.id
+ assert all(item.name != name for item in sdk_client.secret.list())
finally:
- # Delete
- deleted = secret.delete()
- assert deleted is not None
- assert deleted.name == secret_name
-
- # Verify deleted
- remaining = sdk_client.secret.list()
- assert all(s.name != secret_name for s in remaining)
+ for id in ids:
+ try:
+ sdk_client.secret.from_id(id).delete()
+ except NotFoundError:
+ pass
+ @pytest.mark.parametrize("by_name", [False, True])
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- def test_secret_delete_via_ops(self, sdk_client: RunloopSDK) -> None:
+ def test_secret_delete_via_ops(self, sdk_client: RunloopSDK, by_name: bool) -> None:
"""Test deleting a secret via SecretOps.delete()."""
secret_name = unique_name("SDK_TEST_DEL_SECRET").upper().replace("-", "_")
secret = sdk_client.secret.create(name=secret_name, value="to-be-deleted")
try:
- deleted = sdk_client.secret.delete(secret)
+ deleted = sdk_client.secret.delete(secret.name if by_name else secret)
assert deleted.name == secret_name
remaining = sdk_client.secret.list()
@@ -88,22 +84,6 @@ def test_secret_delete_via_ops(self, sdk_client: RunloopSDK) -> None:
pass
raise
- @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
- def test_secret_delete_by_name_string(self, sdk_client: RunloopSDK) -> None:
- """Test deleting a secret by passing a name string to SecretOps.delete()."""
- secret_name = unique_name("SDK_TEST_STR_SECRET").upper().replace("-", "_")
- sdk_client.secret.create(name=secret_name, value="to-be-deleted-by-name")
-
- try:
- deleted = sdk_client.secret.delete(secret_name)
- assert deleted.name == secret_name
- except Exception:
- try:
- sdk_client.secret.delete(secret_name)
- except Exception:
- pass
- raise
-
class TestSecretWithDevbox:
"""Test secret injection into devboxes."""
diff --git a/tests/smoketests/test_scenarios_benchmarks.py b/tests/smoketests/test_scenarios_benchmarks.py
deleted file mode 100644
index 422d53dfb..000000000
--- a/tests/smoketests/test_scenarios_benchmarks.py
+++ /dev/null
@@ -1,82 +0,0 @@
-from __future__ import annotations
-
-from typing import Iterator
-
-import pytest
-
-from runloop_api_client import Runloop
-from runloop_api_client.lib.polling import PollingConfig
-
-from .utils import unique_name
-
-pytestmark = [pytest.mark.smoketest]
-
-
-@pytest.fixture(autouse=True, scope="module")
-def _cleanup(client: Runloop) -> Iterator[None]: # pyright: ignore[reportUnusedFunction]
- yield
- global _devbox_id
- if _devbox_id:
- try:
- client.devboxes.shutdown(_devbox_id)
- except Exception:
- pass
-
-
-"""
-Tests are run sequentially and can be dependent on each other.
-This is to avoid overloading resources and save efficiency.
-"""
-_scenario_id = None
-_run_id = None
-_devbox_id = None
-
-
-@pytest.mark.timeout(30)
-def test_create_scenario(client: Runloop) -> None:
- global _scenario_id
- scenario = client.scenarios.create(
- name=unique_name("scenario"),
- input_context={"problem_statement": "echo hello"},
- scoring_contract={
- "scoring_function_parameters": [
- {
- "name": "cmd-zero",
- "scorer": {"type": "command_scorer", "command": "true"},
- "weight": 1,
- }
- ]
- },
- )
- _scenario_id = scenario.id
-
-
-@pytest.mark.timeout(30)
-def test_start_scenario_run_and_await_env_ready(client: Runloop) -> None:
- assert _scenario_id
- run = client.scenarios.start_run_and_await_env_ready(
- scenario_id=_scenario_id,
- polling_config=PollingConfig(max_attempts=120, interval_seconds=5.0, timeout_seconds=20 * 60),
- )
- assert run.scenario_id == _scenario_id
- global _run_id, _devbox_id
- _run_id = run.id
- _devbox_id = run.devbox_id
-
-
-@pytest.mark.timeout(30)
-def test_score_and_complete_scenario_run(client: Runloop) -> None:
- assert _run_id
- scored = client.scenarios.runs.score_and_complete(
- _run_id, polling_config=PollingConfig(max_attempts=120, interval_seconds=5.0, timeout_seconds=20 * 60)
- )
- assert scored.state in {"completed", "scored", "running", "failed", "timeout", "canceled"}
-
-
-@pytest.mark.timeout(30)
-def test_create_benchmark_and_start_run(client: Runloop) -> None:
- assert _scenario_id
- benchmark = client.benchmarks.create(name=unique_name("benchmark"), scenario_ids=[_scenario_id])
- assert benchmark.id
- run = client.benchmarks.start_run(benchmark_id=benchmark.id)
- assert run.benchmark_id == benchmark.id
diff --git a/tests/test_command_id.py b/tests/test_command_id.py
index 92089d28a..e5d0b7afe 100644
--- a/tests/test_command_id.py
+++ b/tests/test_command_id.py
@@ -65,6 +65,20 @@ def test_execute_preserves_explicit_command_id(self, respx_mock: MockRouter) ->
body = _get_request_body(route)
assert body["command_id"] == "my-custom-id"
+ @pytest.mark.respx(base_url=base_url)
+ def test_execute_and_await_completion(self, respx_mock: MockRouter) -> None:
+ route = respx_mock.post(EXECUTE_PATH).mock(return_value=httpx.Response(200, json=STUB_RESPONSE))
+ with Runloop(base_url=base_url, bearer_token="test") as client:
+ result = client.devboxes.execute_and_await_completion(
+ "dbx_test", command="echo hi", command_id="my-custom-id"
+ )
+
+ assert result.status == "completed"
+ body = _get_request_body(route)
+ assert body["command"] == "echo hi"
+ assert body["command_id"] == "my-custom-id"
+ assert "shell_name" not in body
+
class TestAsyncCommandIdGeneration:
"""Async variant: command_id must be a fresh UUIDv7 per call when not explicitly provided."""
@@ -92,3 +106,18 @@ async def test_execute_preserves_explicit_command_id(self, respx_mock: MockRoute
body = _get_request_body(route)
assert body["command_id"] == "my-custom-id"
+
+ @pytest.mark.respx(base_url=base_url)
+ @pytest.mark.asyncio
+ async def test_execute_and_await_completion(self, respx_mock: MockRouter) -> None:
+ route = respx_mock.post(EXECUTE_PATH).mock(return_value=httpx.Response(200, json=STUB_RESPONSE))
+ async with AsyncRunloop(base_url=base_url, bearer_token="test") as client:
+ result = await client.devboxes.execute_and_await_completion(
+ "dbx_test", command="echo hi", command_id="my-custom-id"
+ )
+
+ assert result.status == "completed"
+ body = _get_request_body(route)
+ assert body["command"] == "echo hi"
+ assert body["command_id"] == "my-custom-id"
+ assert "shell_name" not in body
diff --git a/uv.lock b/uv.lock
index fb84d9a80..f6f208a53 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2422,7 +2422,7 @@ wheels = [
[[package]]
name = "runloop-api-client"
-version = "1.30.0"
+version = "3.0.0"
source = { editable = "." }
dependencies = [
{ name = "anyio" },