diff --git a/.github/workflows/api-acceptance.yml b/.github/workflows/api-acceptance.yml index d4437a4c9..ee010ed18 100644 --- a/.github/workflows/api-acceptance.yml +++ b/.github/workflows/api-acceptance.yml @@ -62,6 +62,7 @@ jobs: OAC_TEST_SERVER_BIN: ${{ runner.temp }}/oac-core-build/oac-core OAC_TEST_OFFICIAL_SDK_PYTHON: python run: | + python services/core/tests/official_schema_test.py python services/core/tests/official_client.py go test ./services/core/tests/integration -run '^(TestFunctionStateOfficialClientReadsAndLiveEvents|TestSavedReferenceRetryOfficialClient|TestAgentUpdateOfficialClient|TestAgentDeletionOfficialClient|TestSessionAgentFilterOfficialClient|TestSessionDeletionOfficialClient|TestEnvironmentInitialFailureOfficialClient|TestSelfHostedInitialCreationOfficialClient|TestSelfHostedCancellationOfficialClient)$' -count=1 - uses: ./.github/actions/e2b-provider diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 151a0c410..0a307c7ad 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -184,8 +184,18 @@ jobs: compose: needs: plan if: needs.plan.result == 'success' && contains(fromJSON(needs.plan.outputs.jobs || '[]'), 'compose') - runs-on: ${{ vars.OAC_USE_GITHUB_RUNNERS == 'true' && 'ubuntu-22.04' || 'blacksmith-2vcpu-ubuntu-2204' }} - timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - runner: ubuntu-24.04 + architecture: amd64 + - runner: ubuntu-24.04-arm + architecture: arm64 + runs-on: ${{ matrix.runner }} + env: + GOARCH: ${{ matrix.architecture }} + timeout-minutes: 30 steps: - uses: actions/checkout@v7 with: @@ -193,6 +203,11 @@ jobs: - uses: actions/setup-go@v7 with: go-version-file: go.mod + - name: Test the native Core installation lifecycle + run: go test ./services/core/cmd/oac -count=1 -timeout=3m + - uses: ./.github/actions/e2b-provider + - name: Build and verify the architecture-matched E2B helper + run: bash scripts/build-e2b-provider.sh - name: Allocate an isolated Compose project run: python3 -c 'import uuid; print("COMPOSE_SMOKE_PROJECT=oac-smoke-" + uuid.uuid4().hex)' >> "$GITHUB_ENV" - name: Build the images, start the installation and verify it diff --git a/.github/workflows/ci-review.yml b/.github/workflows/ci-review.yml index 0cd33e664..2bc1069bd 100644 --- a/.github/workflows/ci-review.yml +++ b/.github/workflows/ci-review.yml @@ -16,12 +16,28 @@ permissions: jobs: review: + name: ${{ matrix.name }} + strategy: + fail-fast: false + matrix: + include: + - kind: code + name: Code review + instructions: | + 检查本次代码改动的正确性、仓库设计规则及已有 CI 结果。先读取 AGENTS.md 和 CONTRIBUTING.md,再查看相关实现和测试。 + 用 gh pr checks 查询这个 PR 已有的检查结果,必要时读取 Actions 日志。检查失败、缺失或尚未完成时如实报告,不把合并当作检查通过的证据。 + 报告包括实际行为变化(1–3 条)、代码审查结论和 CI 状态。全部正常时保持简短;有问题时给出文件与行号、证据和最小修改建议。 + - kind: docs + name: Docs review + instructions: | + 每次都检查文档与本次代码变化是否一致,即使 PR 没改文档。读取相关实现、测试和完整文档,核对平台、命令、配置、路径、默认值、接口和操作流程,找出遗漏更新或已经过时的说明。 + 文档有修改(包括新增、删除和重命名)时,再检查文档内部及同主题文档间的矛盾、重复维护的事实、主题归属和中英文含义。按 AGENTS.md 和 CONTRIBUTING.md 的 Documentation ownership 表判断位置。必要的概述加链接、中英文对照和同一来源生成的文档不算重复。 + 分别报告“文档与代码一致性”和“文档矛盾、重复与归属”。未改文档时,第二项写“本次未修改文档”。只报告本次修改引入或使之过时的、有依据的问题,给出文件与行号、对应代码或文档依据,以及最小修改建议。测试或链接检查通过不代表语义一致。只报告影响读者理解或操作的具体问题,不把未穷举实现细节当作文档缺陷。 if: github.event.pull_request.merged == true runs-on: ${{ vars.OAC_USE_GITHUB_RUNNERS == 'true' && 'ubuntu-24.04' || 'blacksmith-2vcpu-ubuntu-2404' }} timeout-minutes: 30 env: - # allowed_non_write_users turns on a subprocess secret scrub; opt out so Claude's - # commands can read GH_TOKEN and the Feishu webhook. + # Keep GH_TOKEN available to read the PR and Actions logs. CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: '0' steps: # The triggering revision is already on main, so its rules and code are trusted. @@ -30,14 +46,15 @@ jobs: ref: ${{ github.event.pull_request.merge_commit_sha }} fetch-depth: 2 persist-credentials: false - - name: Review and send Feishu card with Claude Code - # Notification is best effort: a failed review or delivery never fails the job. + - name: Prepare the report schema + id: schema + run: python3 scripts/ci_review.py schema "${{ matrix.kind }}" >> "$GITHUB_OUTPUT" + - name: Review with Claude Code + id: llm continue-on-error: true timeout-minutes: 25 uses: anthropics/claude-code-action@12dd8d74c712f5f3669365b2369b558c495b1104 # v1 env: - FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} - FEISHU_WEBHOOK_SECRET: ${{ secrets.FEISHU_WEBHOOK_SECRET }} ANTHROPIC_BASE_URL: https://api.minimax.cn/anthropic ANTHROPIC_AUTH_TOKEN: ${{ secrets.MINIMAX_API_KEY }} CLAUDE_CODE_AUTO_COMPACT_WINDOW: '524288' @@ -54,29 +71,57 @@ jobs: allowed_bots: '*' allowed_non_write_users: '*' prompt: | - 你是 CI 的负责人,负责审核 CI 结果并给出结论。 - 仓库 ${{ github.repository }} 的 PR #${{ github.event.pull_request.number }} 已合入 main: - - PR:https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }} - - 合并 commit:${{ github.event.pull_request.merge_commit_sha }}(已 checkout 到当前目录) - - 用 gh pr checks ${{ github.event.pull_request.number }} --repo ${{ github.repository }} 查询这个 PR 已有的检查结果,必要时读取对应 Actions 日志。不要触发新的 CI,也不要把合并本身当作检查通过的证据;管理员可能绕过门禁。检查失败、缺失或尚未完成时如实报告。 + 你负责本次 PR 的 ${{ matrix.name }},在独立上下文中完成审查。 + 仓库:${{ github.repository }};PR:#${{ github.event.pull_request.number }}。 + 合并提交:${{ github.event.pull_request.merge_commit_sha }},已检出到当前目录。 + 先用 gh pr diff ${{ github.event.pull_request.number }} --repo ${{ github.repository }} 读取变更清单与完整差异,再检查相关文件。 - 任务:给飞书群发一张中文卡片(Card 2.0)总结这次 CI,尽量在 10 轮以内给出结论,工具调用尽可能的并行。 - 环境里有 gh(已登录,GH_TOKEN)、git、node 和完整的仓库代码。 + ${{ matrix.instructions }} - 卡片要让手机上的读者快速看懂,如果一切正常,表达的尽可能简单,如果没有问题,尽量简洁: - 1. 哪个 PR(github id + PR 标题 + 链接) - - 如:`github id: jing332, PR 标题: 添加一个新功能, 链接: https://github.com/org/repo/pull/123` - 2. 改了什么(实际行为变化,1–3 条) - - 如:`添加了一个新功能、修改了 web 和 backend 的 CI` - 3. 是否符合仓库规则(AGENTS.md、CONTRIBUTING.md 及相关文档):未发现明确违规 / 发现需修复问题 / 信息不足,无法确认 - - 如:`符合仓库规则` - 4. CI 哪里失败、可能的原因(可以看日志),全部通过就直接说通过,不要给出细节。如果 CI 失败,则详细说明。 - - 如:`CI 失败,具体是哪几个失败;全部通过 ✅` - - 发送:webhook 地址在环境变量 FEISHU_WEBHOOK_URL。 - 响应 code=0 才算成功,失败就排查并重试,直到发出去。最后用一句中文说明结果,如:`CI 结果已发送至飞书群`。 - 不要在输出里打印 webhook 地址和密钥。 + 按 JSON schema 返回中文报告。status 为 ok(未发现问题)、issues(发现有证据的问题)或 incomplete(审查失败、范围未检查完整或检查结果尚未完成)。有问题且仍有未检查项时,用 issues 并在对应字段中说明缺项。按 schema 把各项结论分别填入字段,不要重复标题;changes 用 1–3 条 Markdown 列表,其他字段无问题时一句话,有问题时保留依据和建议,遵守各字段长度限制。 + 围绕本次 diff 和受影响的文档展开,证据充分后输出结果,避免重复核对同一结论。 + 只读审查,不修改仓库,不触发新的 CI,不发送消息。两份报告会由后续 job 合并发送。 claude_args: >- - --allowedTools Bash Read Write Edit Glob Grep WebFetch WebSearch - --max-turns 30 + --allowedTools Bash Read Glob Grep + --json-schema '${{ steps.schema.outputs.schema }}' + - name: Save the review result + if: always() + env: + REVIEW_OUTCOME: ${{ steps.llm.outcome }} + REVIEW_RESULT: ${{ steps.llm.outputs.structured_output }} + run: python3 scripts/ci_review.py collect "${{ matrix.kind }}" "$RUNNER_TEMP/review-${{ matrix.kind }}.json" + - uses: actions/upload-artifact@v6 + if: always() + with: + name: ci-review-${{ matrix.kind }} + path: ${{ runner.temp }}/review-${{ matrix.kind }}.json + if-no-files-found: error + overwrite: true + retention-days: 7 + + notify: + name: Combined Feishu notification + needs: review + if: always() && github.event.pull_request.merged == true + runs-on: ${{ vars.OAC_USE_GITHUB_RUNNERS == 'true' && 'ubuntu-24.04' || 'blacksmith-2vcpu-ubuntu-2404' }} + timeout-minutes: 5 + permissions: + contents: read + actions: read + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.merge_commit_sha }} + persist-credentials: false + - uses: actions/download-artifact@v6 + continue-on-error: true + with: + pattern: ci-review-* + merge-multiple: true + path: ${{ runner.temp }}/reviews + - name: Send one card with both results + continue-on-error: true + env: + FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} + FEISHU_WEBHOOK_SECRET: ${{ secrets.FEISHU_WEBHOOK_SECRET }} + run: python3 scripts/ci_review.py notify "$RUNNER_TEMP/reviews" diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml index 7be750d35..c901adc6f 100644 --- a/.github/workflows/native.yml +++ b/.github/workflows/native.yml @@ -63,6 +63,14 @@ jobs: run: | go build -ldflags "-X github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/cli.Version=$(git rev-parse HEAD)" -o "$RUNNER_TEMP/oac-daemon${{ runner.os == 'Windows' && '.exe' || '' }}" ./apps/daemon/cmd/oac-daemon node --test scripts/build-native-installer.test.mjs + - name: Build and test the shared Core installer + run: | + go build -o "$RUNNER_TEMP/oac${{ runner.os == 'Windows' && '.exe' || '' }}" ./services/core/cmd/oac + go test ./services/core/cmd/oac -count=1 -timeout=3m + - name: Exercise the Windows Core launcher + if: runner.os == 'Windows' + shell: pwsh + run: ./deploy/test_install.ps1 -Binary "$env:RUNNER_TEMP/oac.exe" - name: Verify native download bootstrap and recovery run: go test ./services/core/internal/nativeinstaller -count=1 -timeout=3m - name: Native filesystem, authentication and process lifecycle @@ -163,3 +171,17 @@ jobs: path: ${{ runner.temp }}/native-ci-diagnostics/ retention-days: 7 if-no-files-found: ignore + + core-intel-mac: + name: Core installer (macOS Intel) + runs-on: macos-15-intel + timeout-minutes: 10 + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref || github.sha }} + - uses: actions/setup-go@v7 + with: + go-version-file: go.mod + - run: go test ./services/core/cmd/oac -count=1 -timeout=3m + - run: go build -o "$RUNNER_TEMP/oac" ./services/core/cmd/oac diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d798c1976..c8ff4ab28 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -114,6 +114,9 @@ jobs: path: ${{ runner.temp }}/native-artifacts - name: Assemble the native installation catalog run: node scripts/build-native-catalog.mjs "$RUNNER_TEMP/native-artifacts" "$RUNNER_TEMP/native-installers" + - uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 - uses: ./.github/actions/e2b-provider - name: Build matched artifacts env: @@ -133,8 +136,8 @@ jobs: for asset in "$HOME/.oac/build/core-distribution/"*; do if [[ -f "$asset" ]]; then ln "$asset" "$HOME/.oac/build/release-upload/"; fi done - cp deploy/install.sh "$HOME/.oac/build/release-upload/install.sh" - (cd "$HOME/.oac/build/release-upload" && sha256sum install.sh > install.sh.sha256) + cp deploy/install.sh deploy/install.ps1 "$HOME/.oac/build/release-upload/" + (cd "$HOME/.oac/build/release-upload" && sha256sum install.sh > install.sh.sha256 && sha256sum install.ps1 > install.ps1.sha256) - name: Sign in to GHCR if: github.event_name == 'push' || inputs.draft_release env: diff --git a/AGENTS.md b/AGENTS.md index 93d98a834..f292b1bb1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,7 @@ Do not multiply entities without necessity. The long-term goal is minimal code, | Boundary | Protocol code | Protocol doc | | --- | --- | --- | -| Application–Core (`/v1`) | Types in `contracts/agents-api/v1/` and route annotations in `services/core/internal/api/`; `make openapi` generates `contracts/agents-api/openapi.yaml` | [Agents API guide](docs/api/public-agent-api.md) | +| Application–Core (`/v1`) | Official schema pinned by `contracts/agents-api/upstream.json` plus Go-owned `x_agents_core` extensions; `make openapi` generates public Go types and `contracts/agents-api/openapi.yaml` | [Agents API guide](docs/api/public-agent-api.md) | | Web and operators–Core (`/core/v1`) | Route annotations in `services/core/internal/api/`; `make openapi` generates `contracts/agents-api/core.openapi.yaml` | [Core administration API](contracts/agents-api/admin-api.md) | | Nodes and daemons–Core (`/api/v1` HTTP routes; the node and daemon wire protocols are separate rows) | Route annotations in `services/core/internal/api/`; `make openapi` generates `contracts/agents-api/runtime.openapi.yaml` | [Machine connection API](contracts/agents-api/machine-api.md) | | Core–Sandbox Provider | `services/core/internal/sandbox/sandbox_provider.go` | [Sandbox Provider guide](docs/sandbox-provider.md) | diff --git a/Makefile b/Makefile index f755dec39..a8825f1ee 100644 --- a/Makefile +++ b/Makefile @@ -32,21 +32,28 @@ sqlc-generate: SWAG ?= go run github.com/swaggo/swag/cmd/swag@$(SWAG_VERSION) -.PHONY: openapi +.PHONY: openapi check-openapi +OPENAPI_FLAGS ?= +check-openapi: + $(MAKE) openapi OPENAPI_FLAGS=--check + python3 scripts/generate-public-api.test.py + openapi: @set -e; root="$${OAC_DEV_HOME:-$$HOME/.oac}/build"; mkdir -p "$$root"; \ output=$$(mktemp -d "$$root/core-openapi.XXXXXX"); trap 'rm -rf "$$output"' EXIT; \ + python3 scripts/generate-public-api.py $(OPENAPI_FLAGS) --swag-roots "$$output/roots.go"; \ $(SWAG) init \ - -g cmd/server/main.go --dir ./services/core,./contracts/agents-api/v1 \ + -g cmd/server/main.go --dir "./services/core,./contracts/agents-api/v1,$$output" \ --output "$$output" \ --outputTypes yaml --parseInternal; \ python3 scripts/patch-agents-openapi.py "$$output/swagger.yaml"; \ - go run ./scripts/openapi-split "$$output/swagger.yaml" contracts/agents-api/openapi.yaml contracts/agents-api/core.openapi.yaml contracts/agents-api/runtime.openapi.yaml + go run ./scripts/openapi-split $(OPENAPI_FLAGS) "$$output/swagger.yaml" "$$output/extensions.json" contracts/agents-api/core.openapi.yaml contracts/agents-api/runtime.openapi.yaml; \ + python3 scripts/generate-public-api.py $(OPENAPI_FLAGS) --extensions "$$output/extensions.json" check-sqlc: python3 scripts/check-sqlc.py -check-go: +check-go: check-openapi go test ./apps/daemon/... ./apps/sandboxio/... ./internal/... ./contracts/agents-api/... ./scripts/openapi-split -count=1 .PHONY: check-runtime-contract @@ -183,7 +190,7 @@ check-microsandbox-provider: .PHONY: check-distribution build-core-distribution check-distribution: node --test scripts/build-native-catalog.test.mjs - go test ./services/web -count=1 + go test ./services/web ./services/core/cmd/oac -count=1 PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s deploy/node -p 'test_*.py' PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s deploy/compose -p 'test_*.py' PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s scripts/acceptance -p 'test_*.py' diff --git a/README.md b/README.md index 5713ac292..d8056f965 100644 --- a/README.md +++ b/README.md @@ -42,12 +42,18 @@ Core keeps durable execution state. The Runtime runs the chosen harness inside t ## Install -On a Linux amd64 host with Docker and Python 3.9+: +On Linux or macOS with [Docker configured](https://openagentcore.dev/docs/getting-started/install#prerequisites): ```sh curl -fsSL https://github.com/MiniMax-AI/OpenAgentCore/releases/latest/download/install.sh | bash ``` +Windows PowerShell: + +```powershell +irm https://github.com/MiniMax-AI/OpenAgentCore/releases/latest/download/install.ps1 | iex +``` + Then: 1. **Sign in to Web**, the admin console, with the Core key the installer created, and **configure the domain and HTTPS**. diff --git a/README.zh-CN.md b/README.zh-CN.md index 5414cea3c..fb55a62a5 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -42,12 +42,18 @@ OpenAgentCore 在你自己的基础设施上运行 AI Agent,对外提供 [Open ## 安装 -在已准备 Docker 和 Python 3.9+ 的 Linux amd64 主机上: +在已按[前置条件](https://openagentcore.dev/zh/docs/getting-started/install#prerequisites)准备 Docker 的 Linux 或 macOS 上: ```sh curl -fsSL https://github.com/MiniMax-AI/OpenAgentCore/releases/latest/download/install.sh | bash ``` +Windows PowerShell: + +```powershell +irm https://github.com/MiniMax-AI/OpenAgentCore/releases/latest/download/install.ps1 | iex +``` + 然后: 1. 用安装器生成的 Core key **登录 Web**(管理控制台),并**配置域名和 HTTPS**。 diff --git a/apps/daemon/internal/agent/claudesdk/cancellation_live_linux_test.go b/apps/daemon/internal/agent/claudesdk/cancellation_live_linux_test.go index 86f7e0cf0..f64e333b0 100644 --- a/apps/daemon/internal/agent/claudesdk/cancellation_live_linux_test.go +++ b/apps/daemon/internal/agent/claudesdk/cancellation_live_linux_test.go @@ -67,7 +67,7 @@ func TestLiveClaudeSDKCancelResume(t *testing.T) { defer cancel() out := make(chan proto.Envelope, 64) request := proto.PromptRequestPayload{RunID: uuid.NewString(), Input: proto.TextInput(prompt), AgentSessionID: resume, ObserveMessages: true, DisableExecutionEnvironment: true, DisableSubagents: true, ExecutionControls: &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "medium"}, Model: "MiniMax-M3", ModelProvider: provider, SystemPrompt: "Follow the user's requested format. Preserve the exact verification value in conversation history. Use no tools."} - running, err := NewFactory(config)(ctx, request, out) + running, err := startSingleTurn(ctx, config, request, out) if err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/agent/claudesdk/cancellation_test.go b/apps/daemon/internal/agent/claudesdk/cancellation_test.go index 8672981b1..5b2c991e7 100644 --- a/apps/daemon/internal/agent/claudesdk/cancellation_test.go +++ b/apps/daemon/internal/agent/claudesdk/cancellation_test.go @@ -27,7 +27,7 @@ func TestCancellationWaitsForDrainAndPublishesOutcome(t *testing.T) { defer cancel() // A stopped consumer must not prevent native output draining or cancellation. out := make(chan proto.Envelope) - running, err := NewFactory(config)(ctx, cancellationRequest(), out) + running, err := startSingleTurn(ctx, config, cancellationRequest(), out) if err != nil { t.Fatal(err) } @@ -89,7 +89,7 @@ func TestFailureKeepsOnlyVerifiedNativeIdentity(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() out := make(chan proto.Envelope, 8) - running, err := NewFactory(cancellationConfig(root, mode))(ctx, cancellationRequest(), out) + running, err := startSingleTurn(ctx, cancellationConfig(root, mode), cancellationRequest(), out) if err != nil { t.Fatal(err) } @@ -125,7 +125,7 @@ func TestCancellationDrainsIntoReadyConsumer(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() out := make(chan proto.Envelope, 16) - running, err := NewFactory(config)(ctx, cancellationRequest(), out) + running, err := startSingleTurn(ctx, config, cancellationRequest(), out) if err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/agent/claudesdk/commands_session_test.go b/apps/daemon/internal/agent/claudesdk/commands_session_test.go index 8ed414dc4..b19a7584e 100644 --- a/apps/daemon/internal/agent/claudesdk/commands_session_test.go +++ b/apps/daemon/internal/agent/claudesdk/commands_session_test.go @@ -77,7 +77,7 @@ func TestWorkspaceCommandCancellationAndBridgeFailuresCloseOnlyPendingCalls(t *t req := workspaceRequest() req.AgentSessionID = "native-session" out := make(chan proto.Envelope, 32) - s, err := NewFactory(config)(ctx, req, out) + s, err := startSingleTurn(ctx, config, req, out) if err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/agent/claudesdk/contracts.go b/apps/daemon/internal/agent/claudesdk/contracts.go index c35ebb6a8..aaf9e92aa 100644 --- a/apps/daemon/internal/agent/claudesdk/contracts.go +++ b/apps/daemon/internal/agent/claudesdk/contracts.go @@ -11,8 +11,6 @@ var ( _ agent.DurableSteerer = (*session)(nil) _ agent.Steerer = (*session)(nil) _ agent.FunctionResultSubmitter = (*session)(nil) - _ agent.PermissionResponder = (*session)(nil) - _ agent.UserChoiceResponder = (*session)(nil) _ agent.WorkspaceDirectoryLister = (*session)(nil) _ agent.WorkspaceWriter = (*session)(nil) _ agent.WorkspaceDirectoryLister = (*executor)(nil) diff --git a/apps/daemon/internal/agent/claudesdk/declaration.go b/apps/daemon/internal/agent/claudesdk/declaration.go index 840861b26..c17df1372 100644 --- a/apps/daemon/internal/agent/claudesdk/declaration.go +++ b/apps/daemon/internal/agent/claudesdk/declaration.go @@ -22,7 +22,6 @@ const claudeSDKNodeEnv = "OAC_RUNTIME_CLAUDE_SDK_NODE" var Declaration = agent.Declaration{Info: proto.SupportedAgentKind{Kind: "claude_sdk", Capabilities: proto.AgentKindCapabilities{ SubagentObservations: proto.CapabilityUnsupported, Streaming: proto.CapabilitySupported, - Permissions: proto.CapabilityUnsupported, Usage: proto.CapabilitySupported, Resume: proto.CapabilitySupported, NativeSessionRecovery: proto.CapabilityUnsupported, @@ -59,9 +58,7 @@ func discoverWithCheck(parent context.Context, options agent.DiscoveryOptions, d if entrypoint == "" { return nil } - out := &agent.Runtime{Info: descriptor, Session: func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, fmt.Errorf("claude_sdk: configured runtime is unavailable") - }} + out := &agent.Runtime{Info: descriptor} var config Config fail := func(err error) *agent.Runtime { @@ -119,8 +116,8 @@ func discoverWithCheck(parent context.Context, options agent.DiscoveryOptions, d } caps := &out.Info.Capabilities caps.EnvironmentNone, caps.FunctionTools = proto.CapabilityUnsupported, proto.CapabilityFromBool(info.SupportsWorkspaceFunctions()) - caps.Preparation, caps.LocalEnvironment = proto.CapabilitySupported, proto.CapabilitySupported - caps.WorkspaceReadPreparation, caps.NativeSessionRecovery = proto.CapabilitySupported, proto.CapabilitySupported + caps.LocalEnvironment, caps.WorkspaceReadPreparation = proto.CapabilitySupported, proto.CapabilitySupported + caps.NativeSessionRecovery = proto.CapabilitySupported } out.Info.Available, out.Info.Version = true, info.SDK out.Info.Capabilities.MessageImages = proto.CapabilityFromBool(info.SupportsMessageImages()) @@ -141,7 +138,6 @@ func discoverWithCheck(parent context.Context, options agent.DiscoveryOptions, d out.Info.Capabilities.MCPHTTPTools, out.Info.Capabilities.MCPHTTPBearerAuth = proto.CapabilityUnsupported, proto.CapabilityUnsupported out.Info.Capabilities.MCPHTTPRequired = proto.CapabilityUnsupported } - out.Session = NewFactory(config) out.Executor = NewExecutorFactory(config) // The view runs the same install; its probe stays on this host. if view, err := newView(Config{Node: node, Entrypoint: entrypoint}, info); err != nil { diff --git a/apps/daemon/internal/agent/claudesdk/declaration_test.go b/apps/daemon/internal/agent/claudesdk/declaration_test.go index e1a3691e3..9181b5454 100644 --- a/apps/daemon/internal/agent/claudesdk/declaration_test.go +++ b/apps/daemon/internal/agent/claudesdk/declaration_test.go @@ -125,12 +125,6 @@ func TestRuntimeDiscoveryConfigurationAndRegistration(t *testing.T) { if info.Capabilities.Preparation.IsSupported() != ready { t.Fatal(info) } - if !ready { - factory, _ := registry.Resolve("claude_sdk") - if _, err := factory(t.Context(), proto.PromptRequestPayload{Model: "fixture", ModelProvider: fixtureProvider()}, nil); err == nil || !strings.Contains(err.Error(), "runtime is unavailable") { - t.Fatal(err) - } - } } } } diff --git a/apps/daemon/internal/agent/claudesdk/error_classification_test.go b/apps/daemon/internal/agent/claudesdk/error_classification_test.go index 3fbd8f8a8..12ed95144 100644 --- a/apps/daemon/internal/agent/claudesdk/error_classification_test.go +++ b/apps/daemon/internal/agent/claudesdk/error_classification_test.go @@ -25,7 +25,7 @@ func TestClassifiedBridgeFailurePreservesTerminalEvidence(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() out := make(chan proto.Envelope, 16) - s, err := NewFactory(config)(ctx, req, out) + s, err := startSingleTurn(ctx, config, req, out) if err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/agent/claudesdk/execution_controls_test.go b/apps/daemon/internal/agent/claudesdk/execution_controls_test.go index 4e10c4b7a..0fea3251d 100644 --- a/apps/daemon/internal/agent/claudesdk/execution_controls_test.go +++ b/apps/daemon/internal/agent/claudesdk/execution_controls_test.go @@ -50,7 +50,7 @@ func TestExecutionControlsRejectUnsupportedProfilesBeforeLaunch(t *testing.T) { t.Setenv("OAC_RUNTIME_HOME", root) config := Config{Node: "must-not-run", Entrypoint: filepath.Join(root, "worker"), StateDir: filepath.Join(root, "state")} request := proto.PromptRequestPayload{ModelProvider: fixtureProvider(), RunID: "run", Input: proto.TextInput("Original input."), ExecutionControls: &controls, Model: "native-model"} - _, err := NewFactory(config)(t.Context(), request, make(chan proto.Envelope, 1)) + _, err := startSingleTurn(t.Context(), config, request, make(chan proto.Envelope, 1)) if err == nil || !strings.Contains(err.Error(), "execution controls require") { t.Fatal("unsupported controls did not fail at admission", err) } @@ -67,7 +67,7 @@ func TestMCPWithoutEnvironmentNoneRejectedBeforeSetup(t *testing.T) { config := Config{Node: "must-not-run", Entrypoint: filepath.Join(root, "worker"), StateDir: filepath.Join(root, "state")} servers := []proto.MCPHTTPServer{{ConnectionOrigin: "service", ServerLabel: "remote", ServerURL: "https://example.test/mcp"}} request := proto.PromptRequestPayload{RunID: "run", Input: proto.TextInput("Input"), MCPHTTPServers: &servers, Model: "fixture", ModelProvider: fixtureProvider()} - _, err := NewFactory(config)(t.Context(), request, make(chan proto.Envelope, 1)) + _, err := startSingleTurn(t.Context(), config, request, make(chan proto.Envelope, 1)) if err == nil || !strings.Contains(err.Error(), "service-origin MCP requires a service execution host") { t.Fatal("MCP reached an unsupported environment", err) } diff --git a/apps/daemon/internal/agent/claudesdk/executor_fixture_test.go b/apps/daemon/internal/agent/claudesdk/executor_fixture_test.go index 55768099f..15d683ccf 100644 --- a/apps/daemon/internal/agent/claudesdk/executor_fixture_test.go +++ b/apps/daemon/internal/agent/claudesdk/executor_fixture_test.go @@ -4,10 +4,32 @@ package claudesdk import ( "bufio" + "context" "encoding/json" "os" + + "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) +// startSingleTurn prepares an Executor for one Turn and closes it once that +// Turn settles, so each test observes the complete native lifecycle. +func startSingleTurn(ctx context.Context, config Config, req proto.PromptRequestPayload, out chan<- proto.Envelope) (agent.Turn, error) { + run, input := req.RunID, req.Input + req.RunID, req.Input = "", nil + resource, err := NewExecutorFactory(config)(ctx, req) + if err != nil { + return nil, err + } + turn, err := resource.StartTurn(ctx, run, input, out) + if turn == nil { + _ = resource.Close(context.Background()) + return nil, err + } + go func() { _, _ = turn.AwaitSettlement(context.Background()); _ = resource.Close(context.Background()) }() + return turn, err +} + func helperTurn(scanner *bufio.Scanner, request *startRequest) (func(bridgeEvent), func()) { _ = json.NewEncoder(os.Stdout).Encode(bridgeEvent{Type: "executor_ready", Protocol: 3}) if !scanner.Scan() { diff --git a/apps/daemon/internal/agent/claudesdk/functions_test.go b/apps/daemon/internal/agent/claudesdk/functions_test.go index b1fe6f442..d0ff4d060 100644 --- a/apps/daemon/internal/agent/claudesdk/functions_test.go +++ b/apps/daemon/internal/agent/claudesdk/functions_test.go @@ -26,7 +26,7 @@ func TestFunctionFactoryNativeReceipts(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() out := make(chan proto.Envelope, 16) - running, err := NewFactory(config)(ctx, request, out) + running, err := startSingleTurn(ctx, config, request, out) if err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/agent/claudesdk/live_linux_test.go b/apps/daemon/internal/agent/claudesdk/live_linux_test.go index bda17a491..a1eafaac8 100644 --- a/apps/daemon/internal/agent/claudesdk/live_linux_test.go +++ b/apps/daemon/internal/agent/claudesdk/live_linux_test.go @@ -143,7 +143,7 @@ func TestLiveClaudeSDKTextResume(t *testing.T) { request.SystemPrompt = "Call lookup exactly once as requested, then report both result parts and any prior verification value. Never retry a failed tool." request.FunctionTools = []proto.FunctionTool{{Name: "lookup", Description: "Return a synthetic verification value.", Parameters: json.RawMessage(`{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":false}`)}} } - running, err := NewFactory(config)(ctx, request, out) + running, err := startSingleTurn(ctx, config, request, out) if err != nil { t.Fatal(err) } @@ -269,7 +269,7 @@ func TestLiveClaudeSDKTextResume(t *testing.T) { proof.SessionID, _ = payload.Metadata[proto.DoneMetaAgentSessionID].(string) } } - // Done reports the Turn outcome; the direct factory closes its Executor + // Done reports the Turn outcome; startSingleTurn closes its Executor // after output settlement. Verify release at that boundary. if _, err := s.AwaitSettlement(ctx); err != nil { t.Fatal(err) @@ -277,7 +277,7 @@ func TestLiveClaudeSDKTextResume(t *testing.T) { select { case <-s.process.Done(): case <-time.After(5 * time.Second): - t.Fatal("direct factory retained its process after settlement") + t.Fatal("single-Turn Executor retained its process after settlement") } if !steeringAt.IsZero() { receipt := <-steeringReply diff --git a/apps/daemon/internal/agent/claudesdk/messages_test.go b/apps/daemon/internal/agent/claudesdk/messages_test.go index 5481adbd7..130bd5fb9 100644 --- a/apps/daemon/internal/agent/claudesdk/messages_test.go +++ b/apps/daemon/internal/agent/claudesdk/messages_test.go @@ -24,7 +24,7 @@ func TestMessageObservations(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() out := make(chan proto.Envelope, 16) - running, err := NewFactory(config)(ctx, request, out) + running, err := startSingleTurn(ctx, config, request, out) if err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/agent/claudesdk/readiness_test.go b/apps/daemon/internal/agent/claudesdk/readiness_test.go index e322ed385..286f90799 100644 --- a/apps/daemon/internal/agent/claudesdk/readiness_test.go +++ b/apps/daemon/internal/agent/claudesdk/readiness_test.go @@ -25,7 +25,7 @@ func TestRequiredMCPNeedsQualifiedRuntime(t *testing.T) { }} req := proto.PromptRequestPayload{ModelProvider: fixtureProvider(), RunID: "run", Input: proto.TextInput("hello"), DisableExecutionEnvironment: true, Model: "fixture", MCPHTTPServers: &[]proto.MCPHTTPServer{{ConnectionOrigin: "service", ServerLabel: "fixture", ServerURL: "https://example.invalid/mcp", Required: true}}} - if _, err := NewFactory(config)(t.Context(), req, make(chan proto.Envelope, 1)); err == nil || err.Error() != "claudesdk: packaged runtime does not support required HTTP MCP" { + if _, err := startSingleTurn(t.Context(), config, req, make(chan proto.Envelope, 1)); err == nil || err.Error() != "claudesdk: packaged runtime does not support required HTTP MCP" { t.Fatalf("unqualified runtime executed required MCP: %v", err) } } @@ -46,7 +46,7 @@ func TestHTTPMCPRejectsOldPackagedRuntime(t *testing.T) { } req := proto.PromptRequestPayload{ModelProvider: fixtureProvider(), RunID: "run", Input: proto.TextInput("hello"), DisableExecutionEnvironment: true, Model: "fixture", MCPHTTPServers: &[]proto.MCPHTTPServer{{ConnectionOrigin: "service", ServerLabel: "fixture", ServerURL: "https://example.invalid/mcp"}}} - if _, err := NewFactory(config)(t.Context(), req, make(chan proto.Envelope, 1)); err == nil || !strings.Contains(err.Error(), "packaged runtime does not support HTTP MCP") { + if _, err := startSingleTurn(t.Context(), config, req, make(chan proto.Envelope, 1)); err == nil || !strings.Contains(err.Error(), "packaged runtime does not support HTTP MCP") { t.Fatalf("old runtime was not rejected before execution: %v", err) } } @@ -83,7 +83,7 @@ func TestMCPBearerRejectsAnonymousOnlyRuntimeWithoutProbeSecrets(t *testing.T) { token := "private-fixture-token" req := proto.PromptRequestPayload{ModelProvider: fixtureProvider(), RunID: "run", Input: proto.TextInput("hello"), DisableExecutionEnvironment: true, Model: "fixture", MCPHTTPServers: &[]proto.MCPHTTPServer{{ConnectionOrigin: "service", ServerLabel: "fixture", ServerURL: "https://example.invalid/mcp", BearerToken: &token}}} - if _, err := NewFactory(config)(t.Context(), req, make(chan proto.Envelope, 1)); err == nil || err.Error() != "claudesdk: packaged runtime does not support authenticated HTTP MCP" { + if _, err := startSingleTurn(t.Context(), config, req, make(chan proto.Envelope, 1)); err == nil || err.Error() != "claudesdk: packaged runtime does not support authenticated HTTP MCP" { t.Fatalf("old runtime executed authenticated request or readiness received its secret: %v", err) } } diff --git a/apps/daemon/internal/agent/claudesdk/restrictions_test.go b/apps/daemon/internal/agent/claudesdk/restrictions_test.go index cb080ae77..6fc389905 100644 --- a/apps/daemon/internal/agent/claudesdk/restrictions_test.go +++ b/apps/daemon/internal/agent/claudesdk/restrictions_test.go @@ -29,7 +29,7 @@ func TestTextFactoryAcceptsRestrictiveCapabilities(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() out := make(chan proto.Envelope, 16) - running, err := NewFactory(config)(ctx, request, out) + running, err := startSingleTurn(ctx, config, request, out) if err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/agent/claudesdk/session.go b/apps/daemon/internal/agent/claudesdk/session.go index 770d212b8..fcf91d376 100644 --- a/apps/daemon/internal/agent/claudesdk/session.go +++ b/apps/daemon/internal/agent/claudesdk/session.go @@ -32,27 +32,6 @@ type session struct { outcome proto.DonePayload } -func NewFactory(config Config) agent.Factory { - prepareExecutor := NewExecutorFactory(config) - return func(ctx context.Context, req proto.PromptRequestPayload, out chan<- proto.Envelope) (agent.Session, error) { - run, input := req.RunID, req.Input - req.RunID, req.Input = "", nil - resource, err := prepareExecutor(ctx, req) - if err != nil { - return nil, err - } - turn, err := resource.StartTurn(ctx, run, input, out) - if turn == nil { - _ = resource.Close(context.Background()) - return nil, err - } - // Factory callers own one execution. Both entrypoints use the same - // Executor implementation; Runtime pooling uses NewExecutorFactory. - go func() { _, _ = turn.AwaitSettlement(context.Background()); _ = resource.Close(context.Background()) }() - return turn, err - } -} - type bridgeEvent struct { EngineErrorCode json.RawMessage `json:"engine_error_code"` TurnID string `json:"turn_id"` diff --git a/apps/daemon/internal/agent/claudesdk/session_test.go b/apps/daemon/internal/agent/claudesdk/session_test.go index 679f156c3..811e8041f 100644 --- a/apps/daemon/internal/agent/claudesdk/session_test.go +++ b/apps/daemon/internal/agent/claudesdk/session_test.go @@ -26,7 +26,7 @@ func TestTextFactoryCompletionAndFailures(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() out := make(chan proto.Envelope, 16) - s, err := NewFactory(config)(ctx, request, out) + s, err := startSingleTurn(ctx, config, request, out) if err != nil { t.Fatal(err) } @@ -88,7 +88,7 @@ func TestTextFactoryRejectsUnsupportedInput(t *testing.T) { case "outside": config.StateDir = filepath.Dir(root) } - _, err := NewFactory(config)(context.Background(), request, make(chan proto.Envelope, 1)) + _, err := startSingleTurn(context.Background(), config, request, make(chan proto.Envelope, 1)) if err == nil || !strings.HasPrefix(err.Error(), "claudesdk:") { t.Fatalf("expected pre-launch rejection, got %v", err) } diff --git a/apps/daemon/internal/agent/claudesdk/steering_test.go b/apps/daemon/internal/agent/claudesdk/steering_test.go index 4157aeaab..25343a2e2 100644 --- a/apps/daemon/internal/agent/claudesdk/steering_test.go +++ b/apps/daemon/internal/agent/claudesdk/steering_test.go @@ -31,7 +31,7 @@ func TestSteeringReceiptsAndLifecycle(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() out := make(chan proto.Envelope, 16) - running, err := NewFactory(config)(ctx, request, out) + running, err := startSingleTurn(ctx, config, request, out) if err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/agent/claudesdk/unsupported.go b/apps/daemon/internal/agent/claudesdk/unsupported.go index 0b4da8274..154d904a4 100644 --- a/apps/daemon/internal/agent/claudesdk/unsupported.go +++ b/apps/daemon/internal/agent/claudesdk/unsupported.go @@ -2,19 +2,10 @@ package claudesdk import ( "context" - "fmt" + "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) -func (s *session) SubmitPermission(context.Context, string, proto.PermissionDecisionPayload) error { - return fmt.Errorf("%w: native permission responses are not exposed", agent.ErrUnsupportedOperation) -} - -func (s *session) SubmitPromptForUserChoice(context.Context, string, proto.PromptForUserChoiceDecisionPayload) error { - return fmt.Errorf("%w: native user-choice responses are not exposed", agent.ErrUnsupportedOperation) -} - func (s *executor) WriteWorkspaceFile(context.Context, string, []byte) (agent.WorkspaceWriteResult, error) { return agent.WorkspaceWriteResult{}, agent.ErrWorkspaceWriteUnsupported } diff --git a/apps/daemon/internal/agent/claudesdk/unsupported_test.go b/apps/daemon/internal/agent/claudesdk/unsupported_test.go index d57a25856..7fe6048c5 100644 --- a/apps/daemon/internal/agent/claudesdk/unsupported_test.go +++ b/apps/daemon/internal/agent/claudesdk/unsupported_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) // Nil concrete receivers prove Unsupported needs no native owner, transport, @@ -25,9 +24,6 @@ func TestUnsupportedExtensionsHaveNoNativeEffects(t *testing.T) { t.Fatal("unsupported error disclosed input") } } - var turn *session - check(turn.SubmitPermission(ctx, secret, proto.PermissionDecisionPayload{})) - check(turn.SubmitPromptForUserChoice(ctx, secret, proto.PromptForUserChoiceDecisionPayload{})) for _, owner := range []agent.WorkspaceWriter{(*executor)(nil), (*session)(nil)} { result, err := owner.WriteWorkspaceFile(ctx, secret, []byte(secret)) check(err) diff --git a/apps/daemon/internal/agent/claudesdk/usage_test.go b/apps/daemon/internal/agent/claudesdk/usage_test.go index 299078c5e..82b845c05 100644 --- a/apps/daemon/internal/agent/claudesdk/usage_test.go +++ b/apps/daemon/internal/agent/claudesdk/usage_test.go @@ -27,7 +27,7 @@ func TestUsageTransportPreservesSnapshotOnFailureAndDone(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() out := make(chan proto.Envelope, 16) - s, err := NewFactory(config)(ctx, request, out) + s, err := startSingleTurn(ctx, config, request, out) if err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/agent/claudesdk/view_test.go b/apps/daemon/internal/agent/claudesdk/view_test.go index ba6a24729..a7123f622 100644 --- a/apps/daemon/internal/agent/claudesdk/view_test.go +++ b/apps/daemon/internal/agent/claudesdk/view_test.go @@ -180,7 +180,7 @@ func resolveTestView(t *testing.T) agent.View { loader := viewloader.Fragment{Closure: []agent.ViewMount{lib}, Overlays: []agent.ViewOverlay{{Path: "/lib64/ld-linux-x86-64.so.2", Source: filepath.Join(root, "lib", "ld.so"), Exec: true}}, LibraryPath: lib.Path()} declared := declareView(probe, RuntimeInfo{NativePath: "native/claude"}, probe.Node, filepath.Join(root, "bundle"), "dist/main.js", loader) registry := agent.NewRegistry() - registry.Register(Declaration, agent.Runtime{Info: Declaration.Info, Session: NewFactory(probe), View: declared}) + registry.Register(Declaration, agent.Runtime{Info: Declaration.Info, View: declared}) view, err := registry.ResolveView(Declaration.Info.Kind) if err != nil { t.Fatal(err) diff --git a/apps/daemon/internal/agent/claudesdk/workspace_launch_test.go b/apps/daemon/internal/agent/claudesdk/workspace_launch_test.go index b95cda8fd..f9b109e90 100644 --- a/apps/daemon/internal/agent/claudesdk/workspace_launch_test.go +++ b/apps/daemon/internal/agent/claudesdk/workspace_launch_test.go @@ -41,7 +41,7 @@ esac t.Fatal("readiness did not receive replacement environment", err) } out := make(chan proto.Envelope, 8) - s, err := NewFactory(config)(t.Context(), workspaceRequest(), out) + s, err := startSingleTurn(t.Context(), config, workspaceRequest(), out) if err != nil { t.Fatal(err) } @@ -64,7 +64,7 @@ esac if err := os.WriteFile(config.Node, []byte(script), 0o700); err != nil { t.Fatal(err) } - if _, err := NewFactory(config)(t.Context(), workspaceRequest(), out); err == nil { + if _, err := startSingleTurn(t.Context(), config, workspaceRequest(), out); err == nil { t.Fatal("old packaged bridge accepted workspace execution") } if _, err := os.Stat(filepath.Join(config.StateDir, "unexpected-start")); !os.IsNotExist(err) { diff --git a/apps/daemon/internal/agent/claudesdk/workspace_live_linux_test.go b/apps/daemon/internal/agent/claudesdk/workspace_live_linux_test.go index c0c65d862..7a93ef048 100644 --- a/apps/daemon/internal/agent/claudesdk/workspace_live_linux_test.go +++ b/apps/daemon/internal/agent/claudesdk/workspace_live_linux_test.go @@ -93,7 +93,7 @@ func TestLiveClaudeWorkspaceFactory(t *testing.T) { req.ObserveMessages = true req.Model, req.SystemPrompt = "MiniMax-M3", "Follow the exact verification instructions using the requested native tools. Preserve conversation facts. No other files, network operations or background work." proof := evidence{RunID: req.RunID} - running, err := NewFactory(config)(ctx, req, out) + running, err := startSingleTurn(ctx, config, req, out) if err != nil { if name == "missing-history" && running == nil && strings.Contains(err.Error(), "history_unavailable") { proof.Failure = err.Error() diff --git a/apps/daemon/internal/agent/codex/approval_policy.go b/apps/daemon/internal/agent/codex/approval_policy.go deleted file mode 100644 index 9b084e4da..000000000 --- a/apps/daemon/internal/agent/codex/approval_policy.go +++ /dev/null @@ -1,85 +0,0 @@ -package codex - -import ( - "encoding/json" - "fmt" -) - -// SilentGranularPolicy disables every granular Codex approval gate. -func SilentGranularPolicy() AskForApproval { - g := GranularAskForApproval{} // zero value = every gate false - return AskForApproval{Granular: &g} -} - -// HumanApprovalPolicy lets Codex request sandbox escalation from Core. -func HumanApprovalPolicy() AskForApproval { - return AskForApproval{String: "on-request"} -} - -// IsSilent reports whether p suppresses every approval surface. The -// daemon logs this distinction when it starts a thread. Server-request -// handlers are always registered so explicit policy overrides cannot leave -// Codex waiting on an unhandled request. -// -// A nil pointer is treated as silent — the safe default when callers -// forget to wire a policy. -func IsSilent(p *AskForApproval) bool { - if p == nil { - return true - } - if p.Granular != nil { - g := p.Granular - return !g.SandboxApproval && !g.Rules && !g.SkillApproval && - !g.RequestPermissions && !g.MCPElicitations - } - // Bare string variants ("never" is silent; everything else surfaces). - return p.String == "never" -} - -// MarshalJSON emits codex-rs's discriminated union — either a bare -// string or the granular object. Producing both would deserialise to -// the granular branch on the codex side (it wins precedence in -// codex-rs/protocol.rs AskForApproval), but the wire would be wrong; we -// validate exclusivity here. -func (a AskForApproval) MarshalJSON() ([]byte, error) { - hasString := a.String != "" - hasGranular := a.Granular != nil - if hasString && hasGranular { - return nil, fmt.Errorf("codex: AskForApproval must set exactly one of String or Granular, got both") - } - if hasString { - return json.Marshal(a.String) - } - if hasGranular { - return json.Marshal(struct { - Granular *GranularAskForApproval `json:"granular"` - }{Granular: a.Granular}) - } - // Neither set — preserve the historical zero-value encoding. Production - // plans always set a policy explicitly; an empty marshal would - // produce `null`, which codex-rs rejects. - return json.Marshal(struct { - Granular GranularAskForApproval `json:"granular"` - }{}) -} - -// UnmarshalJSON is the inverse: codex's ThreadStartResult.approvalPolicy -// echoes back as either a string or {"granular":...}. Provided so tests -// can round-trip without surprises; production code only marshals. -func (a *AskForApproval) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err == nil { - a.String = s - a.Granular = nil - return nil - } - var obj struct { - Granular *GranularAskForApproval `json:"granular"` - } - if err := json.Unmarshal(data, &obj); err != nil { - return fmt.Errorf("codex: AskForApproval: %w", err) - } - a.Granular = obj.Granular - a.String = "" - return nil -} diff --git a/apps/daemon/internal/agent/codex/approval_policy_test.go b/apps/daemon/internal/agent/codex/approval_policy_test.go deleted file mode 100644 index 7a98f06cd..000000000 --- a/apps/daemon/internal/agent/codex/approval_policy_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package codex - -import ( - "encoding/json" - "testing" -) - -func TestSilentGranularPolicy_AllFalse(t *testing.T) { - p := SilentGranularPolicy() - if p.Granular == nil { - t.Fatal("SilentGranularPolicy must populate Granular") - } - g := *p.Granular - if g.SandboxApproval || g.Rules || g.SkillApproval || g.RequestPermissions || g.MCPElicitations { - t.Fatalf("silent policy must have every gate false, got %+v", g) - } - if !IsSilent(&p) { - t.Fatal("IsSilent must return true for the silent default") - } -} - -func TestHumanApprovalPolicy_UsesOnRequest(t *testing.T) { - p := HumanApprovalPolicy() - if p.String != "on-request" || p.Granular != nil { - t.Fatalf("HumanApprovalPolicy = %+v, want on-request string policy", p) - } - if IsSilent(&p) { - t.Fatal("HumanApprovalPolicy must surface app-server requests") - } -} - -func TestIsSilent_NilIsSilent(t *testing.T) { - if !IsSilent(nil) { - t.Fatal("nil policy must be treated as silent (safe default)") - } -} - -func TestIsSilent_StringPolicies(t *testing.T) { - cases := []struct { - name string - policy AskForApproval - want bool - }{ - {"never silences", AskForApproval{String: "never"}, true}, - {"on-request loud", AskForApproval{String: "on-request"}, false}, - {"untrusted loud", AskForApproval{String: "untrusted"}, false}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - if got := IsSilent(&tc.policy); got != tc.want { - t.Fatalf("IsSilent(%+v) = %v, want %v", tc.policy, got, tc.want) - } - }) - } -} - -func TestIsSilent_PartialGranularIsLoud(t *testing.T) { - // Flipping any single gate true must make IsSilent report false so - // the daemon registers the loud server-request handler. - for _, flip := range []func(*GranularAskForApproval){ - func(g *GranularAskForApproval) { g.SandboxApproval = true }, - func(g *GranularAskForApproval) { g.Rules = true }, - func(g *GranularAskForApproval) { g.SkillApproval = true }, - func(g *GranularAskForApproval) { g.RequestPermissions = true }, - func(g *GranularAskForApproval) { g.MCPElicitations = true }, - } { - g := GranularAskForApproval{} - flip(&g) - p := AskForApproval{Granular: &g} - if IsSilent(&p) { - t.Fatalf("IsSilent must be false when any gate is true: %+v", g) - } - } -} - -func TestAskForApproval_MarshalString(t *testing.T) { - p := AskForApproval{String: "never"} - raw, err := json.Marshal(p) - if err != nil { - t.Fatalf("marshal: %v", err) - } - if string(raw) != `"never"` { - t.Fatalf("marshal string variant = %s", raw) - } -} - -func TestAskForApproval_MarshalGranular(t *testing.T) { - g := GranularAskForApproval{RequestPermissions: true} - p := AskForApproval{Granular: &g} - raw, err := json.Marshal(p) - if err != nil { - t.Fatalf("marshal: %v", err) - } - want := `{"granular":{"sandbox_approval":false,"rules":false,"skill_approval":false,"request_permissions":true,"mcp_elicitations":false}}` - if string(raw) != want { - t.Fatalf("marshal granular = %s\nwant = %s", raw, want) - } -} - -func TestAskForApproval_MarshalEmpty_DefaultsSilent(t *testing.T) { - // Neither field set — must not produce `null` (codex rejects it). - var p AskForApproval - raw, err := json.Marshal(p) - if err != nil { - t.Fatalf("marshal: %v", err) - } - if string(raw) == "null" { - t.Fatalf("empty AskForApproval must not marshal to null: %s", raw) - } - if string(raw) != `{"granular":{"sandbox_approval":false,"rules":false,"skill_approval":false,"request_permissions":false,"mcp_elicitations":false}}` { - t.Fatalf("empty AskForApproval marshal = %s", raw) - } -} - -func TestAskForApproval_MarshalBothErrors(t *testing.T) { - g := GranularAskForApproval{} - p := AskForApproval{String: "never", Granular: &g} - if _, err := json.Marshal(p); err == nil { - t.Fatal("setting both String and Granular must error on marshal") - } -} - -func TestAskForApproval_RoundTripString(t *testing.T) { - var got AskForApproval - if err := json.Unmarshal([]byte(`"on-request"`), &got); err != nil { - t.Fatalf("unmarshal: %v", err) - } - if got.String != "on-request" || got.Granular != nil { - t.Fatalf("round-trip = %+v", got) - } -} - -func TestAskForApproval_RoundTripGranular(t *testing.T) { - src := `{"granular":{"sandbox_approval":true,"rules":false,"skill_approval":false,"request_permissions":false,"mcp_elicitations":false}}` - var got AskForApproval - if err := json.Unmarshal([]byte(src), &got); err != nil { - t.Fatalf("unmarshal: %v", err) - } - if got.Granular == nil || !got.Granular.SandboxApproval || got.String != "" { - t.Fatalf("round-trip = %+v", got) - } -} diff --git a/apps/daemon/internal/agent/codex/contracts.go b/apps/daemon/internal/agent/codex/contracts.go index 7a19d9eb8..a8b58b88e 100644 --- a/apps/daemon/internal/agent/codex/contracts.go +++ b/apps/daemon/internal/agent/codex/contracts.go @@ -11,8 +11,6 @@ var ( _ agent.DurableSteerer = (*Session)(nil) _ agent.Steerer = (*Session)(nil) _ agent.FunctionResultSubmitter = (*Session)(nil) - _ agent.PermissionResponder = (*Session)(nil) - _ agent.UserChoiceResponder = (*Session)(nil) _ agent.WorkspaceDirectoryLister = (*Session)(nil) _ agent.WorkspaceWriter = (*Session)(nil) _ agent.WorkspaceDirectoryLister = (*Executor)(nil) diff --git a/apps/daemon/internal/agent/codex/declaration.go b/apps/daemon/internal/agent/codex/declaration.go index 49f2c528d..2b497b07e 100644 --- a/apps/daemon/internal/agent/codex/declaration.go +++ b/apps/daemon/internal/agent/codex/declaration.go @@ -16,7 +16,6 @@ var Declaration = agent.Declaration{Info: proto.SupportedAgentKind{ Capabilities: proto.AgentKindCapabilities{ SubagentObservations: proto.CapabilitySupported, Streaming: proto.CapabilitySupported, - Permissions: proto.CapabilitySupported, Usage: proto.CapabilitySupported, Resume: proto.CapabilitySupported, NativeSessionRecovery: proto.CapabilityUnsupported, @@ -50,7 +49,7 @@ func discover(ctx context.Context, options agent.DiscoveryOptions, info proto.Su return discoverWithCheck(ctx, options, info, CheckCLIAvailable) } func discoverWithCheck(parent context.Context, options agent.DiscoveryOptions, info proto.SupportedAgentKind, check func(context.Context, string) (string, error)) *agent.Runtime { - runtime := &agent.Runtime{Info: info, Session: Factory} + runtime := &agent.Runtime{Info: info} ctx, cancel := context.WithTimeout(parent, 15*time.Second) defer cancel() version, err := check(ctx, "") diff --git a/apps/daemon/internal/agent/codex/declaration_test.go b/apps/daemon/internal/agent/codex/declaration_test.go index b059fef73..65a4460c4 100644 --- a/apps/daemon/internal/agent/codex/declaration_test.go +++ b/apps/daemon/internal/agent/codex/declaration_test.go @@ -29,7 +29,7 @@ func TestMCPRequiredDiscoveryRequiresPinnedNative(t *testing.T) { // The declaration must retain the complete baseline capability descriptor. func TestDeclaredCapabilityBaseline(t *testing.T) { - expected := map[string]bool{"SubagentObservations": true, "Streaming": true, "Permissions": true, "Usage": true, "Resume": true, "Steering": true, "MessageItems": true, "ToolObservations": true, "EnvironmentNone": true, "ProgrammaticToolCallingDisable": true, "WebSearchControl": true, "MessageImages": true, "FunctionResultImages": true, "SubagentControl": true, "DurableInputReceipts": true, "DurableTurns": true, "FunctionTools": true, "MCPHTTPTools": true, "MCPHTTPBearerAuth": true} + expected := map[string]bool{"SubagentObservations": true, "Streaming": true, "Usage": true, "Resume": true, "Steering": true, "MessageItems": true, "ToolObservations": true, "EnvironmentNone": true, "ProgrammaticToolCallingDisable": true, "WebSearchControl": true, "MessageImages": true, "FunctionResultImages": true, "SubagentControl": true, "DurableInputReceipts": true, "DurableTurns": true, "FunctionTools": true, "MCPHTTPTools": true, "MCPHTTPBearerAuth": true} expected["ExecutionControls"], expected["TextVerbosity"] = SupportsTextVerbosity, SupportsTextVerbosity value := reflect.ValueOf(Declaration.Info.Capabilities) for i := 0; i < value.NumField(); i++ { @@ -49,7 +49,7 @@ func TestDeclaredCapabilityBaseline(t *testing.T) { func TestUnavailableRuntimeHasNoExecutionFactories(t *testing.T) { runtime := discoverWithCheck(t.Context(), agent.DiscoveryOptions{Stdout: io.Discard, Stderr: io.Discard}, Declaration.Info, func(context.Context, string) (string, error) { return "", errors.New("missing") }) - if runtime.Info.Available || runtime.Executor != nil || runtime.View != nil || runtime.Session == nil { + if runtime.Info.Available || runtime.Executor != nil || runtime.View != nil { t.Fatalf("unavailable runtime: %+v", runtime) } } diff --git a/apps/daemon/internal/agent/codex/execution_controls_test.go b/apps/daemon/internal/agent/codex/execution_controls_test.go index 182a48fb0..3b9260110 100644 --- a/apps/daemon/internal/agent/codex/execution_controls_test.go +++ b/apps/daemon/internal/agent/codex/execution_controls_test.go @@ -14,7 +14,7 @@ func TestExecutionControlsSelectNativeSettings(t *testing.T) { t.Fatal(err) } plan.Cleanup() - if len(plan.ExtraConfig) != 1 || plan.ExtraConfig[0][0] != "model_provider" { + if want := [][2]string{{"tools.experimental_request_user_input.enabled", "false"}, {"model_provider", `"` + oacProviderSlug + `"`}}; !reflect.DeepEqual(plan.ExtraConfig, want) { t.Fatal("native settings without ExecutionControls", plan.ExtraConfig) } for _, search := range []string{"disabled", "cached", "live"} { @@ -24,7 +24,7 @@ func TestExecutionControlsSelectNativeSettings(t *testing.T) { t.Fatal(err) } plan.Cleanup() - want := [][2]string{{"web_search", `"` + search + `"`}, {"model_verbosity", `"` + verbosity + `"`}, {"model_provider", `"` + oacProviderSlug + `"`}} + want := [][2]string{{"tools.experimental_request_user_input.enabled", "false"}, {"web_search", `"` + search + `"`}, {"model_verbosity", `"` + verbosity + `"`}, {"model_provider", `"` + oacProviderSlug + `"`}} if !reflect.DeepEqual(plan.ExtraConfig, want) { t.Fatalf("config = %v, want %v", plan.ExtraConfig, want) } diff --git a/apps/daemon/internal/agent/codex/executor.go b/apps/daemon/internal/agent/codex/executor.go index 75403c57d..d8997e525 100644 --- a/apps/daemon/internal/agent/codex/executor.go +++ b/apps/daemon/internal/agent/codex/executor.go @@ -72,7 +72,7 @@ func (e *Executor) StartTurn(ctx context.Context, runID string, input proto.Mess functions: functions, observeMessages: base.observeMessages, observeSubagentIdentities: base.observeSubagentIdentities, cfg: base.cfg, rpc: base.rpc, cancelCtx: turnCtx, cancelFn: cancel, waitDone: make(chan struct{}), outputDone: make(chan struct{}), cleanup: func() {}, - bufs: NewItemBuffers(), resolvedModel: base.resolvedModel, interactions: newPendingCodexInteractions(), runID: runID, out: out} + bufs: NewItemBuffers(), resolvedModel: base.resolvedModel, runID: runID, out: out} if previous != nil { s.threadID = previous.currentThreadID() s.retiredTurns = make(map[string]bool, len(previous.retiredTurns)+1) diff --git a/apps/daemon/internal/agent/codex/executor_native_test.go b/apps/daemon/internal/agent/codex/executor_native_test.go index 9505d4b50..762ef7389 100644 --- a/apps/daemon/internal/agent/codex/executor_native_test.go +++ b/apps/daemon/internal/agent/codex/executor_native_test.go @@ -3,6 +3,7 @@ package codex import ( "context" "encoding/json" + "log/slog" "os" "os/exec" "strings" @@ -12,7 +13,6 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" "github.com/MiniMax-AI/OpenAgentCore/internal/modelprovider" - obslog "github.com/MiniMax-AI/OpenAgentCore/internal/obs/log" ) // This opt-in test uses the real pinned harness and an explicitly configured @@ -48,7 +48,7 @@ func TestExecutorNativeReuse(t *testing.T) { } cfg := defaultSessionConfig() cfg.codexBinary = binary - cfg.logger = obslog.Discard() + cfg.logger = slog.New(slog.DiscardHandler) req := proto.PromptRequestPayload{ AgentKind: "codex", AgentStateKey: "executor-native", DisableExecutionEnvironment: true, DisableSubagents: true, ObserveMessages: true, diff --git a/apps/daemon/internal/agent/codex/executor_test.go b/apps/daemon/internal/agent/codex/executor_test.go index 954f00869..9c88a0579 100644 --- a/apps/daemon/internal/agent/codex/executor_test.go +++ b/apps/daemon/internal/agent/codex/executor_test.go @@ -2,8 +2,10 @@ package codex import ( "context" + "encoding/json" "errors" "os" + "strings" "sync" "sync/atomic" "testing" @@ -17,22 +19,32 @@ import ( func executorFixture(t *testing.T, mode string) (*Executor, string) { t.Helper() req, cfg, root := preparationFixture(t) + e, err := testExecutor(t, mode, req, cfg) + if err != nil { + t.Fatal(err) + } + return e, root +} + +// testExecutor prepares through the production factory and closes the owner at cleanup. +func testExecutor(t *testing.T, mode string, req proto.PromptRequestPayload, cfg sessionConfig) (*Executor, error) { + t.Helper() t.Setenv("OAC_TEST_EXECUTOR_MODE", mode) ownerCtx, cancelOwner := context.WithCancel(context.Background()) t.Cleanup(cancelOwner) e, err := newExecutor(ownerCtx, req, cfg) - if err != nil { - t.Fatal(err) + if e != nil { + t.Cleanup(func() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := e.Close(ctx); err != nil { + t.Error(err) + } + }) } - t.Cleanup(func() { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := e.Close(ctx); err != nil { - t.Error(err) - } - }) - return e, root + return e, err } + func awaitExecutorTurn(t *testing.T, turn agent.Turn, out <-chan proto.Envelope) agent.TurnSettlement { t.Helper() ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) @@ -52,6 +64,21 @@ func awaitExecutorTurn(t *testing.T, turn agent.Turn, out <-chan proto.Envelope) } return settlement } + +// settledFrames waits for a Turn to settle and returns its complete output. +func settledFrames(t *testing.T, turn agent.Turn, out <-chan proto.Envelope) []proto.Envelope { + t.Helper() + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) + defer cancel() + if _, err := turn.AwaitSettlement(ctx); errors.Is(err, context.DeadlineExceeded) { + t.Fatal("Turn did not settle") + } + var frames []proto.Envelope + for frame := range out { + frames = append(frames, frame) + } + return frames +} func TestExecutorNormalTurnsKeepProcessAndThread(t *testing.T) { e, root := executorFixture(t, "complete") var previous agent.Turn @@ -84,6 +111,95 @@ func TestExecutorNormalTurnsKeepProcessAndThread(t *testing.T) { t.Fatal("normal completion closed executor") } } +func TestExecutorFreezesPreparedConfiguration(t *testing.T) { + for _, resume := range []bool{false, true} { + t.Run(map[bool]string{false: "new", true: "resumed"}[resume], func(t *testing.T) { + req, cfg, root := preparationFixture(t) + expectedThread := "thread/start" + if resume { + req.AgentSessionID, expectedThread = "fixture-native-thread", "thread/resume" + } + e, err := testExecutor(t, "complete", req, cfg) + if err != nil { + t.Fatal(err) + } + assertPreparationOnly(t, root) + cwd := e.prepared.plan.Cwd + // Caller-owned data cannot revise the prepared native configuration. + req.Model = "different-model" + req.AgentSessionID = "different-thread" + copy(req.FunctionTools[0].Parameters, strings.ReplaceAll(string(req.FunctionTools[0].Parameters), "integer", "boolean")) + out := make(chan proto.Envelope, 20) + turn, err := e.StartTurn(t.Context(), "actual-run", proto.TextInput("actual prompt"), out) + if err != nil { + t.Fatal(err) + } + awaitExecutorTurn(t, turn, out) + counts := map[string]int{} + for _, frame := range preparationFrames(t, root) { + counts[frame.Method]++ + var params struct { + Model string `json:"model"` + ThreadID string `json:"threadId"` + DynamicTools []dynamicFunctionTool `json:"dynamicTools"` + Cwd string `json:"cwd"` + Environments json.RawMessage `json:"environments"` + } + if err := json.Unmarshal(frame.Params, ¶ms); err != nil { + t.Fatal(err) + } + if frame.Method == "thread/start" && (params.Model != "fixture-model" || len(params.DynamicTools) != 1 || + !strings.Contains(string(params.DynamicTools[0].InputSchema), "integer") || params.Cwd != cwd) { + t.Fatal("prepared configuration changed", string(frame.Params)) + } + if frame.Method == "thread/resume" && params.ThreadID != "fixture-native-thread" { + t.Fatal("prepared resume changed") + } + if len(params.Environments) != 0 { + t.Fatal("prepared environment changed") + } + } + if counts["initialize"] != 1 || counts["environment/status"] != 2 || counts[expectedThread] != 1 || counts["turn/start"] != 1 { + t.Fatal("unexpected native setup/start count", counts) + } + }) + } +} + +func TestExecutorUnavailableOwnerStartsNoTurn(t *testing.T) { + for _, reason := range []string{"closed", "owner cancelled", "rpc exited"} { + t.Run(reason, func(t *testing.T) { + req, cfg, root := preparationFixture(t) + owner, cancelOwner := context.WithCancel(context.Background()) + defer cancelOwner() + e, err := newExecutor(owner, req, cfg) + if err != nil { + t.Fatal(err) + } + defer e.Close(context.Background()) + switch reason { + case "closed": + err = e.Close(t.Context()) + case "owner cancelled": + cancelOwner() + case "rpc exited": + err = e.prepared.session.rpc.Close() + } + if err != nil { + t.Fatal(err) + } + out := make(chan proto.Envelope, 1) + if turn, err := e.StartTurn(t.Context(), "late-run", proto.TextInput("must not start"), out); turn != nil || err == nil { + t.Fatal("unavailable executor started a Turn", err) + } + if len(out) != 0 { + t.Fatal("rejected start emitted output") + } + assertPreparationOnly(t, root) + }) + } +} + func TestExecutorCancellationSettlesThenReuses(t *testing.T) { e, root := executorFixture(t, "complete") out := make(chan proto.Envelope, 20) diff --git a/apps/daemon/internal/agent/codex/executor_turn.go b/apps/daemon/internal/agent/codex/executor_turn.go index d02eb988c..60746293f 100644 --- a/apps/daemon/internal/agent/codex/executor_turn.go +++ b/apps/daemon/internal/agent/codex/executor_turn.go @@ -44,7 +44,6 @@ func (s *Session) settleExecutorTurn(startErr error) { s.operationMu.Unlock() s.stopSteering() s.stopFunctionCalls() - s.stopCodexInteractionTimers() if !s.nativeSettled.Load() || startErr != nil || !s.rpc.Alive() { s.cancelFn() s.settlementErr = errors.Join(s.settlementErr, s.rpc.Close()) @@ -120,7 +119,6 @@ func (s *Session) cancelExecutorTurn(ctx context.Context) error { defer close(s.cancelReady) s.stopFunctionCalls() turnID, active := s.stopSteering() - s.stopCodexInteractionTimers() if !s.terminal.Load() && active { if turnID == "" { s.cancelErr = errors.New("codex: cancellation has no native turn identity") @@ -150,9 +148,7 @@ func (s *Session) cancelExecutorTurn(ctx context.Context) error { var _ agent.Turn = (*Session)(nil) -// Server callbacks retain their originating Turn. MCP elicitation may be a -// standalone thread-scoped request in the native protocol; a supplied Turn ID -// must still match exactly. +// Server callbacks retain their originating Turn, whose ID must match exactly. func (s *Session) onServerRequest(method string, handler ServerRequestHandler) { s.rpc.OnServerRequest(method, func(raw json.RawMessage, id any) (any, error) { if s.executor != nil { @@ -161,8 +157,7 @@ func (s *Session) onServerRequest(method string, handler ServerRequestHandler) { TurnID string `json:"turnId"` } if json.Unmarshal(raw, &scope) != nil || !s.isRootThread(scope.ThreadID) || s.terminal.Load() || - (scope.TurnID != "" && !s.isRootTurn(scope.ThreadID, scope.TurnID)) || - (scope.TurnID == "" && method != "mcpServer/elicitation/request") { + scope.TurnID == "" || !s.isRootTurn(scope.ThreadID, scope.TurnID) { return nil, errors.New("codex: request does not belong to the active turn") } } diff --git a/apps/daemon/internal/agent/codex/function_receipt_test.go b/apps/daemon/internal/agent/codex/function_receipt_test.go index decbf235e..fd138a009 100644 --- a/apps/daemon/internal/agent/codex/function_receipt_test.go +++ b/apps/daemon/internal/agent/codex/function_receipt_test.go @@ -16,7 +16,7 @@ import ( func TestFunctionResultWaitsForNativeCompletion(t *testing.T) { client, server, cleanup := NewTestClient() defer cleanup() - session, output := newInteractionTestSession(client.JSONRPCClient) + session, output := newFunctionTestSession(client.JSONRPCClient) session.setThreadID("thread") session.startSteering("thread", "turn") var err error @@ -61,7 +61,7 @@ func pendingReceiptContext(t *testing.T, ctx context.Context) (*Session, ServerS t.Helper() client, server, cleanup := NewTestClient() t.Cleanup(cleanup) - s, out := newInteractionTestSession(client.JSONRPCClient) + s, out := newFunctionTestSession(client.JSONRPCClient) s.cfg.logger = slog.New(slog.NewTextHandler(io.Discard, nil)) s.setThreadID("thread") s.startSteering("thread", "turn") @@ -169,7 +169,7 @@ func TestFunctionReceiptShutdownDoesNotConfirmApplication(t *testing.T) { func TestFunctionReceiptDeadlineEndsUncertainExecution(t *testing.T) { client, server, cleanup := NewTestClient() defer cleanup() - s, out := newInteractionTestSession(client.JSONRPCClient) + s, out := newFunctionTestSession(client.JSONRPCClient) s.setThreadID("thread") s.startSteering("thread", "turn") s.functions, _ = prepareFunctionTools([]proto.FunctionTool{{Name: "lookup", Parameters: json.RawMessage(`{}`)}}) @@ -207,7 +207,7 @@ func TestFunctionReceiptDeadlineEndsUncertainExecution(t *testing.T) { func TestFunctionReceiptWinsSimultaneousDeadline(t *testing.T) { for range 100 { client, _, cleanup := NewTestClient() - s, _ := newInteractionTestSession(client.JSONRPCClient) + s, _ := newFunctionTestSession(client.JSONRPCClient) s.setThreadID("thread") s.startSteering("thread", "turn") s.functions, _ = prepareFunctionTools(nil) diff --git a/apps/daemon/internal/agent/codex/function_write_receipt_test.go b/apps/daemon/internal/agent/codex/function_write_receipt_test.go index 49cdc6826..7e6730647 100644 --- a/apps/daemon/internal/agent/codex/function_write_receipt_test.go +++ b/apps/daemon/internal/agent/codex/function_write_receipt_test.go @@ -31,7 +31,7 @@ func (w *confirmedResultWriter) Close() error { w.once.Do(func() { close(w.close func TestFunctionConfirmedReceiptDuringWriteDeadline(t *testing.T) { client, _, cleanup := NewTestClient() defer cleanup() - s, out := newInteractionTestSession(client.JSONRPCClient) + s, out := newFunctionTestSession(client.JSONRPCClient) s.setThreadID("thread") s.startSteering("thread", "turn") s.functions, _ = prepareFunctionTools([]proto.FunctionTool{{Name: "lookup", Parameters: json.RawMessage(`{}`)}}) @@ -64,7 +64,7 @@ func TestFunctionConfirmedReceiptDuringWriteDeadline(t *testing.T) { func TestFunctionUnconfirmedWriteDeadlineClosesNative(t *testing.T) { client, _, cleanup := NewTestClient() defer cleanup() - s, out := newInteractionTestSession(client.JSONRPCClient) + s, out := newFunctionTestSession(client.JSONRPCClient) s.setThreadID("thread") s.startSteering("thread", "turn") s.functions, _ = prepareFunctionTools([]proto.FunctionTool{{Name: "lookup", Parameters: json.RawMessage(`{}`)}}) diff --git a/apps/daemon/internal/agent/codex/functions_test.go b/apps/daemon/internal/agent/codex/functions_test.go index 1fc34979f..a2fd9c752 100644 --- a/apps/daemon/internal/agent/codex/functions_test.go +++ b/apps/daemon/internal/agent/codex/functions_test.go @@ -12,12 +12,20 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) +func newFunctionTestSession(rpc *JSONRPCClient) (*Session, <-chan proto.Envelope) { + out := make(chan proto.Envelope, 1) + ctx, cancel := context.WithCancel(context.Background()) + s := &Session{runID: "run-test", out: out, rpc: rpc, cancelCtx: ctx, cancelFn: cancel} + s.registerHandlers() + return s, out +} + func TestFunctionCallWaitsAndRepliesOnce(t *testing.T) { for _, success := range []bool{true, false} { t.Run(map[bool]string{true: "success", false: "failure"}[success], func(t *testing.T) { tc, srv, cleanup := NewTestClient() defer cleanup() - s, out := newInteractionTestSession(tc.JSONRPCClient) + s, out := newFunctionTestSession(tc.JSONRPCClient) var err error s.functions, err = prepareFunctionTools([]proto.FunctionTool{{Name: "lookup", Parameters: json.RawMessage(`{"type":"object"}`)}}) if err != nil { @@ -88,7 +96,7 @@ func TestFunctionCallWaitsAndRepliesOnce(t *testing.T) { func TestFunctionCallRejectsUnregisteredAndClosedRuns(t *testing.T) { tc, _, cleanup := NewTestClient() defer cleanup() - s, _ := newInteractionTestSession(tc.JSONRPCClient) + s, _ := newFunctionTestSession(tc.JSONRPCClient) s.setThreadID("thread") s.startSteering("thread", "turn") s.functions, _ = prepareFunctionTools([]proto.FunctionTool{{Name: "lookup", Parameters: json.RawMessage(`{}`)}}) diff --git a/apps/daemon/internal/agent/codex/mcp_http_preflight_test.go b/apps/daemon/internal/agent/codex/mcp_http_preflight_test.go index 3ab7ace7d..d3f643744 100644 --- a/apps/daemon/internal/agent/codex/mcp_http_preflight_test.go +++ b/apps/daemon/internal/agent/codex/mcp_http_preflight_test.go @@ -1,13 +1,11 @@ package codex import ( - "context" "encoding/json" "os" "path/filepath" "strings" "testing" - "time" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) @@ -155,9 +153,9 @@ func TestPublicMCPHTTPPreparationChecksBeforeNewAndResumedThread(t *testing.T) { path := filepath.Join(root, "native-config.json") writeMCPHTTPConfigResponse(t, path, response) t.Setenv("OAC_TEST_PREPARATION_MCP_CONFIG", path) - p, err := newPreparation(t.Context(), req, cfg) + e, err := testExecutor(t, "complete", req, cfg) if strings.HasPrefix(mode, "reject") { - if err == nil || p != nil { + if err == nil || e != nil { t.Fatal("ambient MCP configuration admitted") } assertPreparationOnly(t, root) @@ -167,18 +165,18 @@ func TestPublicMCPHTTPPreparationChecksBeforeNewAndResumedThread(t *testing.T) { if err != nil { t.Fatal(err) } - defer p.Close() assertPreparationOnly(t, root) home, err := allocCodexHome(req.AgentStateKey) if err != nil { t.Fatal(err) } - s, err := p.start(t.Context(), "actual-run", proto.TextInput("actual prompt"), make(chan proto.Envelope, 8)) + out := make(chan proto.Envelope, 20) + turn, err := e.StartTurn(t.Context(), "actual-run", proto.TextInput("actual prompt"), out) if err != nil { t.Fatal(err) } - defer s.Cancel(context.Background()) - frames := waitPreparationMethod(t, root, "turn/start") + awaitExecutorTurn(t, turn, out) + frames := preparationFrames(t, root) checked, statuses := false, 0 for _, frame := range frames { if frame.Method == "environment/status" { @@ -203,12 +201,6 @@ func TestPublicMCPHTTPPreparationChecksBeforeNewAndResumedThread(t *testing.T) { t.Fatal("preflight started discovery") } } - _ = s.Cancel(context.Background()) - select { - case <-s.waitDone: - case <-time.After(4 * time.Second): - t.Fatal("native fixture did not release") - } }) } } diff --git a/apps/daemon/internal/agent/codex/mcp_required_test.go b/apps/daemon/internal/agent/codex/mcp_required_test.go index b488c4aae..cf8860f10 100644 --- a/apps/daemon/internal/agent/codex/mcp_required_test.go +++ b/apps/daemon/internal/agent/codex/mcp_required_test.go @@ -1,13 +1,13 @@ package codex import ( - "context" "os" "path/filepath" "strings" "testing" "time" + "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) @@ -34,17 +34,20 @@ func TestRequiredMCPWaitsForNativeThreadAndNeverRestartsFailedResume(t *testing. t.Setenv("OAC_TEST_PREPARATION_MCP_CONFIG", config) gate := filepath.Join(root, "required-initialization") t.Setenv("OAC_TEST_PREPARATION_THREAD_GATE", gate) - p, err := newPreparation(t.Context(), req, cfg) + e, err := testExecutor(t, "complete", req, cfg) if err != nil { t.Fatal(err) } - defer p.Close() out := make(chan proto.Envelope, 16) - s, err := p.start(t.Context(), "required-run", proto.TextInput("actual prompt"), out) - if err != nil { - t.Fatal(err) + type started struct { + turn agent.Turn + err error } - defer s.Cancel(context.Background()) + result := make(chan started, 1) + go func() { + turn, err := e.StartTurn(t.Context(), "required-run", proto.TextInput("actual prompt"), out) + result <- started{turn, err} + }() waitPreparationMethod(t, root, method) time.Sleep(100 * time.Millisecond) assertNoTurn := func() { @@ -67,19 +70,23 @@ func TestRequiredMCPWaitsForNativeThreadAndNeverRestartsFailedResume(t *testing. if err := os.WriteFile(gate, []byte(state), 0o600); err != nil { t.Fatal(err) } - if state == "ready" { - waitPreparationMethod(t, root, "turn/start") - return - } + var start started select { - case <-s.waitDone: + case start = <-result: case <-time.After(4 * time.Second): - t.Fatal("failed initialization did not terminate") + t.Fatal("native initialization did not finish") + } + if (start.err == nil) != (state == "ready") { + t.Fatal("native initialization outcome changed", start.err) + } + frames := settledFrames(t, start.turn, out) + if state == "ready" { + return } assertNoTurn() failed := false - for len(out) > 0 { - failed = (<-out).Type == proto.TypeError || failed + for _, frame := range frames { + failed = frame.Type == proto.TypeError || failed } if !failed { t.Fatal("native initialization failure was not reported") diff --git a/apps/daemon/internal/agent/codex/options.go b/apps/daemon/internal/agent/codex/options.go index a8e904194..c1cde2f10 100644 --- a/apps/daemon/internal/agent/codex/options.go +++ b/apps/daemon/internal/agent/codex/options.go @@ -62,18 +62,12 @@ type SessionPlan struct { // ModelReasoningEffort is frozen for launch and every native Turn. ModelReasoningEffort string - // ApprovalPolicy + Sandbox apply to both new and resumed threads. - ApprovalPolicy AskForApproval - Sandbox SandboxMode - Permissions string - // Cleanup is the deferred housekeeping the session must run after the child exits. Cleanup func() } // BuildSessionPlan derives a SessionPlan from the request's frozen model // configuration and ExecutionControls. The codex binary is resolved via PATH. -// Daemon-managed Codex sessions bypass approvals and the engine sandbox. func BuildSessionPlan(req proto.PromptRequestPayload) (SessionPlan, error) { return buildSessionPlan(req, func() (agent.ViewDir, error) { home, err := allocCodexHome(req.AgentStateKey) @@ -85,9 +79,9 @@ func BuildSessionPlan(req proto.PromptRequestPayload) (SessionPlan, error) { // only after the request validates. func buildSessionPlan(req proto.PromptRequestPayload, allocHome func() (agent.ViewDir, error)) (SessionPlan, error) { plan := SessionPlan{ - ApprovalPolicy: AskForApproval{String: "never"}, - Sandbox: SandboxDangerFullAcces, - Cleanup: func() {}, + // Harnesses run unattended: Codex never offers its ask-the-user tool. + ExtraConfig: [][2]string{{"tools.experimental_request_user_input.enabled", "false"}}, + Cleanup: func() {}, } prepared, err := harnessconfiguration.Configuration().Prepare(req) if err != nil { diff --git a/apps/daemon/internal/agent/codex/options_test.go b/apps/daemon/internal/agent/codex/options_test.go index f04f77ce9..d8c04c387 100644 --- a/apps/daemon/internal/agent/codex/options_test.go +++ b/apps/daemon/internal/agent/codex/options_test.go @@ -1,6 +1,7 @@ package codex import ( + "slices" "strings" "testing" @@ -12,11 +13,8 @@ func TestBuildSessionPlan_DefaultsToBypass(t *testing.T) { if err != nil { t.Fatalf("BuildSessionPlan: %v", err) } - if plan.ApprovalPolicy.String != "never" { - t.Fatalf("default policy must bypass approvals, got %+v", plan.ApprovalPolicy) - } - if plan.Sandbox != SandboxDangerFullAcces { - t.Fatalf("default sandbox = %s, want danger-full-access", plan.Sandbox) + if !slices.Contains(plan.ExtraConfig, [2]string{"tools.experimental_request_user_input.enabled", "false"}) { + t.Fatalf("default plan must disable the native ask-the-user tool, got %+v", plan.ExtraConfig) } if plan.Cleanup == nil { t.Fatal("Cleanup must be non-nil") diff --git a/apps/daemon/internal/agent/codex/permission_profile_test.go b/apps/daemon/internal/agent/codex/permission_profile_test.go index eed3437f1..642327d50 100644 --- a/apps/daemon/internal/agent/codex/permission_profile_test.go +++ b/apps/daemon/internal/agent/codex/permission_profile_test.go @@ -16,9 +16,6 @@ func TestRuntimeUsesHostPermissions(t *testing.T) { if err != nil { t.Fatal(err) } - if plan.Sandbox != "danger-full-access" || plan.Permissions != "" || plan.ApprovalPolicy.String != "never" { - t.Fatal("Runtime must bypass inner sandbox", plan) - } if plan.Cwd != req.LocalEnvironment.WorkspaceRoot { t.Fatal("native cwd is not the bound workspace root", plan.Cwd) } diff --git a/apps/daemon/internal/agent/codex/preparation.go b/apps/daemon/internal/agent/codex/preparation.go index 402794dbf..d47acc718 100644 --- a/apps/daemon/internal/agent/codex/preparation.go +++ b/apps/daemon/internal/agent/codex/preparation.go @@ -11,20 +11,6 @@ import ( obslog "github.com/MiniMax-AI/OpenAgentCore/internal/obs/log" ) -func newSession(parent context.Context, req proto.PromptRequestPayload, out chan<- proto.Envelope, cfg sessionConfig) (*Session, error) { - if out == nil { - return nil, errors.New("codex: nil out channel") - } - runID, prompt := req.RunID, req.Input - req.RunID, req.Input = "", nil - prepared, err := newPreparation(parent, req, cfg) - if err != nil { - return nil, err - } - defer prepared.Close() - return prepared.start(parent, runID, prompt, out) -} - func newPreparation(parent context.Context, req proto.PromptRequestPayload, cfg sessionConfig) (*Prepared, error) { if req.ExecutionControls != nil && req.ExecutionControls.OutputFormat != nil { return nil, errors.New("codex: structured output is not qualified") @@ -92,7 +78,6 @@ func newPreparation(parent context.Context, req proto.PromptRequestPayload, cfg cleanup: sync.OnceFunc(plan.Cleanup), bufs: NewItemBuffers(), resolvedModel: plan.Model, - interactions: newPendingCodexInteractions(), } plan.Cleanup = s.cleanup p := &Prepared{ diff --git a/apps/daemon/internal/agent/codex/preparation_close_test.go b/apps/daemon/internal/agent/codex/preparation_close_test.go index 4abb0ce68..70cc5d769 100644 --- a/apps/daemon/internal/agent/codex/preparation_close_test.go +++ b/apps/daemon/internal/agent/codex/preparation_close_test.go @@ -43,3 +43,32 @@ func TestPreparedCloseWaitsForOwnerCleanup(t *testing.T) { } waitPreparedRelease(t, p, root) } + +func TestPreparedSessionCancellationDuringReadiness(t *testing.T) { + req, cfg, root := preparationFixture(t) + t.Setenv("OAC_TEST_PREPARATION_BLOCK", "1") + owner, cancel := context.WithCancel(t.Context()) + defer cancel() + result := make(chan error, 1) + go func() { + p, err := newPreparation(owner, req, cfg) + if p != nil { + _ = p.Close() + } + result <- err + }() + waitPreparationMethod(t, root, "environment/status") + cancel() + select { + case err := <-result: + if err == nil { + t.Fatal("cancelled readiness succeeded") + } + case <-time.After(4 * time.Second): + t.Fatal("cancelled readiness did not finish") + } + if len(preparedCatalogs(t, root)) != 0 { + t.Fatal("failed readiness leaked model catalog") + } + assertPreparationOnly(t, root) +} diff --git a/apps/daemon/internal/agent/codex/preparation_helpers_test.go b/apps/daemon/internal/agent/codex/preparation_helpers_test.go index 3c950524f..984f25b7b 100644 --- a/apps/daemon/internal/agent/codex/preparation_helpers_test.go +++ b/apps/daemon/internal/agent/codex/preparation_helpers_test.go @@ -29,7 +29,6 @@ func preparationFixture(t *testing.T) (proto.PromptRequestPayload, sessionConfig t.Setenv("OAC_TEST_PREPARATION_FRAMES", filepath.Join(root, "frames.jsonl")) t.Setenv("OAC_TEST_PREPARATION_STATUS", filepath.Join(root, "environment-status")) t.Setenv("OAC_TEST_PREPARATION_BLOCK", "") - t.Setenv("OAC_TEST_PREPARATION_OBSERVE", "") for _, key := range []string{"CODEX_EXEC_SERVER_URL", "CODEX_EXEC_SERVER_NOISE_REGISTRY_URL", "CODEX_EXEC_SERVER_NOISE_ENVIRONMENT_ID", "CODEX_EXEC_SERVER_NOISE_AUTH_TOKEN"} { t.Setenv(key, "") } @@ -257,14 +256,6 @@ func TestPreparationFakeCodexProcess(t *testing.T) { } if frame.Method == "turn/start" { _ = output.Encode(map[string]any{"jsonrpc": "2.0", "method": "turn/started", "params": map[string]any{"threadId": "fixture-native-thread", "turn": map[string]string{"id": "fixture-native-turn"}}}) - if os.Getenv("OAC_TEST_PREPARATION_OBSERVE") == "1" { - for _, raw := range []string{ - `{"method":"item/completed","params":{"threadId":"fixture-native-thread","turnId":"fixture-native-turn","item":{"type":"agentMessage","id":"message","text":"observed partial answer"}}}`, - `{"method":"thread/tokenUsage/updated","params":{"threadId":"fixture-native-thread","turnId":"fixture-native-turn","tokenUsage":{"total":{"inputTokens":30,"cachedInputTokens":4,"outputTokens":10,"reasoningOutputTokens":2,"totalTokens":40}}}}`, - } { - _ = output.Encode(json.RawMessage(raw)) - } - } } } _ = log.Close() diff --git a/apps/daemon/internal/agent/codex/preparation_router_test.go b/apps/daemon/internal/agent/codex/preparation_router_test.go index 2ba23bef6..e965460a2 100644 --- a/apps/daemon/internal/agent/codex/preparation_router_test.go +++ b/apps/daemon/internal/agent/codex/preparation_router_test.go @@ -2,7 +2,6 @@ package codex import ( "context" - "errors" "os" "path/filepath" "testing" @@ -58,9 +57,7 @@ func TestPreparationRouterRetainsActualNativeChild(t *testing.T) { req.DisableExecutionEnvironment = false req.LocalEnvironment = &proto.LocalEnvironment{ID: environment, WorkspaceDirectory: "/workspace", NetworkAccess: "enabled", CapabilitySources: &agentcapabilities.Input{}} registry := agent.NewRegistry() - registry.RegisterKind(proto.SupportedAgentKind{Kind: "codex", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{LocalEnvironment: proto.CapabilitySupported, FunctionTools: proto.CapabilitySupported})}, harnessconfiguration.Configuration(), func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, errors.New("ordinary Factory must not run") - }) + registry.RegisterKind(proto.SupportedAgentKind{Kind: "codex", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{LocalEnvironment: proto.CapabilitySupported, FunctionTools: proto.CapabilitySupported})}, harnessconfiguration.Configuration()) prepared := make(chan *Prepared, 1) registry.RegisterExecutor("codex", func(ctx context.Context, req proto.PromptRequestPayload) (agent.Executor, error) { e, err := newExecutor(ctx, req, cfg) diff --git a/apps/daemon/internal/agent/codex/prepared.go b/apps/daemon/internal/agent/codex/prepared.go index a8a57c30d..d069e9311 100644 --- a/apps/daemon/internal/agent/codex/prepared.go +++ b/apps/daemon/internal/agent/codex/prepared.go @@ -1,16 +1,8 @@ package codex -import ( - "context" - "errors" - "strings" - "sync" +import "sync" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" -) - -// Prepared owns a connected native resource until start transfers it to a Session -// or an Executor takes it. +// Prepared owns a connected native resource until an Executor takes it. // It observes owner cancellation and RPC exit, not continuous executor readiness. type Prepared struct { mu sync.Mutex @@ -18,62 +10,19 @@ type Prepared struct { plan SessionPlan resumeID string requireExistingNativeSession bool - claimed bool - closed bool started bool transferred chan struct{} } -// start consumes the preparation once. ctx bounds only this start operation; -// cancellation after return does not cancel the transferred Session. The original -// owner context remains its lifetime context. On success the Session owns out. -func (p *Prepared) start(ctx context.Context, runID string, prompt proto.MessageInput, out chan<- proto.Envelope) (*Session, error) { - if out == nil || strings.TrimSpace(runID) == "" || prompt.Validate() != nil { - return nil, errors.New("codex: start requires a run identity, prompt and output channel") - } - p.mu.Lock() - if p.claimed || p.closed { - p.mu.Unlock() - return nil, errors.New("codex: preparation is no longer available") - } - p.claimed = true - p.mu.Unlock() - - transferred := false - defer func() { - if !transferred { - _ = p.Close() - } - }() - p.mu.Lock() - defer p.mu.Unlock() - if p.closed || ctx.Err() != nil || p.session.cancelCtx.Err() != nil || !p.session.rpc.Alive() { - return nil, errors.New("codex: prepared harness is no longer available") - } - s := p.session - s.runID, s.out = runID, out - if s.observeSubagentIdentities { - s.startSubagentObservations() - } - s.registerHandlers() - p.started = true - close(p.transferred) - transferred = true - req := proto.PromptRequestPayload{RunID: runID, Input: prompt, AgentSessionID: p.resumeID, RequireExistingNativeSession: p.requireExistingNativeSession} - go s.run(p.plan, req) - return s, nil -} - // Close waits for unused teardown and plan cleanup, including another caller's -// ongoing Close. After a successful start it is inert; use -// the returned Session's cancellation path to release the transferred resource. +// ongoing Close. After an Executor takes the resource it is inert; use +// Executor.Close to release it. func (p *Prepared) Close() error { p.mu.Lock() if p.started { p.mu.Unlock() return nil } - p.closed = true p.mu.Unlock() p.session.cancelFn() err := p.session.rpc.Close() diff --git a/apps/daemon/internal/agent/codex/prepared_test.go b/apps/daemon/internal/agent/codex/prepared_test.go deleted file mode 100644 index 83d63d008..000000000 --- a/apps/daemon/internal/agent/codex/prepared_test.go +++ /dev/null @@ -1,234 +0,0 @@ -package codex - -import ( - "context" - "encoding/json" - "strings" - "sync" - "testing" - "time" - - "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" -) - -func TestPreparedSessionTransfersSameResourceOnce(t *testing.T) { - for _, resume := range []bool{false, true} { - t.Run(map[bool]string{false: "new", true: "resumed"}[resume], func(t *testing.T) { - req, cfg, root := preparationFixture(t) - if resume { - req.AgentSessionID = "fixture-native-thread" - } - p, err := newPreparation(t.Context(), req, cfg) - if err != nil { - t.Fatal(err) - } - defer p.Close() - assertPreparationOnly(t, root) - if len(preparedCatalogs(t, root)) != 1 { - t.Fatal("preparation did not retain its model catalog") - } - pid := p.session.rpc.process.Cmd.Process.Pid - cfgPreparedCwd := p.plan.Cwd - // Caller-owned data cannot revise the prepared native configuration. - req.Model = "different-model" - req.AgentSessionID = "different-thread" - copy(req.FunctionTools[0].Parameters, strings.ReplaceAll(string(req.FunctionTools[0].Parameters), "integer", "boolean")) - out := make(chan proto.Envelope, 8) - startCtx, stopStart := context.WithCancel(t.Context()) - session, err := p.start(startCtx, "actual-run", proto.TextInput("actual prompt"), out) - stopStart() - if err != nil { - t.Fatal(err) - } - defer session.Cancel(context.Background()) - if session.rpc != p.session.rpc || session.rpc.process.Cmd.Process.Pid != pid { - t.Fatal("start replaced the prepared native resource") - } - if err := p.Close(); err != nil || !session.rpc.Alive() { - t.Fatal("close cancelled transferred resource", err) - } - if again, err := p.start(t.Context(), "second", proto.TextInput("second prompt"), out); err == nil || again != nil { - t.Fatal("preparation started twice", err) - } - frames := waitPreparationMethod(t, root, "turn/start") - counts := map[string]int{} - for _, frame := range frames { - counts[frame.Method]++ - if frame.PID != pid { - t.Fatal("preparation and start used different children") - } - var params struct { - Model string `json:"model"` - ThreadID string `json:"threadId"` - DynamicTools []dynamicFunctionTool `json:"dynamicTools"` - Cwd string `json:"cwd"` - Environments json.RawMessage `json:"environments"` - } - if err := json.Unmarshal(frame.Params, ¶ms); err != nil { - t.Fatal(err) - } - if frame.Method == "thread/start" { - if params.Model != "fixture-model" || len(params.DynamicTools) != 1 || !strings.Contains(string(params.DynamicTools[0].InputSchema), "integer") { - t.Fatal("prepared configuration changed", string(frame.Params)) - } - } - if frame.Method == "thread/resume" && params.ThreadID != "fixture-native-thread" { - t.Fatal("prepared resume changed") - } - if len(params.Environments) != 0 || (frame.Method == "thread/start" && params.Cwd != cfgPreparedCwd) || p.plan.Cwd != cfgPreparedCwd { - t.Fatal("prepared environment changed") - } - } - expectedThread := "thread/start" - if resume { - expectedThread = "thread/resume" - } - if counts["initialize"] != 1 || counts["environment/status"] != 2 || counts[expectedThread] != 1 || counts["turn/start"] != 1 { - t.Fatal("unexpected native setup/start count", counts) - } - if err := session.Cancel(context.Background()); err != nil { - t.Fatal(err) - } - select { - case <-session.waitDone: - case <-time.After(4 * time.Second): - t.Fatal("started session did not release") - } - waitPreparedRelease(t, p, root) - }) - } -} - -func TestPreparedSessionAbandonmentAndFailedStart(t *testing.T) { - for _, reason := range []string{"close", "owner cancelled", "rpc exited", "start cancelled"} { - t.Run(reason, func(t *testing.T) { - req, cfg, root := preparationFixture(t) - owner, cancelOwner := context.WithCancel(t.Context()) - defer cancelOwner() - p, err := newPreparation(owner, req, cfg) - if err != nil { - t.Fatal(err) - } - defer p.Close() - startCtx := t.Context() - switch reason { - case "close": - if err := p.Close(); err != nil { - t.Fatal(err) - } - case "owner cancelled": - cancelOwner() - case "rpc exited": - if err := p.session.rpc.Close(); err != nil { - t.Fatal(err) - } - case "start cancelled": - var cancel context.CancelFunc - startCtx, cancel = context.WithCancel(t.Context()) - cancel() - } - if reason == "owner cancelled" || reason == "rpc exited" || reason == "close" { - // Release must happen without a later Start driving cleanup. - waitPreparedRelease(t, p, root) - } - out := make(chan proto.Envelope, 8) - if started, err := p.start(startCtx, "late-run", proto.TextInput("must not start"), out); err == nil || started != nil { - t.Fatal("abandoned preparation started", err) - } - waitPreparedRelease(t, p, root) - assertPreparationOnly(t, root) - if len(out) != 0 { - t.Fatal("failed preparation emitted run output") - } - count := 0 - for _, frame := range preparationFrames(t, root) { - if frame.Method == "environment/status" { - count++ - } - } - if count != 2 { - t.Fatal("failed start reconnected the native environment", count) - } - }) - } -} - -func TestPreparedSessionConcurrentStartAndClose(t *testing.T) { - req, cfg, root := preparationFixture(t) - p, err := newPreparation(t.Context(), req, cfg) - if err != nil { - t.Fatal(err) - } - defer p.Close() - begin := make(chan struct{}) - var wg sync.WaitGroup - started := make(chan *Session, 8) - for range 8 { - wg.Add(1) - go func() { - defer wg.Done() - <-begin - s, err := p.start(t.Context(), "run", proto.TextInput("prompt"), make(chan proto.Envelope, 8)) - if err == nil { - started <- s - } - }() - } - wg.Add(1) - go func() { defer wg.Done(); <-begin; _ = p.Close() }() - close(begin) - wg.Wait() - close(started) - count := 0 - for session := range started { - count++ - _ = session.Cancel(context.Background()) - select { - case <-session.waitDone: - case <-time.After(4 * time.Second): - t.Fatal("concurrent start retained a session") - } - } - if count > 1 { - t.Fatal("multiple ownership transfers", count) - } - waitPreparedRelease(t, p, root) - turns := 0 - for _, frame := range preparationFrames(t, root) { - if frame.Method == "turn/start" { - turns++ - } - } - if turns > 1 { - t.Fatal("multiple native Turns", turns) - } -} - -func TestPreparedSessionCancellationDuringReadiness(t *testing.T) { - req, cfg, root := preparationFixture(t) - t.Setenv("OAC_TEST_PREPARATION_BLOCK", "1") - owner, cancel := context.WithCancel(t.Context()) - defer cancel() - result := make(chan error, 1) - go func() { - p, err := newPreparation(owner, req, cfg) - if p != nil { - _ = p.Close() - } - result <- err - }() - waitPreparationMethod(t, root, "environment/status") - cancel() - select { - case err := <-result: - if err == nil { - t.Fatal("cancelled readiness succeeded") - } - case <-time.After(4 * time.Second): - t.Fatal("cancelled readiness did not finish") - } - if len(preparedCatalogs(t, root)) != 0 { - t.Fatal("failed readiness leaked model catalog") - } - assertPreparationOnly(t, root) -} diff --git a/apps/daemon/internal/agent/codex/protocol.go b/apps/daemon/internal/agent/codex/protocol.go index 462c10f3e..2daa4acb7 100644 --- a/apps/daemon/internal/agent/codex/protocol.go +++ b/apps/daemon/internal/agent/codex/protocol.go @@ -64,8 +64,8 @@ func (e *JsonRpcError) Error() string { return fmt.Sprintf("%d %s", e.Code, e.Me // --------------------------------------------------------------------------- // InitializeCapabilities mirrors codex-rs/app-server/src/protocol.rs. -// experimentalApi=true opts into the granular AskForApproval enum and -// the thread/* notification stream. +// experimentalApi=true opts into the experimental thread/* fields and +// notification stream. type InitializeCapabilities struct { ExperimentalAPI bool `json:"experimentalApi"` RequestAttestation bool `json:"requestAttestation"` @@ -93,83 +93,19 @@ type SkillsExtraRootsSetParams struct { ExtraRoots []string `json:"extraRoots"` } -// --------------------------------------------------------------------------- -// Approval / sandbox policies -// --------------------------------------------------------------------------- - -// GranularAskForApproval names each approval gate the codex agent can -// surface. All-false produces a fully silent run; turning any single -// field true makes the corresponding ServerRequest reach the daemon for -// human approval. -// -// JSON tags MUST stay snake_case — codex-rs deserialises this struct -// from a config TOML / RPC param with field names like sandbox_approval, -// and renaming silently breaks the wire. -type GranularAskForApproval struct { - SandboxApproval bool `json:"sandbox_approval"` - Rules bool `json:"rules"` - SkillApproval bool `json:"skill_approval"` - RequestPermissions bool `json:"request_permissions"` - MCPElicitations bool `json:"mcp_elicitations"` -} - -// AskForApproval is the discriminated union codex accepts on -// ThreadStartParams.approvalPolicy. The serialiser must emit EITHER -// {"granular": {...}} OR a bare string ("never" / "on-request" / -// "on-failure" / "untrusted"). See MarshalJSON in approval_policy.go. -type AskForApproval struct { - String string // "" when granular is set - Granular *GranularAskForApproval // nil when string is set -} - -// SandboxMode is the wire-level sandbox setting codex's v2 thread/start -// API accepts. Serializes to a kebab-case string per -// codex-rs/app-server-protocol/src/protocol/v2/shared.rs::SandboxMode. -// -// Earlier (~0.137-era) the field on ThreadStartParams was named -// `sandboxPolicy` and took a tagged object `{type: "dangerFullAccess"}`. -// 0.141.0 renamed it to `sandbox` and flattened it to one of these three -// strings. Sending the old shape no longer errors loudly — codex just -// silently falls back to read-only, which makes prompts terminate -// immediately with no agent output. Keep the constants pinned exactly -// to the kebab values upstream serializes. -type SandboxMode string - -const ( - SandboxReadOnly SandboxMode = "read-only" - SandboxWorkspaceWrite SandboxMode = "workspace-write" - SandboxDangerFullAcces SandboxMode = "danger-full-access" -) - -// SandboxPolicy is the legacy compound type kept for internal -// representation only — codex still echoes it back on some response -// shapes (turn_context inside rollout files, for example). It is NOT -// what ThreadStartParams.Sandbox takes on the wire. -type SandboxPolicy struct { - Type SandboxMode `json:"type"` - WritableRoots []string `json:"writableRoots,omitempty"` - NetworkAccess bool `json:"networkAccess,omitempty"` - ExcludeTmpdirEnvVar bool `json:"excludeTmpdirEnvVar,omitempty"` - ExcludeSlashTmp bool `json:"excludeSlashTmp,omitempty"` -} - // --------------------------------------------------------------------------- // thread/start, thread/resume, thread/list // --------------------------------------------------------------------------- type ThreadStartParams struct { - HistoryMode string `json:"historyMode,omitempty"` - Cwd string `json:"cwd"` - Model string `json:"model,omitempty"` - ModelProvider string `json:"modelProvider,omitempty"` - ApprovalPolicy AskForApproval `json:"approvalPolicy"` - // Sandbox is the v0.141+ field name; previously called sandboxPolicy - // and took a tagged-enum object. Wire format now is a kebab-case - // string: "read-only" / "workspace-write" / "danger-full-access". - // Sending the old object shape causes codex to silently default to - // read-only, which terminates the turn before the model can reply. - Sandbox SandboxMode `json:"sandbox,omitempty"` - Permissions string `json:"permissions,omitempty"` + HistoryMode string `json:"historyMode,omitempty"` + Cwd string `json:"cwd"` + Model string `json:"model,omitempty"` + ModelProvider string `json:"modelProvider,omitempty"` + ApprovalPolicy string `json:"approvalPolicy"` + // Sandbox is the kebab-case mode string. Codex silently falls back to + // read-only for the legacy sandboxPolicy object. + Sandbox string `json:"sandbox"` DeveloperInstructions string `json:"developerInstructions,omitempty"` RuntimeWorkspaceRoots []string `json:"runtimeWorkspaceRoots,omitempty"` DynamicTools []dynamicFunctionTool `json:"dynamicTools,omitempty"` @@ -187,19 +123,16 @@ type Thread struct { } type ThreadStartResult struct { - Thread Thread `json:"thread"` - Model string `json:"model,omitempty"` - ApprovalPolicy *AskForApproval `json:"approvalPolicy,omitempty"` - Sandbox *SandboxPolicy `json:"sandbox,omitempty"` + Thread Thread `json:"thread"` + Model string `json:"model,omitempty"` } type ThreadResumeParams struct { - Cwd string `json:"cwd,omitempty"` - DeveloperInstructions string `json:"developerInstructions"` - ThreadID string `json:"threadId"` - ApprovalPolicy AskForApproval `json:"approvalPolicy"` - Sandbox SandboxMode `json:"sandbox,omitempty"` - Permissions string `json:"permissions,omitempty"` + Cwd string `json:"cwd,omitempty"` + DeveloperInstructions string `json:"developerInstructions"` + ThreadID string `json:"threadId"` + ApprovalPolicy string `json:"approvalPolicy"` + Sandbox string `json:"sandbox"` } // --------------------------------------------------------------------------- @@ -381,84 +314,3 @@ type ErrorNotification struct { Error *TurnError `json:"error,omitempty"` Message string `json:"message,omitempty"` } - -// --------------------------------------------------------------------------- -// Approval and user-input ServerRequest params. Explicit requests defer their -// responses until Web or IM submits the decision, even with bypass defaults. -// --------------------------------------------------------------------------- - -type CommandExecutionRequestApprovalParams struct { - ThreadID string `json:"threadId"` - TurnID string `json:"turnId"` - ItemID string `json:"itemId"` - Command *string `json:"command,omitempty"` - Cwd *string `json:"cwd,omitempty"` - Reason *string `json:"reason,omitempty"` -} - -type FileChangeRequestApprovalParams struct { - ThreadID string `json:"threadId"` - TurnID string `json:"turnId"` - ItemID string `json:"itemId"` - Reason *string `json:"reason,omitempty"` - GrantRoot *string `json:"grantRoot,omitempty"` -} - -type PermissionsRequestApprovalParams struct { - ThreadID string `json:"threadId"` - TurnID string `json:"turnId"` - ItemID string `json:"itemId"` - Cwd string `json:"cwd"` - Reason *string `json:"reason,omitempty"` - Permissions map[string]any `json:"permissions,omitempty"` -} - -// CommandExecutionApprovalDecision is the verdict the daemon writes -// back to a Codex approval ServerRequest. "accept" / "decline" / "cancel" -// / "acceptForSession". -type CommandExecutionApprovalDecision = string - -// ApprovalDecisionResult is the result body for command-execution and -// file-change approvals. item/permissions/requestApproval uses the distinct -// PermissionsRequestApprovalResponse contract below. -type ApprovalDecisionResult struct { - Decision CommandExecutionApprovalDecision `json:"decision"` -} - -// PermissionsRequestApprovalResponse mirrors Codex app-server's dedicated -// response contract. Approving echoes the requested permission profile; -// denying returns an empty profile, which grants no additional capability. -type PermissionsRequestApprovalResponse struct { - Permissions map[string]any `json:"permissions"` - Scope string `json:"scope,omitempty"` -} - -type ToolRequestUserInputOption struct { - Label string `json:"label"` - Description string `json:"description"` -} - -type ToolRequestUserInputQuestion struct { - ID string `json:"id"` - Header string `json:"header"` - Question string `json:"question"` - Options []ToolRequestUserInputOption `json:"options,omitempty"` - IsOther bool `json:"isOther,omitempty"` - IsSecret bool `json:"isSecret,omitempty"` -} - -type ToolRequestUserInputParams struct { - ThreadID string `json:"threadId"` - TurnID string `json:"turnId"` - ItemID string `json:"itemId"` - Questions []ToolRequestUserInputQuestion `json:"questions"` - AutoResolutionMs *uint64 `json:"autoResolutionMs,omitempty"` -} - -type ToolRequestUserInputAnswer struct { - Answers []string `json:"answers"` -} - -type ToolRequestUserInputResponse struct { - Answers map[string]ToolRequestUserInputAnswer `json:"answers"` -} diff --git a/apps/daemon/internal/agent/codex/protocol_wire_test.go b/apps/daemon/internal/agent/codex/protocol_wire_test.go index d32590d7c..895888bab 100644 --- a/apps/daemon/internal/agent/codex/protocol_wire_test.go +++ b/apps/daemon/internal/agent/codex/protocol_wire_test.go @@ -6,66 +6,12 @@ import ( "testing" ) -// TestThreadStartParams_SandboxIsKebabString pins the v0.141+ wire -// format reviewer caught the hard way: codex's ThreadStartParams.sandbox -// is a kebab-case string ("read-only" / "workspace-write" / -// "danger-full-access"), NOT the legacy {"type":"dangerFullAccess"} -// tagged object on a sandboxPolicy field. -// -// Sending the legacy shape used to terminate every turn in <1s with an -// empty agent message body, because codex silently fell back to -// read-only when it didn't recognise the field name. -func TestThreadStartParams_SandboxIsKebabString(t *testing.T) { - cases := []struct { - name string - mode SandboxMode - want string - }{ - {"read-only", SandboxReadOnly, "read-only"}, - {"workspace-write", SandboxWorkspaceWrite, "workspace-write"}, - {"danger-full-access", SandboxDangerFullAcces, "danger-full-access"}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - params := ThreadStartParams{ - Cwd: "/workspace", - Model: "gpt-5.5", - ApprovalPolicy: SilentGranularPolicy(), - Sandbox: tc.mode, - } - raw, err := json.Marshal(params) - if err != nil { - t.Fatalf("marshal: %v", err) - } - body := string(raw) - if !strings.Contains(body, `"sandbox":"`+tc.want+`"`) { - t.Fatalf("wire body missing sandbox=%q: %s", tc.want, body) - } - // The legacy field name MUST be gone. Even shipping it as - // "sandboxPolicy" alongside the new field would confuse - // future-version codex servers that strict-deserialise. - if strings.Contains(body, `"sandboxPolicy"`) { - t.Fatalf("legacy sandboxPolicy field leaked into wire: %s", body) - } - // And the kebab-case value must NOT collide with the old - // camelCase enum names. - for _, oldName := range []string{`"readOnly"`, `"workspaceWrite"`, `"dangerFullAccess"`} { - if strings.Contains(body, oldName) { - t.Fatalf("legacy camelCase enum value %s leaked: %s", oldName, body) - } - } - }) - } -} - // TestThreadStartParams_OmitsEmptyOptionalFields pins that model_provider // and other optional fields don't leak null/empty values into the wire. // codex would reject malformed enum values otherwise. func TestThreadStartParams_OmitsEmptyOptionalFields(t *testing.T) { params := ThreadStartParams{ - Cwd: "/workspace", - ApprovalPolicy: SilentGranularPolicy(), - Sandbox: SandboxDangerFullAcces, + Cwd: "/workspace", // Model, ModelProvider, DeveloperInstructions deliberately empty } raw, _ := json.Marshal(params) @@ -88,11 +34,9 @@ func TestThreadStartParams_OmitsEmptyOptionalFields(t *testing.T) { // override. Sending it on both paths is belt + suspenders. func TestThreadStartParams_ModelProviderIsCamelCaseField(t *testing.T) { params := ThreadStartParams{ - Cwd: "/workspace", - Model: "gpt-5.5", - ModelProvider: "oac", - ApprovalPolicy: SilentGranularPolicy(), - Sandbox: SandboxDangerFullAcces, + Cwd: "/workspace", + Model: "gpt-5.5", + ModelProvider: "oac", } raw, _ := json.Marshal(params) body := string(raw) diff --git a/apps/daemon/internal/agent/codex/recovery_test.go b/apps/daemon/internal/agent/codex/recovery_test.go index 97eff6936..4c9b158db 100644 --- a/apps/daemon/internal/agent/codex/recovery_test.go +++ b/apps/daemon/internal/agent/codex/recovery_test.go @@ -161,26 +161,20 @@ func TestPreparedRecoveryCannotStartWithoutExistingHistory(t *testing.T) { req.DisableExecutionEnvironment = false req.LocalEnvironment = &proto.LocalEnvironment{ID: uuid.NewString(), WorkspaceDirectory: "/workspace", NetworkAccess: "enabled", CapabilitySources: &agentcapabilities.Input{}, WorkspaceRoot: cwd} } - p, err := newPreparation(t.Context(), req, cfg) + e, err := testExecutor(t, "complete", req, cfg) if err != nil { t.Fatal(err) } - defer p.Close() - if p.plan.Cwd != cwd { - t.Fatalf("cwd = %q, want %q", p.plan.Cwd, cwd) + if e.prepared.plan.Cwd != cwd { + t.Fatalf("cwd = %q, want %q", e.prepared.plan.Cwd, cwd) } assertPreparationOnly(t, root) out := make(chan proto.Envelope, 16) - session, err := p.start(t.Context(), "recovery-run", proto.TextInput("continue"), out) - if err != nil { - t.Fatal(err) - } - defer session.Cancel(context.Background()) - select { - case <-p.session.waitDone: - case <-time.After(4 * time.Second): - t.Fatal("recovery did not terminate") + turn, err := e.StartTurn(t.Context(), "recovery-run", proto.TextInput("continue"), out) + if err == nil { + t.Fatal("missing history started a Turn") } + settledFrames(t, turn, out) found := false for _, frame := range preparationFrames(t, root) { if frame.Method == "thread/list" { diff --git a/apps/daemon/internal/agent/codex/rpc_close_test.go b/apps/daemon/internal/agent/codex/rpc_close_test.go index fbd9c8175..036b1c898 100644 --- a/apps/daemon/internal/agent/codex/rpc_close_test.go +++ b/apps/daemon/internal/agent/codex/rpc_close_test.go @@ -80,7 +80,7 @@ func TestJSONRPCClientCloseCanRetryUnreapedChild(t *testing.T) { closeConcurrently(true) ownerCtx, cancelOwner := context.WithCancel(t.Context()) s := &Session{rpc: client, cancelCtx: ownerCtx, cancelFn: cancelOwner, - cfg: defaultSessionConfig(), interactions: newPendingCodexInteractions(), bufs: NewItemBuffers()} + cfg: defaultSessionConfig(), bufs: NewItemBuffers()} ctx, cancelWait := context.WithTimeout(t.Context(), 20*time.Millisecond) defer cancelWait() if err := s.Cancel(ctx); !errors.Is(err, context.DeadlineExceeded) { diff --git a/apps/daemon/internal/agent/codex/server_requests.go b/apps/daemon/internal/agent/codex/server_requests.go deleted file mode 100644 index 5e77e3c9d..000000000 --- a/apps/daemon/internal/agent/codex/server_requests.go +++ /dev/null @@ -1,362 +0,0 @@ -package codex - -import ( - "crypto/rand" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "strings" - "sync" - "time" - - "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" -) - -type pendingCodexPermission struct { - rpcID any - kind codexPermissionKind - permissions map[string]any - timeout time.Duration - timer *time.Timer -} - -type pendingCodexAsk struct { - rpcID any - questionIDs []string - timeout time.Duration - timer *time.Timer -} - -const codexInteractionTimeout = 10 * time.Minute - -type codexPermissionKind uint8 - -const ( - codexDecisionApproval codexPermissionKind = iota - codexPermissionsApproval - codexMCPApproval -) - -type pendingCodexInteractions struct { - mu sync.Mutex - permissions map[string]pendingCodexPermission - asks map[string]pendingCodexAsk -} - -func newPendingCodexInteractions() *pendingCodexInteractions { - return &pendingCodexInteractions{ - permissions: make(map[string]pendingCodexPermission), - asks: make(map[string]pendingCodexAsk), - } -} - -func codexInteractionID(prefix string) string { - var bytes [8]byte - if _, err := rand.Read(bytes[:]); err == nil { - return prefix + "_" + hex.EncodeToString(bytes[:]) - } - return fmt.Sprintf("%s_fallback", prefix) -} - -func (s *Session) handleCodexCommandApproval(raw json.RawMessage, rpcID any) (any, error) { - var params CommandExecutionRequestApprovalParams - if err := json.Unmarshal(raw, ¶ms); err != nil { - return nil, fmt.Errorf("decode command approval: %w", err) - } - title := stringPointer(params.Command) - if title == "" { - title = "Run command" - } - return s.deferCodexPermission(rpcID, codexDecisionApproval, "command_execution", title, stringPointer(params.Reason), raw, nil) -} - -func (s *Session) handleCodexFileApproval(raw json.RawMessage, rpcID any) (any, error) { - var params FileChangeRequestApprovalParams - if err := json.Unmarshal(raw, ¶ms); err != nil { - return nil, fmt.Errorf("decode file approval: %w", err) - } - title := stringPointer(params.GrantRoot) - if title == "" { - title = "Apply file changes" - } - return s.deferCodexPermission(rpcID, codexDecisionApproval, "file_change", title, stringPointer(params.Reason), raw, nil) -} - -func (s *Session) handleCodexPermissionsApproval(raw json.RawMessage, rpcID any) (any, error) { - var params PermissionsRequestApprovalParams - if err := json.Unmarshal(raw, ¶ms); err != nil { - return nil, fmt.Errorf("decode permissions approval: %w", err) - } - title := strings.TrimSpace(params.Cwd) - if title == "" { - title = "Grant additional permissions" - } - return s.deferCodexPermission(rpcID, codexPermissionsApproval, "permission_request", title, stringPointer(params.Reason), raw, params.Permissions) -} - -func (s *Session) deferCodexPermission(rpcID any, kind codexPermissionKind, tool, title, detail string, raw json.RawMessage, permissions map[string]any) (any, error) { - requestID := codexInteractionID("perm") - payload := map[string]any{} - _ = json.Unmarshal(raw, &payload) - pending := pendingCodexPermission{ - rpcID: rpcID, kind: kind, permissions: permissions, - timeout: codexInteractionTimeout, - } - s.interactions.mu.Lock() - // Start the timer while holding the table lock. Even a future very short - // timeout then blocks in expireCodexPermission until the entry is visible, - // rather than firing before insertion and leaving an immortal request. - pending.timer = time.AfterFunc(pending.timeout, func() { s.expireCodexPermission(requestID) }) - s.interactions.permissions[requestID] = pending - s.interactions.mu.Unlock() - env, err := proto.NewEnvelope(proto.TypePermissionRequest, s.runID, proto.PermissionRequestPayload{ - RequestID: requestID, Tool: tool, Title: title, Detail: detail, Payload: payload, - }) - if err != nil { - s.interactions.mu.Lock() - pending, ok := s.interactions.permissions[requestID] - if ok { - delete(s.interactions.permissions, requestID) - } - s.interactions.mu.Unlock() - if ok && pending.timer != nil { - pending.timer.Stop() - } - return nil, err - } - s.trySend(env) - return DeferReply, nil -} - -func (s *Session) handleCodexUserInput(raw json.RawMessage, rpcID any) (any, error) { - var params ToolRequestUserInputParams - if err := json.Unmarshal(raw, ¶ms); err != nil { - return nil, fmt.Errorf("decode requestUserInput: %w", err) - } - if len(params.Questions) == 0 { - return nil, errors.New("requestUserInput contains no questions") - } - askID := codexInteractionID("ask") - questions := make([]proto.PromptForUserChoiceQuestion, 0, len(params.Questions)) - questionIDs := make([]string, 0, len(params.Questions)) - for index, question := range params.Questions { - options := make([]proto.PromptForUserChoiceOption, 0, len(question.Options)) - for _, option := range question.Options { - options = append(options, proto.PromptForUserChoiceOption{Label: option.Label, Description: option.Description}) - } - header := strings.TrimSpace(question.Header) - if header == "" { - header = fmt.Sprintf("q%d", index) - } - questionID := strings.TrimSpace(question.ID) - if questionID == "" { - questionID = header - } - questionIDs = append(questionIDs, questionID) - questions = append(questions, proto.PromptForUserChoiceQuestion{ - ID: questionID, Header: header, Question: question.Question, Options: options, - IsOther: question.IsOther, IsSecret: question.IsSecret, - }) - } - timeout := codexInteractionTimeout - if params.AutoResolutionMs != nil && *params.AutoResolutionMs > 0 { - const maxMillis = uint64(^uint64(0)>>1) / uint64(time.Millisecond) - if *params.AutoResolutionMs <= maxMillis { - timeout = time.Duration(*params.AutoResolutionMs) * time.Millisecond - } - } - pending := pendingCodexAsk{rpcID: rpcID, questionIDs: questionIDs, timeout: timeout} - s.interactions.mu.Lock() - pending.timer = time.AfterFunc(pending.timeout, func() { s.expireCodexAsk(askID) }) - s.interactions.asks[askID] = pending - s.interactions.mu.Unlock() - env, err := proto.NewEnvelope(proto.TypePromptForUserChoice, s.runID, proto.PromptForUserChoicePayload{ - AskID: askID, Questions: questions, AutoResolutionMs: params.AutoResolutionMs, - }) - if err != nil { - s.interactions.mu.Lock() - pending, ok := s.interactions.asks[askID] - if ok { - delete(s.interactions.asks, askID) - } - s.interactions.mu.Unlock() - if ok && pending.timer != nil { - pending.timer.Stop() - } - return nil, err - } - s.trySend(env) - return DeferReply, nil -} - -func (s *Session) submitCodexPermission(requestID string, decision proto.PermissionDecisionPayload) error { - if !s.beginOperation() { - return agent.ErrSteeringInactive - } - defer s.endOperation() - s.interactions.mu.Lock() - pending, ok := s.interactions.permissions[requestID] - if ok { - delete(s.interactions.permissions, requestID) - } - s.interactions.mu.Unlock() - if !ok { - return agent.ErrUnknownPermission - } - if pending.timer != nil { - pending.timer.Stop() - } - if err := s.sendCodexPermissionReply(pending, decision.Approved); err != nil { - s.interactions.mu.Lock() - pending.timer = time.AfterFunc(pending.timeout, func() { s.expireCodexPermission(requestID) }) - s.interactions.permissions[requestID] = pending - s.interactions.mu.Unlock() - return err - } - return nil -} - -func (s *Session) sendCodexPermissionReply(pending pendingCodexPermission, approved bool) error { - if pending.kind == codexPermissionsApproval { - permissions := map[string]any{} - if approved && pending.permissions != nil { - permissions = pending.permissions - } - return s.rpc.SendServerReply(pending.rpcID, PermissionsRequestApprovalResponse{ - Permissions: permissions, - Scope: "turn", - }) - } - value := "decline" - if approved { - value = "accept" - } - if pending.kind == codexMCPApproval { - var content map[string]any - if approved { - content = map[string]any{} - } - return s.rpc.SendServerReply(pending.rpcID, mcpElicitationResponse{Action: value, Content: content}) - } - return s.rpc.SendServerReply(pending.rpcID, ApprovalDecisionResult{Decision: value}) -} - -func (s *Session) submitCodexUserInput(askID string, decision proto.PromptForUserChoiceDecisionPayload) error { - if !s.beginOperation() { - return agent.ErrSteeringInactive - } - defer s.endOperation() - s.interactions.mu.Lock() - pending, ok := s.interactions.asks[askID] - var answers map[string][]string - if ok { - var err error - answers, err = decision.AnswersFor(pending.questionIDs) - if err != nil { - s.interactions.mu.Unlock() - return err - } - delete(s.interactions.asks, askID) - } - s.interactions.mu.Unlock() - if !ok { - return agent.ErrUnknownAsk - } - if pending.timer != nil { - pending.timer.Stop() - } - if decision.Cancelled { - reason := strings.TrimSpace(decision.Reason) - if reason == "" { - reason = "user cancelled input request" - } - if err := s.rpc.SendServerError(pending.rpcID, -32001, reason, nil); err != nil { - s.interactions.mu.Lock() - pending.timer = time.AfterFunc(pending.timeout, func() { s.expireCodexAsk(askID) }) - s.interactions.asks[askID] = pending - s.interactions.mu.Unlock() - return err - } - return nil - } - result := ToolRequestUserInputResponse{Answers: make(map[string]ToolRequestUserInputAnswer, len(pending.questionIDs))} - for _, questionID := range pending.questionIDs { - values := answers[questionID] - if values == nil { - values = []string{} - } - result.Answers[questionID] = ToolRequestUserInputAnswer{Answers: values} - } - if err := s.rpc.SendServerReply(pending.rpcID, result); err != nil { - s.interactions.mu.Lock() - pending.timer = time.AfterFunc(pending.timeout, func() { s.expireCodexAsk(askID) }) - s.interactions.asks[askID] = pending - s.interactions.mu.Unlock() - return err - } - return nil -} - -func (s *Session) expireCodexPermission(requestID string) { - if !s.beginOperation() { - return - } - defer s.endOperation() - s.interactions.mu.Lock() - pending, ok := s.interactions.permissions[requestID] - if ok { - delete(s.interactions.permissions, requestID) - } - s.interactions.mu.Unlock() - if ok { - if pending.timer != nil { - pending.timer.Stop() - } - _ = s.sendCodexPermissionReply(pending, false) - } -} - -func (s *Session) expireCodexAsk(askID string) { - if !s.beginOperation() { - return - } - defer s.endOperation() - s.interactions.mu.Lock() - pending, ok := s.interactions.asks[askID] - if ok { - delete(s.interactions.asks, askID) - } - s.interactions.mu.Unlock() - if ok { - if pending.timer != nil { - pending.timer.Stop() - } - _ = s.rpc.SendServerError(pending.rpcID, -32001, "input request timed out", nil) - } -} - -func (s *Session) stopCodexInteractionTimers() { - s.interactions.mu.Lock() - defer s.interactions.mu.Unlock() - for id, pending := range s.interactions.permissions { - if pending.timer != nil { - pending.timer.Stop() - } - delete(s.interactions.permissions, id) - } - for id, pending := range s.interactions.asks { - if pending.timer != nil { - pending.timer.Stop() - } - delete(s.interactions.asks, id) - } -} - -func stringPointer(value *string) string { - if value == nil { - return "" - } - return strings.TrimSpace(*value) -} diff --git a/apps/daemon/internal/agent/codex/server_requests_mcp.go b/apps/daemon/internal/agent/codex/server_requests_mcp.go deleted file mode 100644 index 4cb4de00e..000000000 --- a/apps/daemon/internal/agent/codex/server_requests_mcp.go +++ /dev/null @@ -1,38 +0,0 @@ -package codex - -import ( - "encoding/json" - "errors" - "fmt" - "strings" -) - -type mcpElicitationParams struct { - ServerName string `json:"serverName"` - Mode string `json:"mode"` - Message string `json:"message"` - Schema struct { - Type string `json:"type"` - Properties map[string]json.RawMessage `json:"properties"` - Required []string `json:"required"` - } `json:"requestedSchema"` -} - -type mcpElicitationResponse struct { - Action string `json:"action"` - Content map[string]any `json:"content"` -} - -func (s *Session) handleCodexMCPElicitation(raw json.RawMessage, rpcID any) (any, error) { - var params mcpElicitationParams - if err := json.Unmarshal(raw, ¶ms); err != nil { - return nil, fmt.Errorf("decode MCP elicitation: %w", err) - } - if params.Mode != "form" || params.Schema.Type != "object" || params.Schema.Properties == nil || len(params.Schema.Properties) != 0 || len(params.Schema.Required) != 0 { - return nil, errors.New("MCP elicitation requires unsupported input; only empty confirmation forms are supported") - } - if strings.TrimSpace(params.ServerName) == "" || strings.TrimSpace(params.Message) == "" { - return nil, errors.New("MCP confirmation requires a server name and message") - } - return s.deferCodexPermission(rpcID, codexMCPApproval, "mcp:"+params.ServerName, params.Message, "", raw, nil) -} diff --git a/apps/daemon/internal/agent/codex/server_requests_mcp_test.go b/apps/daemon/internal/agent/codex/server_requests_mcp_test.go deleted file mode 100644 index a72ec3d9e..000000000 --- a/apps/daemon/internal/agent/codex/server_requests_mcp_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package codex - -import ( - "encoding/json" - "errors" - "testing" - "time" - - "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" -) - -func TestCodexMCPConfirmationUsesPermissionLifecycle(t *testing.T) { - for _, action := range []string{"approve", "deny", "expire"} { - t.Run(action, func(t *testing.T) { - tc, srv, cleanup := NewTestClient() - defer cleanup() - s, out := newInteractionTestSession(tc.JSONRPCClient) - defer s.stopCodexInteractionTimers() - params := json.RawMessage(`{"threadId":"thread-1","turnId":"turn-1","serverName":"qa-service-desk","mode":"form","message":"Allow get_test_ticket?","requestedSchema":{"type":"object","properties":{}},"_meta":{"codex_approval_kind":"mcp_tool_call","persist":["session","always"]}}`) - if err := SendServerRequest(srv, "rpc-mcp", "mcpServer/elicitation/request", params); err != nil { - t.Fatal(err) - } - var env proto.Envelope - select { - case env = <-out: - case <-time.After(2 * time.Second): - t.Fatal("MCP confirmation was not surfaced") - } - var request proto.PermissionRequestPayload - if err := env.DecodePayload(&request); err != nil { - t.Fatal(err) - } - if env.Type != proto.TypePermissionRequest || env.ID != "run-test" || request.RequestID == "" || request.Tool != "mcp:qa-service-desk" || request.Title != "Allow get_test_ticket?" { - t.Fatalf("incorrect confirmation: %+v / %+v", env, request) - } - done := make(chan error, 1) - go func() { - if action == "expire" { - s.expireCodexPermission(request.RequestID) - done <- nil - return - } - done <- s.SubmitPermission(t.Context(), request.RequestID, proto.PermissionDecisionPayload{Approved: action == "approve"}) - }() - var reply struct { - ID string `json:"id"` - Result map[string]json.RawMessage `json:"result"` - } - decodeCodexReply(t, srv, &reply) - if err := <-done; err != nil { - t.Fatal(err) - } - wantAction, wantContent := `"decline"`, `null` - if action == "approve" { - wantAction, wantContent = `"accept"`, `{}` - } - if reply.ID != "rpc-mcp" || len(reply.Result) != 2 || string(reply.Result["action"]) != wantAction || string(reply.Result["content"]) != wantContent { - t.Fatalf("incorrect MCP response: %+v", reply) - } - if err := s.SubmitPermission(t.Context(), request.RequestID, proto.PermissionDecisionPayload{Approved: true}); !errors.Is(err, agent.ErrUnknownPermission) { - t.Fatalf("resolved confirmation accepted again: %v", err) - } - }) - } -} - -func TestCodexMCPElicitationRejectsUnsupportedInputWithoutApproval(t *testing.T) { - for _, params := range []string{ - `{"serverName":"qa","mode":"form","message":"Enter name","requestedSchema":{"type":"object","properties":{"name":{"type":"string"}}}}`, - `{"serverName":"qa","mode":"url","message":"Sign in","url":"https://example.test/login","elicitationId":"login"}`, - `{"serverName":"qa","mode":"form","message":"Confirm","requestedSchema":{"type":"object","properties":{},"required":["name"]}}`, - `{"serverName":"qa","mode":"form","message":"Confirm","requestedSchema":{"type":"object"}}`, - `{"mode":"form","requestedSchema":{"type":"object","properties":{}}}`, - } { - t.Run(params, func(t *testing.T) { - tc, srv, cleanup := NewTestClient() - defer cleanup() - s, out := newInteractionTestSession(tc.JSONRPCClient) - defer s.stopCodexInteractionTimers() - if err := SendServerRequest(srv, "rpc-unsupported", "mcpServer/elicitation/request", json.RawMessage(params)); err != nil { - t.Fatal(err) - } - var reply struct { - Error *struct { - Code int `json:"code"` - } `json:"error"` - } - decodeCodexReply(t, srv, &reply) - if reply.Error == nil || reply.Error.Code != -32603 { - t.Fatalf("unsupported input was not rejected: %+v", reply) - } - select { - case env := <-out: - t.Fatalf("unsupported input became an approval: %+v", env) - default: - } - }) - } -} diff --git a/apps/daemon/internal/agent/codex/server_requests_test.go b/apps/daemon/internal/agent/codex/server_requests_test.go deleted file mode 100644 index 4bae35ed0..000000000 --- a/apps/daemon/internal/agent/codex/server_requests_test.go +++ /dev/null @@ -1,357 +0,0 @@ -package codex - -import ( - "context" - "encoding/json" - "errors" - "testing" - "time" - - "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" -) - -func newInteractionTestSession(rpc *JSONRPCClient) (*Session, <-chan proto.Envelope) { - out := make(chan proto.Envelope, 1) - ctx, cancel := context.WithCancel(context.Background()) - s := &Session{ - runID: "run-test", - out: out, - rpc: rpc, - cancelCtx: ctx, - cancelFn: cancel, - interactions: newPendingCodexInteractions(), - } - s.registerHandlers() - return s, out -} - -func TestCodexPermissionRequestWaitsForHumanDecision(t *testing.T) { - tc, srv, cleanup := NewTestClient() - defer cleanup() - s, out := newInteractionTestSession(tc.JSONRPCClient) - - command := "go test ./..." - reason := "requires process execution" - if err := SendServerRequest(srv, "rpc-perm-1", "item/commandExecution/requestApproval", CommandExecutionRequestApprovalParams{ - ThreadID: "thread-1", TurnID: "turn-1", ItemID: "item-1", Command: &command, Reason: &reason, - }); err != nil { - t.Fatalf("send server request: %v", err) - } - - var env proto.Envelope - select { - case env = <-out: - case <-time.After(2 * time.Second): - t.Fatal("permission request was not surfaced to OpenAgentCore") - } - if env.Type != proto.TypePermissionRequest { - t.Fatalf("envelope type = %q, want %q", env.Type, proto.TypePermissionRequest) - } - var request proto.PermissionRequestPayload - if err := env.DecodePayload(&request); err != nil { - t.Fatalf("decode permission payload: %v", err) - } - if env.ID != "run-test" || request.RequestID == "" { - t.Fatalf("permission correlation = env.ID %q request.ID %q", env.ID, request.RequestID) - } - if request.Tool != "command_execution" || request.Title != command || request.Detail != reason { - t.Fatalf("permission payload = %+v", request) - } - - submitDone := make(chan error, 1) - go func() { - submitDone <- s.SubmitPermission(context.Background(), request.RequestID, proto.PermissionDecisionPayload{Approved: true}) - }() - reply := readCodexServerReply(t, srv) - if err := <-submitDone; err != nil { - t.Fatalf("submit permission: %v", err) - } - if reply.ID != "rpc-perm-1" || reply.Result.Decision != "accept" { - t.Fatalf("reply = %+v", reply) - } -} - -func TestCodexPermissionsApprovalUsesDedicatedResponseSchema(t *testing.T) { - for _, tt := range []struct { - name string - approved bool - wantGrants bool - }{ - {name: "approve echoes requested grants", approved: true, wantGrants: true}, - {name: "deny grants nothing", approved: false, wantGrants: false}, - } { - t.Run(tt.name, func(t *testing.T) { - tc, srv, cleanup := NewTestClient() - defer cleanup() - s, out := newInteractionTestSession(tc.JSONRPCClient) - requested := map[string]any{ - "network": map[string]any{"enabled": true}, - "fileSystem": map[string]any{"write": []any{"/workspace"}}, - } - if err := SendServerRequest(srv, "rpc-permissions", "item/permissions/requestApproval", PermissionsRequestApprovalParams{ - Cwd: "/workspace", Permissions: requested, - }); err != nil { - t.Fatalf("send permissions request: %v", err) - } - env := <-out - var request proto.PermissionRequestPayload - if err := env.DecodePayload(&request); err != nil { - t.Fatalf("decode permission payload: %v", err) - } - done := make(chan error, 1) - go func() { - done <- s.SubmitPermission(context.Background(), request.RequestID, proto.PermissionDecisionPayload{Approved: tt.approved}) - }() - var reply struct { - ID string `json:"id"` - Result struct { - Permissions map[string]any `json:"permissions"` - Scope string `json:"scope"` - Decision string `json:"decision"` - } `json:"result"` - } - decodeCodexReply(t, srv, &reply) - if err := <-done; err != nil { - t.Fatalf("submit permissions: %v", err) - } - if reply.Result.Scope != "turn" || reply.Result.Decision != "" { - t.Fatalf("permissions reply = %+v", reply.Result) - } - _, granted := reply.Result.Permissions["network"] - if granted != tt.wantGrants { - t.Fatalf("permissions = %+v, want grants=%v", reply.Result.Permissions, tt.wantGrants) - } - }) - } -} - -func TestCodexUserInputMapsAnswersByQuestionID(t *testing.T) { - tc, srv, cleanup := NewTestClient() - defer cleanup() - s, out := newInteractionTestSession(tc.JSONRPCClient) - - autoResolutionMs := uint64(120_000) - if err := SendServerRequest(srv, "rpc-ask-1", "item/tool/requestUserInput", ToolRequestUserInputParams{ - ThreadID: "thread-1", TurnID: "turn-1", ItemID: "item-ask", - AutoResolutionMs: &autoResolutionMs, - Questions: []ToolRequestUserInputQuestion{ - {ID: "deployment", Header: "Deploy", Question: "Where?", IsOther: true, Options: []ToolRequestUserInputOption{{Label: "Staging"}, {Label: "Production"}}}, - {ID: "checks", Header: "Checks", Question: "Which checks?", IsSecret: true, Options: []ToolRequestUserInputOption{{Label: "Unit"}, {Label: "E2E"}}}, - }, - }); err != nil { - t.Fatalf("send server request: %v", err) - } - - var env proto.Envelope - select { - case env = <-out: - case <-time.After(2 * time.Second): - t.Fatal("requestUserInput was not surfaced to OpenAgentCore") - } - if env.Type != proto.TypePromptForUserChoice { - t.Fatalf("envelope type = %q, want %q", env.Type, proto.TypePromptForUserChoice) - } - var request proto.PromptForUserChoicePayload - if err := env.DecodePayload(&request); err != nil { - t.Fatalf("decode user input payload: %v", err) - } - if len(request.Questions) != 2 || request.Questions[0].ID != "deployment" || request.Questions[1].ID != "checks" || request.Questions[0].Header != "Deploy" { - t.Fatalf("user input payload = %+v", request) - } - if !request.Questions[0].IsOther || !request.Questions[1].IsSecret || request.AutoResolutionMs == nil || *request.AutoResolutionMs != autoResolutionMs { - t.Fatalf("user input metadata = %+v", request) - } - s.interactions.mu.Lock() - pending := s.interactions.asks[request.AskID] - s.interactions.mu.Unlock() - if pending.timeout != 2*time.Minute { - t.Fatalf("ask timeout = %v, want 2m", pending.timeout) - } - - if err := s.SubmitPromptForUserChoice(context.Background(), request.AskID, proto.PromptForUserChoiceDecisionPayload{ - QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "foreign", Answers: []string{"yes"}}}, - }); err == nil { - t.Fatal("accepted a foreign question ID") - } - // Invalid identity must leave the original native request pending. - submitDone := make(chan error, 1) - go func() { - submitDone <- s.SubmitPromptForUserChoice(context.Background(), request.AskID, proto.PromptForUserChoiceDecisionPayload{ - QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{ - {QuestionID: "checks", Answers: []string{"Unit", "E2E"}}, - {QuestionID: "deployment", Answers: []string{"Staging"}}, - }, - }) - }() - - var reply struct { - ID string `json:"id"` - Result ToolRequestUserInputResponse `json:"result"` - } - decodeCodexReply(t, srv, &reply) - if err := <-submitDone; err != nil { - t.Fatalf("submit user input: %v", err) - } - if reply.ID != "rpc-ask-1" { - t.Fatalf("reply id = %q", reply.ID) - } - if got := reply.Result.Answers["deployment"].Answers; len(got) != 1 || got[0] != "Staging" { - t.Fatalf("deployment answers = %v", got) - } - if got := reply.Result.Answers["checks"].Answers; len(got) != 2 || got[0] != "Unit" || got[1] != "E2E" { - t.Fatalf("checks answers = %v", got) - } -} - -func TestCodexUserInputCancellationReturnsErrorInsteadOfEmptyAnswers(t *testing.T) { - tc, srv, cleanup := NewTestClient() - defer cleanup() - s, out := newInteractionTestSession(tc.JSONRPCClient) - - if err := SendServerRequest(srv, "rpc-ask-cancel", "item/tool/requestUserInput", ToolRequestUserInputParams{ - Questions: []ToolRequestUserInputQuestion{{ID: "confirm", Header: "Confirm", Question: "Continue?"}}, - }); err != nil { - t.Fatalf("send server request: %v", err) - } - var env proto.Envelope - select { - case env = <-out: - case <-time.After(2 * time.Second): - t.Fatal("requestUserInput was not surfaced to OpenAgentCore") - } - var request proto.PromptForUserChoicePayload - if err := env.DecodePayload(&request); err != nil { - t.Fatalf("decode user input payload: %v", err) - } - submitDone := make(chan error, 1) - go func() { - submitDone <- s.SubmitPromptForUserChoice(context.Background(), request.AskID, proto.PromptForUserChoiceDecisionPayload{ - Cancelled: true, Reason: "cancelled by user", - }) - }() - - var reply struct { - ID string `json:"id"` - Error *struct { - Code int `json:"code"` - Message string `json:"message"` - } `json:"error"` - } - decodeCodexReply(t, srv, &reply) - if err := <-submitDone; err != nil { - t.Fatalf("cancel user input: %v", err) - } - if reply.ID != "rpc-ask-cancel" || reply.Error == nil || reply.Error.Code != -32001 { - t.Fatalf("cancel reply = %+v", reply) - } -} - -func TestCodexInteractionExpiryUnblocksRuntime(t *testing.T) { - t.Run("permission declines", func(t *testing.T) { - tc, srv, cleanup := NewTestClient() - defer cleanup() - s, out := newInteractionTestSession(tc.JSONRPCClient) - command := "deploy" - if err := SendServerRequest(srv, "rpc-perm-timeout", "item/commandExecution/requestApproval", CommandExecutionRequestApprovalParams{Command: &command}); err != nil { - t.Fatalf("send permission request: %v", err) - } - env := <-out - var request proto.PermissionRequestPayload - if err := env.DecodePayload(&request); err != nil { - t.Fatalf("decode permission payload: %v", err) - } - expireDone := make(chan struct{}) - go func() { s.expireCodexPermission(request.RequestID); close(expireDone) }() - reply := readCodexServerReply(t, srv) - <-expireDone - if reply.Result.Decision != "decline" { - t.Fatalf("expiry decision = %q", reply.Result.Decision) - } - if err := s.SubmitPermission(context.Background(), request.RequestID, proto.PermissionDecisionPayload{Approved: true}); !errors.Is(err, agent.ErrUnknownPermission) { - t.Fatalf("late permission error = %v, want ErrUnknownPermission", err) - } - }) - - t.Run("permission profile grants nothing", func(t *testing.T) { - tc, srv, cleanup := NewTestClient() - defer cleanup() - s, out := newInteractionTestSession(tc.JSONRPCClient) - if err := SendServerRequest(srv, "rpc-profile-timeout", "item/permissions/requestApproval", PermissionsRequestApprovalParams{ - Permissions: map[string]any{"network": map[string]any{"enabled": true}}, - }); err != nil { - t.Fatalf("send permission profile request: %v", err) - } - env := <-out - var request proto.PermissionRequestPayload - if err := env.DecodePayload(&request); err != nil { - t.Fatalf("decode permission payload: %v", err) - } - done := make(chan struct{}) - go func() { s.expireCodexPermission(request.RequestID); close(done) }() - var reply struct { - Result PermissionsRequestApprovalResponse `json:"result"` - } - decodeCodexReply(t, srv, &reply) - <-done - if len(reply.Result.Permissions) != 0 || reply.Result.Scope != "turn" { - t.Fatalf("expiry permissions response = %+v", reply.Result) - } - }) - - t.Run("user input returns timeout error", func(t *testing.T) { - tc, srv, cleanup := NewTestClient() - defer cleanup() - s, out := newInteractionTestSession(tc.JSONRPCClient) - if err := SendServerRequest(srv, "rpc-ask-timeout", "item/tool/requestUserInput", ToolRequestUserInputParams{ - Questions: []ToolRequestUserInputQuestion{{ID: "q1", Header: "Confirm", Question: "Continue?"}}, - }); err != nil { - t.Fatalf("send input request: %v", err) - } - var request proto.PromptForUserChoicePayload - if err := (<-out).DecodePayload(&request); err != nil { - t.Fatalf("decode input request: %v", err) - } - expireDone := make(chan struct{}) - go func() { s.expireCodexAsk(request.AskID); close(expireDone) }() - var reply struct { - Error *struct { - Code int `json:"code"` - } `json:"error"` - } - decodeCodexReply(t, srv, &reply) - <-expireDone - if reply.Error == nil || reply.Error.Code != -32001 { - t.Fatalf("expiry reply = %+v", reply) - } - if err := s.SubmitPromptForUserChoice(context.Background(), request.AskID, proto.PromptForUserChoiceDecisionPayload{QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "q0", Answers: []string{"yes"}}}}); !errors.Is(err, agent.ErrUnknownAsk) { - t.Fatalf("late input error = %v, want ErrUnknownAsk", err) - } - }) -} - -type approvalReply struct { - ID string `json:"id"` - Result ApprovalDecisionResult `json:"result"` -} - -func readCodexServerReply(t *testing.T, srv ServerSide) approvalReply { - t.Helper() - var reply approvalReply - decodeCodexReply(t, srv, &reply) - return reply -} - -func decodeCodexReply(t *testing.T, srv ServerSide, target any) { - t.Helper() - done := make(chan error, 1) - go func() { done <- json.NewDecoder(srv.FromClient).Decode(target) }() - select { - case err := <-done: - if err != nil { - t.Fatalf("decode Codex reply: %v", err) - } - case <-time.After(2 * time.Second): - t.Fatal("Codex reply timed out") - } -} diff --git a/apps/daemon/internal/agent/codex/session.go b/apps/daemon/internal/agent/codex/session.go index 27ab44063..1a5db1094 100644 --- a/apps/daemon/internal/agent/codex/session.go +++ b/apps/daemon/internal/agent/codex/session.go @@ -20,8 +20,8 @@ import ( // adapter safety net. const terminalSendTimeout = 2 * time.Second -// sessionConfig is the cross-cutting knob bag — production callers go -// through Factory which uses defaults. +// sessionConfig is the cross-cutting knob bag; production callers use +// defaultSessionConfig. type sessionConfig struct { // codexBinary is the trusted install on this host. Probes run it here. codexBinary string @@ -39,13 +39,6 @@ func defaultSessionConfig() sessionConfig { } } -// Factory implements agent.Factory for agent_kind="codex". Spawns one -// codex app-server child for one Turn. The run stream closes when the turn -// completes; the child remains until the caller cancels the Session. -func Factory(ctx context.Context, req proto.PromptRequestPayload, out chan<- proto.Envelope) (agent.Session, error) { - return newSession(ctx, req, out, defaultSessionConfig()) -} - // Session implements agent.Session. State lifecycle: // // 1. Preparation initializes RPC and verifies the selected environment. @@ -113,24 +106,11 @@ type Session struct { finalText string lastErrText string - interactions *pendingCodexInteractions - outcome cancellationOutcomeState + outcome cancellationOutcomeState } var _ agent.Session = (*Session)(nil) -// SubmitPermission completes the deferred Codex app-server request that -// produced the Core permission envelope. -func (s *Session) SubmitPermission(_ context.Context, permID string, decision proto.PermissionDecisionPayload) error { - return s.submitCodexPermission(permID, decision) -} - -// SubmitPromptForUserChoice maps Core's header/answer pairs back to -// Codex's question-id keyed requestUserInput response. -func (s *Session) SubmitPromptForUserChoice(_ context.Context, askID string, decision proto.PromptForUserChoiceDecisionPayload) error { - return s.submitCodexUserInput(askID, decision) -} - // --------------------------------------------------------------------------- // notification handlers // --------------------------------------------------------------------------- @@ -152,14 +132,10 @@ func (s *Session) registerHandlers() { rpc.OnNotification("thread/tokenUsage/updated", s.onUsageUpdated) rpc.OnNotification("error", s.onErrorNotif) - s.onServerRequest("item/commandExecution/requestApproval", s.handleCodexCommandApproval) - s.onServerRequest("item/fileChange/requestApproval", s.handleCodexFileApproval) - s.onServerRequest("item/permissions/requestApproval", s.handleCodexPermissionsApproval) - // Older app-server releases used this unseparated method name. - s.onServerRequest("item/permissionsRequestApproval", s.handleCodexPermissionsApproval) - s.onServerRequest("item/tool/requestUserInput", s.handleCodexUserInput) + // Harnesses run unattended. Approval policy never and the session plan keep + // native asks from being raised; any other server request gets the + // client's method-not-found reply. s.onServerRequest("item/tool/call", s.handleFunctionCall) - s.onServerRequest("mcpServer/elicitation/request", s.handleCodexMCPElicitation) } func (s *Session) onTurnStarted(raw json.RawMessage) { diff --git a/apps/daemon/internal/agent/codex/session_cancel.go b/apps/daemon/internal/agent/codex/session_cancel.go index 25024d5b5..091eae119 100644 --- a/apps/daemon/internal/agent/codex/session_cancel.go +++ b/apps/daemon/internal/agent/codex/session_cancel.go @@ -42,7 +42,6 @@ func (s *Session) cancelNativeWork() { defer close(s.cancelReady) s.stopFunctionCalls() turnID, active := s.stopSteering() - s.stopCodexInteractionTimers() // Best effort: a known Turn must use its native identity. An explicit // empty ID invokes native startup cancellation before turn/started. if threadID := s.currentThreadID(); threadID != "" && active { diff --git a/apps/daemon/internal/agent/codex/session_cancel_test.go b/apps/daemon/internal/agent/codex/session_cancel_test.go index b5879197b..32e794b6a 100644 --- a/apps/daemon/internal/agent/codex/session_cancel_test.go +++ b/apps/daemon/internal/agent/codex/session_cancel_test.go @@ -116,7 +116,7 @@ func cancellationTestSession(t *testing.T) (*Session, *TestClient, ServerSide) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) s := &Session{rpc: client.JSONRPCClient, cancelCtx: ctx, cancelFn: cancel, - cfg: defaultSessionConfig(), interactions: newPendingCodexInteractions(), out: make(chan proto.Envelope, 8), bufs: NewItemBuffers()} + cfg: defaultSessionConfig(), out: make(chan proto.Envelope, 8), bufs: NewItemBuffers()} return s, client, server } diff --git a/apps/daemon/internal/agent/codex/session_cancel_write_test.go b/apps/daemon/internal/agent/codex/session_cancel_write_test.go index 53cbb1392..bb2abe700 100644 --- a/apps/daemon/internal/agent/codex/session_cancel_write_test.go +++ b/apps/daemon/internal/agent/codex/session_cancel_write_test.go @@ -146,7 +146,7 @@ func TestCancelReleasesBlockedNativeProcess(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() s := &Session{rpc: client, cancelCtx: ctx, cancelFn: cancel, - cfg: defaultSessionConfig(), interactions: newPendingCodexInteractions(), bufs: NewItemBuffers()} + cfg: defaultSessionConfig(), bufs: NewItemBuffers()} s.setThreadID("native-thread") s.onTurnStarted(json.RawMessage(`{"threadId":"native-thread","turn":{"id":"native-turn"}}`)) cancelDone := make(chan struct{}) diff --git a/apps/daemon/internal/agent/codex/session_plan.go b/apps/daemon/internal/agent/codex/session_plan.go index ea99f3174..cb8008cb2 100644 --- a/apps/daemon/internal/agent/codex/session_plan.go +++ b/apps/daemon/internal/agent/codex/session_plan.go @@ -31,9 +31,6 @@ func prepareSessionPlan(ctx context.Context, req proto.PromptRequestPayload, cfg disableProgrammaticTools(&plan, req.ExecutionControls) if req.LocalEnvironment != nil { plan.Cwd = req.LocalEnvironment.WorkspaceRoot - plan.Sandbox = "danger-full-access" - plan.Permissions = "" - plan.ApprovalPolicy = AskForApproval{String: "never"} } else { // environment:none has no workspace; the Session's private home is its cwd. plan.Cwd = plan.home.View diff --git a/apps/daemon/internal/agent/codex/session_run.go b/apps/daemon/internal/agent/codex/session_run.go index 7517bfb98..3394acf90 100644 --- a/apps/daemon/internal/agent/codex/session_run.go +++ b/apps/daemon/internal/agent/codex/session_run.go @@ -9,29 +9,6 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) -func (s *Session) run(plan SessionPlan, req proto.PromptRequestPayload) { - defer close(s.waitDone) - defer s.stopCodexInteractionTimers() - defer s.stopFunctionCalls() - defer s.cleanup() - defer s.closeRunOutput() - - if err := s.startNative(s.cancelCtx, plan, req); err != nil { - s.emitTerminal(err.Error(), true) - return - } - - // Block until terminal handlers close the RPC child or cancellation arrives. - select { - case <-s.rpc.Done(): - if !s.cancelled.Load() && s.cancelCtx.Err() == nil { - s.emitTerminal("codex: connection closed before the run completed", true) - } - case <-s.cancelCtx.Done(): - _ = s.rpc.Close() - } -} - func (s *Session) startNative(ctx context.Context, plan SessionPlan, req proto.PromptRequestPayload) error { if s.currentThreadID() == "" { if err := s.resolveThread(req, plan); err != nil { diff --git a/apps/daemon/internal/agent/codex/session_steering_lifecycle_test.go b/apps/daemon/internal/agent/codex/session_steering_lifecycle_test.go index 9b2bf38f0..6dc663ea0 100644 --- a/apps/daemon/internal/agent/codex/session_steering_lifecycle_test.go +++ b/apps/daemon/internal/agent/codex/session_steering_lifecycle_test.go @@ -83,10 +83,11 @@ func TestBlockedSteeringWriteEndsRunWithTerminalFrames(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() out := make(chan proto.Envelope, 8) + turnCtx, cancelTurn := context.WithCancel(ctx) s := &Session{ - runID: "run", rpc: client.JSONRPCClient, cancelCtx: ctx, out: out, resolvedModel: "synthetic", - cfg: sessionConfig{logger: obslog.Bg()}, waitDone: make(chan struct{}), cleanup: func() {}, - bufs: NewItemBuffers(), interactions: newPendingCodexInteractions(), + executor: &Executor{}, runID: "run", rpc: client.JSONRPCClient, cancelCtx: turnCtx, cancelFn: cancelTurn, out: out, resolvedModel: "synthetic", + cfg: sessionConfig{logger: obslog.Bg()}, waitDone: make(chan struct{}), outputDone: make(chan struct{}), cleanup: func() {}, + bufs: NewItemBuffers(), } s.registerHandlers() ready := make(chan error, 1) @@ -119,7 +120,8 @@ func TestBlockedSteeringWriteEndsRunWithTerminalFrames(t *testing.T) { } ready <- nil }() - go s.run(SessionPlan{Model: "synthetic"}, proto.PromptRequestPayload{Input: proto.TextInput("first")}) + // Executor.StartTurn starts and settles each Turn through these two steps. + go s.settleExecutorTurn(s.startNative(ctx, SessionPlan{Model: "synthetic"}, proto.PromptRequestPayload{Input: proto.TextInput("first")})) if err := <-ready; err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/agent/codex/session_thread.go b/apps/daemon/internal/agent/codex/session_thread.go index 28681b400..35699b8a8 100644 --- a/apps/daemon/internal/agent/codex/session_thread.go +++ b/apps/daemon/internal/agent/codex/session_thread.go @@ -5,14 +5,20 @@ import ( "fmt" ) +// Harnesses run unattended with the launching user's permissions: Codex never +// raises a native approval request and applies no inner sandbox. +const ( + approvalPolicyNever = "never" + sandboxDangerFullAccess = "danger-full-access" +) + func (s *Session) startThread(plan SessionPlan) error { params := ThreadStartParams{ Cwd: plan.Cwd, Model: plan.Model, ModelProvider: plan.ModelProvider, - ApprovalPolicy: plan.ApprovalPolicy, - Sandbox: plan.Sandbox, - Permissions: plan.Permissions, + ApprovalPolicy: approvalPolicyNever, + Sandbox: sandboxDangerFullAccess, DeveloperInstructions: plan.SystemPrompt, } if s.observeSubagentIdentities { @@ -26,8 +32,7 @@ func (s *Session) startThread(plan SessionPlan) error { "cwd", params.Cwd, "model", params.Model, "model_provider", params.ModelProvider, - "sandbox", string(params.Sandbox), - "approval_silent", IsSilent(¶ms.ApprovalPolicy), + "sandbox", params.Sandbox, "developer_instructions_len", len(params.DeveloperInstructions)) _, err := s.rpc.requestWithResult(s.cancelCtx, "thread/start", params, func(raw json.RawMessage) error { return s.bindThreadResult(raw, "") @@ -37,8 +42,7 @@ func (s *Session) startThread(plan SessionPlan) error { func (s *Session) resumeThread(threadID string, plan SessionPlan) error { params := ThreadResumeParams{ - ThreadID: threadID, ApprovalPolicy: plan.ApprovalPolicy, Sandbox: plan.Sandbox, - Permissions: plan.Permissions, + ThreadID: threadID, ApprovalPolicy: approvalPolicyNever, Sandbox: sandboxDangerFullAccess, DeveloperInstructions: plan.SystemPrompt, } if plan.mcpServers != nil { diff --git a/apps/daemon/internal/agent/codex/subagent_observations_test.go b/apps/daemon/internal/agent/codex/subagent_observations_test.go index 7c4b79415..a96258ca7 100644 --- a/apps/daemon/internal/agent/codex/subagent_observations_test.go +++ b/apps/daemon/internal/agent/codex/subagent_observations_test.go @@ -75,7 +75,7 @@ func observationSession(t *testing.T, status string) (*Session, *subagentFixture t.Fatal(err) } f.persist(t) - s := &Session{runID: "run", nativeHome: agent.ViewDir{Host: f.home, View: f.home}, rpc: client.JSONRPCClient, out: out, cancelCtx: ctx, cancelFn: cancel, cfg: defaultSessionConfig(), bufs: NewItemBuffers(), interactions: newPendingCodexInteractions()} + s := &Session{runID: "run", nativeHome: agent.ViewDir{Host: f.home, View: f.home}, rpc: client.JSONRPCClient, out: out, cancelCtx: ctx, cancelFn: cancel, cfg: defaultSessionConfig(), bufs: NewItemBuffers()} s.setThreadID("root") s.beginRootTurn("root", "root-turn") s.startSubagentObservations() diff --git a/apps/daemon/internal/agent/codex/view.go b/apps/daemon/internal/agent/codex/view.go index 1f7d85bfa..b64e0c499 100644 --- a/apps/daemon/internal/agent/codex/view.go +++ b/apps/daemon/internal/agent/codex/view.go @@ -163,9 +163,6 @@ func prepareViewPlan(ctx context.Context, req proto.PromptRequestPayload, cfg se } disableProgrammaticTools(&plan, req.ExecutionControls) plan.Cwd = cwd - plan.Sandbox = SandboxDangerFullAcces - plan.Permissions = "" - plan.ApprovalPolicy = AskForApproval{String: "never"} if req.DisableSubagents { disableSubagents(&plan) } diff --git a/apps/daemon/internal/agent/codex/view_test.go b/apps/daemon/internal/agent/codex/view_test.go index a2a995f34..fda7a6bc3 100644 --- a/apps/daemon/internal/agent/codex/view_test.go +++ b/apps/daemon/internal/agent/codex/view_test.go @@ -24,7 +24,7 @@ func TestViewExecutorLaunchesInTheSessionView(t *testing.T) { info := Declaration.Info info.Available = true registry := agent.NewRegistry() - registry.Register(Declaration, agent.Runtime{Info: info, Session: Factory, View: &declared}) + registry.Register(Declaration, agent.Runtime{Info: info, View: &declared}) view, err := registry.ResolveView("codex") if err != nil { t.Fatal(err) diff --git a/apps/daemon/internal/agent/configuration_test.go b/apps/daemon/internal/agent/configuration_test.go index 2e6455f31..a1ad66749 100644 --- a/apps/daemon/internal/agent/configuration_test.go +++ b/apps/daemon/internal/agent/configuration_test.go @@ -18,41 +18,27 @@ func TestEveryRegistryEntryPreparesTheBoundModelConfiguration(t *testing.T) { configuration := harnessconfig.Configuration{Providers: []harnessconfig.Provider{{Protocol: "responses"}}} calls := 0 expected := errors.New("native entry reached") - registry.RegisterKind(proto.SupportedAgentKind{Kind: "fixture", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, configuration, func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - calls++ - return nil, expected - }) + registry.RegisterKind(proto.SupportedAgentKind{Kind: "fixture", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, configuration) registry.RegisterExecutor("fixture", func(context.Context, proto.PromptRequestPayload) (agent.Executor, error) { calls++ return nil, expected }) configuration.Providers[0].Protocol = "anthropic" - factory, _ := registry.Resolve("fixture") executor, _ := registry.ResolveExecutor("fixture") - entries := []func(proto.PromptRequestPayload) error{ - func(req proto.PromptRequestPayload) error { _, err := factory(t.Context(), req, nil); return err }, - func(req proto.PromptRequestPayload) error { _, err := executor(t.Context(), req); return err }, - } - for _, entry := range entries { - responses := &modelprovider.Provider{Protocol: modelprovider.Responses, BaseURL: "https://provider.example", APIKey: "private-sentinel"} - for _, req := range []proto.PromptRequestPayload{ - {Model: "fixture", ModelProvider: &modelprovider.Provider{Protocol: modelprovider.Anthropic, BaseURL: "https://provider.example", APIKey: "private-sentinel"}}, - {ModelProvider: responses}, - {Model: "fixture"}, - {HarnessConfig: proto.HarnessConfig(`{"unknown":"private-sentinel"}`)}, - } { - before := calls - err := entry(req) - if err == nil || errors.Is(err, expected) || calls != before || strings.Contains(err.Error(), "private-sentinel") { - t.Fatal("invalid configuration reached native entry or leaked values") - } - } - if err := entry(proto.PromptRequestPayload{Model: "fixture", ModelProvider: responses}); !errors.Is(err, expected) { - t.Fatal("bound declaration was lost or mutated", err) + responses := &modelprovider.Provider{Protocol: modelprovider.Responses, BaseURL: "https://provider.example", APIKey: "private-sentinel"} + for _, req := range []proto.PromptRequestPayload{ + {Model: "fixture", ModelProvider: &modelprovider.Provider{Protocol: modelprovider.Anthropic, BaseURL: "https://provider.example", APIKey: "private-sentinel"}}, + {ModelProvider: responses}, + {Model: "fixture"}, + {HarnessConfig: proto.HarnessConfig(`{"unknown":"private-sentinel"}`)}, + } { + _, err := executor(t.Context(), req) + if err == nil || errors.Is(err, expected) || calls != 0 || strings.Contains(err.Error(), "private-sentinel") { + t.Fatal("invalid configuration reached native entry or leaked values") } } - if calls != 2 { - t.Fatal("unexpected native calls", calls) + if _, err := executor(t.Context(), proto.PromptRequestPayload{Model: "fixture", ModelProvider: responses}); !errors.Is(err, expected) || calls != 1 { + t.Fatal("bound declaration was lost or mutated", err) } } @@ -62,5 +48,5 @@ func TestRegistryRejectsInvalidConfigurationDeclaration(t *testing.T) { t.Fatal("invalid configuration registered") } }() - agent.NewRegistry().RegisterKind(proto.SupportedAgentKind{Kind: "fixture", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, harnessconfig.Configuration{Providers: []harnessconfig.Provider{{Protocol: "unknown"}}}, stubFactory("fixture")) + agent.NewRegistry().RegisterKind(proto.SupportedAgentKind{Kind: "fixture", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, harnessconfig.Configuration{Providers: []harnessconfig.Provider{{Protocol: "unknown"}}}) } diff --git a/apps/daemon/internal/agent/contract_declarations_test.go b/apps/daemon/internal/agent/contract_declarations_test.go index a07c4e29e..4479f26d4 100644 --- a/apps/daemon/internal/agent/contract_declarations_test.go +++ b/apps/daemon/internal/agent/contract_declarations_test.go @@ -22,8 +22,6 @@ func TestPublicHarnessContractDeclarations(t *testing.T) { "DurableSteerer": {"session"}, "Steerer": {"session"}, "FunctionResultSubmitter": {"session"}, - "PermissionResponder": {"session"}, - "UserChoiceResponder": {"session"}, "WorkspaceDirectoryLister": {"executor", "session"}, "WorkspaceWriter": {"executor", "session"}, } diff --git a/apps/daemon/internal/agent/contracttest/text.go b/apps/daemon/internal/agent/contracttest/text.go index 5c0d76e4a..c389163a7 100644 --- a/apps/daemon/internal/agent/contracttest/text.go +++ b/apps/daemon/internal/agent/contracttest/text.go @@ -32,7 +32,7 @@ type TextFixture struct { // TextLifecycle verifies ordinary reuse, exact Turn ownership, durable active // input, confirmed cancellation and continuation of a healthy Executor. It asks -// for no optional tools, images, interactions, workspace or detailed Usage. +// for no optional tools, images, workspace or detailed Usage. // Native fault injection and history recovery have separate adapter tests. func TextLifecycle(t *testing.T, fixture TextFixture) { t.Helper() diff --git a/apps/daemon/internal/agent/harness.go b/apps/daemon/internal/agent/harness.go index 4f6bc8b26..e3d0b9826 100644 --- a/apps/daemon/internal/agent/harness.go +++ b/apps/daemon/internal/agent/harness.go @@ -14,8 +14,8 @@ // Registration: each adapter exports one Declaration. The Runtime discovers the // static declaration list and installs each resulting Runtime through Register. // Availability and factory selection belong to the adapter. RegisterKind resets -// the other factories, so Register installs it first. Preparation capabilities -// are derived from the declared factories. +// the factories, so Register installs it first. RegisterExecutor derives the +// Preparation capability; the adapter declares WorkspaceReadPreparation. // // Runtime registration and Core service qualification remain separate. A public // Harness also needs a profile in services/core/internal/engine; advertising @@ -48,7 +48,7 @@ import ( // Declaration is the complete startup contract for a Harness implementation. // Discover returns nil when the adapter is not configured. An unavailable -// configured adapter returns a Runtime with Available=false and a session factory. +// configured adapter returns a Runtime with Available=false and no factories. // Discovery owns runtime-specific configuration, readiness and feature gates. type Declaration struct { Info proto.SupportedAgentKind @@ -65,7 +65,6 @@ type DiscoveryOptions struct { // Runtime binds one discovered descriptor to its native factories. type Runtime struct { Info proto.SupportedAgentKind - Session Factory Executor ExecutorFactory // View declares how the Harness runs in an agent-host Session view. // A nil View means the agent host rejects the kind with ErrUnsupportedOperation. @@ -77,7 +76,10 @@ func (r *Registry) Register(declaration Declaration, runtime Runtime) { if runtime.Info.Kind != declaration.Info.Kind { panic("agent.Registry.Register: discovery kind differs from declaration") } - r.RegisterKind(runtime.Info, declaration.Configuration, runtime.Session) + if !runtime.Info.Available && runtime.Executor != nil { + panic("agent.Registry.Register: unavailable runtime has factories") + } + r.RegisterKind(runtime.Info, declaration.Configuration) if runtime.Executor != nil { r.RegisterExecutor(runtime.Info.Kind, runtime.Executor) } @@ -553,8 +555,8 @@ type Executor interface { type Turn interface { Session DurableSteerer - // Success confirms closed output and settled native input, function, - // interaction and child-work obligations. Errors cannot prove cancellation. + // Success confirms closed output and settled native input, function and + // child-work obligations. Errors cannot prove cancellation. AwaitSettlement(context.Context) (TurnSettlement, error) } @@ -565,8 +567,8 @@ type TurnSettlement struct { Reason string } -// Session is the cancellation and outcome surface shared by direct-call -// sessions and Turns. Every owner exposes observed state. +// Session is the cancellation and outcome surface of a Turn. Every owner +// exposes observed state. // For Executor-owned Turns, AwaitSettlement and Executor.Close define settlement // and resource retirement; Cancel alone does not transfer resource ownership. type Session interface { @@ -602,20 +604,6 @@ type FunctionResultSubmitter interface { SubmitFunctionResult(context.Context, proto.FunctionResultPayload) error } -// PermissionResponder accepts decisions for qualified permission requests. -// An adapter that never supports these interactions returns ErrUnsupportedOperation. -// Unknown or expired requests return ErrUnknownPermission. -type PermissionResponder interface { - SubmitPermission(context.Context, string, proto.PermissionDecisionPayload) error -} - -// UserChoiceResponder accepts answers for qualified user-choice requests. -// An adapter that never supports these interactions returns ErrUnsupportedOperation. -// Unknown or expired requests return ErrUnknownAsk. -type UserChoiceResponder interface { - SubmitPromptForUserChoice(context.Context, string, proto.PromptForUserChoiceDecisionPayload) error -} - // Workspace extensions, implemented by each owner explicitly. The common // Runtime may supply an authorized workspace owner independently of the adapter. // A resource without native access returns the corresponding Unsupported error; @@ -640,25 +628,16 @@ type WorkspaceWriter interface { WriteWorkspaceFile(context.Context, string, []byte) (WorkspaceWriteResult, error) } -// Kind registration and the direct-call factory. - -// Factory builds a Session that runs req.Input as req.RunID without an -// Executor; the router starts every Run through RegisterExecutor instead. out -// is the channel the agent writes into and closes exactly once after terminal -// output. ctx is cancelled to wind the session down. -type Factory func(ctx context.Context, req proto.PromptRequestPayload, out chan<- proto.Envelope) (Session, error) +// Kind registration. -// RegisterKind installs f and the heartbeat descriptor for an -// agent_kind. Callers may set Available=false when an adapter exists -// but its underlying CLI is not usable. -func (r *Registry) RegisterKind(info proto.SupportedAgentKind, configuration harnessconfig.Configuration, f Factory) { +// RegisterKind installs the heartbeat descriptor and model configuration for an +// agent_kind. Callers may set Available=false when an adapter exists but its +// underlying CLI is not usable. +func (r *Registry) RegisterKind(info proto.SupportedAgentKind, configuration harnessconfig.Configuration) { kind := info.Kind if kind == "" { panic("agent.Registry.Register: empty kind") } - if f == nil { - panic("agent.Registry.Register: nil factory") - } if err := configuration.ValidateDeclaration(); err != nil { panic(err) } @@ -669,12 +648,6 @@ func (r *Registry) RegisterKind(info proto.SupportedAgentKind, configuration har r.mu.Lock() defer r.mu.Unlock() r.configurations[kind] = configuration - r.factories[kind] = func(ctx context.Context, req proto.PromptRequestPayload, out chan<- proto.Envelope) (Session, error) { - if _, err := configuration.Prepare(req); err != nil { - return nil, err - } - return f(ctx, req, out) - } delete(r.executors, kind) delete(r.views, kind) info.Capabilities.Preparation = proto.CapabilityUnsupported diff --git a/apps/daemon/internal/agent/installroot/lock.go b/apps/daemon/internal/agent/installroot/lock.go deleted file mode 100644 index 19d9295e4..000000000 --- a/apps/daemon/internal/agent/installroot/lock.go +++ /dev/null @@ -1,69 +0,0 @@ -// Package installroot coordinates adapter installations within one daemon process. -package installroot - -import ( - "context" - "os" - "sync" -) - -type installRootLock struct { - info os.FileInfo - token chan struct{} - users int -} - -var installRoots = struct { - sync.Mutex - entries map[*installRootLock]struct{} -}{entries: make(map[*installRootLock]struct{})} - -// Lock creates the install root if needed and holds its filesystem identity -// until the returned function is called once. -// Waiting callers may cancel without interrupting the current installation. -func Lock(ctx context.Context, root string) (func(), error) { - if err := ctx.Err(); err != nil { - return nil, err - } - if err := os.MkdirAll(root, 0o755); err != nil { - return nil, err - } - info, err := os.Stat(root) - if err != nil { - return nil, err - } - installRoots.Lock() - var entry *installRootLock - for candidate := range installRoots.entries { - if os.SameFile(candidate.info, info) { - entry = candidate - break - } - } - if entry == nil { - entry = &installRootLock{info: info, token: make(chan struct{}, 1)} - entry.token <- struct{}{} - installRoots.entries[entry] = struct{}{} - } - entry.users++ - installRoots.Unlock() - - release := func() { - installRoots.Lock() - entry.users-- - if entry.users == 0 { - delete(installRoots.entries, entry) - } - installRoots.Unlock() - } - select { - case <-ctx.Done(): - release() - return nil, ctx.Err() - case <-entry.token: - return func() { - entry.token <- struct{}{} - release() - }, nil - } -} diff --git a/apps/daemon/internal/agent/installroot/lock_test.go b/apps/daemon/internal/agent/installroot/lock_test.go deleted file mode 100644 index 4359af299..000000000 --- a/apps/daemon/internal/agent/installroot/lock_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package installroot - -import ( - "context" - "os" - "path/filepath" - "strings" - "testing" - "time" -) - -func TestLockCaseAliases(t *testing.T) { - parent := t.TempDir() - alias := strings.ToUpper(parent) - realInfo, err := os.Stat(parent) - if err != nil { - t.Fatal(err) - } - aliasInfo, err := os.Stat(alias) - if err != nil || !os.SameFile(realInfo, aliasInfo) { - t.Skip("filesystem does not expose this case alias") - } - root := filepath.Join(parent, "new-root") - unlock, err := Lock(context.Background(), root) - if err != nil { - t.Fatal(err) - } - defer unlock() - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond) - defer cancel() - release, err := Lock(ctx, filepath.Join(alias, "NEW-ROOT")) - if err == nil { - release() - t.Fatal("case alias acquired an independent lock") - } - if err != context.DeadlineExceeded { - t.Fatalf("lock error = %v", err) - } -} diff --git a/apps/daemon/internal/agent/installroot/probe.go b/apps/daemon/internal/agent/installroot/probe.go index 21f50ed47..d2932e3f8 100644 --- a/apps/daemon/internal/agent/installroot/probe.go +++ b/apps/daemon/internal/agent/installroot/probe.go @@ -1,3 +1,4 @@ +// Package installroot probes native adapter installations. package installroot import ( diff --git a/apps/daemon/internal/agent/mcode/contracts.go b/apps/daemon/internal/agent/mcode/contracts.go index 3df9bbe3d..f58d8ca5e 100644 --- a/apps/daemon/internal/agent/mcode/contracts.go +++ b/apps/daemon/internal/agent/mcode/contracts.go @@ -11,8 +11,6 @@ var ( _ agent.DurableSteerer = (*Session)(nil) _ agent.Steerer = (*Session)(nil) _ agent.FunctionResultSubmitter = (*Session)(nil) - _ agent.PermissionResponder = (*Session)(nil) - _ agent.UserChoiceResponder = (*Session)(nil) _ agent.WorkspaceDirectoryLister = (*Session)(nil) _ agent.WorkspaceWriter = (*Session)(nil) _ agent.WorkspaceDirectoryLister = (*executor)(nil) diff --git a/apps/daemon/internal/agent/mcode/declaration.go b/apps/daemon/internal/agent/mcode/declaration.go index ff79fd25f..461cd53ed 100644 --- a/apps/daemon/internal/agent/mcode/declaration.go +++ b/apps/daemon/internal/agent/mcode/declaration.go @@ -14,7 +14,6 @@ import ( var Declaration = agent.Declaration{Info: proto.SupportedAgentKind{Kind: "mcode", Capabilities: proto.AgentKindCapabilities{ SubagentObservations: proto.CapabilityUnsupported, Streaming: proto.CapabilitySupported, - Permissions: proto.CapabilitySupported, Usage: proto.CapabilityUnsupported, Resume: proto.CapabilitySupported, NativeSessionRecovery: proto.CapabilityUnsupported, @@ -47,7 +46,7 @@ func discover(ctx context.Context, options agent.DiscoveryOptions, info proto.Su return discoverWithCheck(ctx, options, info, CheckCLIAvailable) } func discoverWithCheck(parent context.Context, options agent.DiscoveryOptions, result proto.SupportedAgentKind, check func(context.Context, string) (string, error)) *agent.Runtime { - runtime := &agent.Runtime{Info: result, Session: Factory} + runtime := &agent.Runtime{Info: result} ctx, cancel := context.WithTimeout(parent, 15*time.Second) defer cancel() @@ -57,27 +56,23 @@ func discoverWithCheck(parent context.Context, options agent.DiscoveryOptions, r return runtime } result.Available, result.Version = true, version - if SupportsExecution(version) { - result.Capabilities.Steering = proto.CapabilitySupported - result.Capabilities.DurableTurns = proto.CapabilitySupported - result.Capabilities.DurableInputReceipts = proto.CapabilitySupported - result.Capabilities.ExecutionControls = proto.CapabilitySupported - result.Capabilities.ProgrammaticToolCallingDisable = proto.CapabilitySupported - result.Capabilities.ToolObservations = proto.CapabilitySupported - result.Capabilities.SubagentControl = proto.CapabilitySupported - // Native preparation verifies the applied admission/tool profile before input. - result.Capabilities.SubagentObservations = proto.CapabilitySupported - result.Capabilities.EnvironmentNone = proto.CapabilitySupported - result.Capabilities.MCPHTTPTools = proto.CapabilitySupported - result.Capabilities.MCPHTTPBearerAuth = proto.CapabilitySupported - } + result.Capabilities.Steering = proto.CapabilitySupported + result.Capabilities.DurableTurns = proto.CapabilitySupported + result.Capabilities.DurableInputReceipts = proto.CapabilitySupported + result.Capabilities.ExecutionControls = proto.CapabilitySupported + result.Capabilities.ProgrammaticToolCallingDisable = proto.CapabilitySupported + result.Capabilities.ToolObservations = proto.CapabilitySupported + result.Capabilities.SubagentControl = proto.CapabilitySupported + // Native preparation verifies the applied admission/tool profile before input. + result.Capabilities.SubagentObservations = proto.CapabilitySupported + result.Capabilities.EnvironmentNone = proto.CapabilitySupported + result.Capabilities.MCPHTTPTools = proto.CapabilitySupported + result.Capabilities.MCPHTTPBearerAuth = proto.CapabilitySupported runtime.Info = result workspace := discoverWorkspace(parent, options, runtime) if runtime.Info.Available { runtime.Executor = NewExecutorFactory(workspace) - if SupportsExecution(version) { - runtime.View = discoverView(options) - } + runtime.View = discoverView(options) } fmt.Fprintf(options.Stdout, "mcode preflight ok (%s)\n", version) return runtime diff --git a/apps/daemon/internal/agent/mcode/declaration_test.go b/apps/daemon/internal/agent/mcode/declaration_test.go index 229d6192e..386c4df2d 100644 --- a/apps/daemon/internal/agent/mcode/declaration_test.go +++ b/apps/daemon/internal/agent/mcode/declaration_test.go @@ -2,6 +2,7 @@ package mcode import ( "context" + "errors" "io" "reflect" "testing" @@ -10,20 +11,20 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) -func TestMCodeExecutionOptInIsVersionBound(t *testing.T) { +// An available runtime is execution-capable; a rejected native version is unavailable. +func TestMCodeExecutionFollowsAvailability(t *testing.T) { for _, tc := range []struct { - enabled, version string - qualified bool - }{{"", "0.4.12", false}, {"1", "0.3.11", false}, {"1", "0.4.12", true}} { - t.Run(tc.enabled+"/"+tc.version, func(t *testing.T) { - t.Setenv("OAC_RUNTIME_MCODE_AGENTS_API", tc.enabled) + version string + check error + }{{SupportedVersion, nil}, {"0.3.11", errors.New("mcode: unsupported version 0.3.11")}} { + t.Run(tc.version, func(t *testing.T) { rc := agent.DiscoveryOptions{Stdout: io.Discard, Stderr: io.Discard} - runtime := discoverWithCheck(t.Context(), rc, Declaration.Info, func(context.Context, string) (string, error) { return tc.version, nil }) - if runtime.Executor == nil || runtime.Info.Capabilities.WorkspaceReadPreparation.IsSupported() { + runtime := discoverWithCheck(t.Context(), rc, Declaration.Info, func(context.Context, string) (string, error) { return tc.version, tc.check }) + info, available := runtime.Info, tc.check == nil + if (runtime.Executor != nil) != available || info.Capabilities.WorkspaceReadPreparation.IsSupported() { t.Fatalf("factories: %+v", runtime) } - info := runtime.Info - if !info.Available || info.Capabilities.EnvironmentNone.IsSupported() != tc.qualified || info.Capabilities.DurableInputReceipts.IsSupported() != tc.qualified || info.Capabilities.SubagentObservations.IsSupported() != tc.qualified { + if info.Available != available || info.Capabilities.EnvironmentNone.IsSupported() != available || info.Capabilities.DurableInputReceipts.IsSupported() != available || info.Capabilities.SubagentObservations.IsSupported() != available { t.Fatalf("capabilities=%+v", info.Capabilities) } if info.Capabilities.NativeSessionRecovery.IsSupported() || info.Capabilities.LocalEnvironment.IsSupported() || info.Capabilities.FunctionTools.IsSupported() { @@ -35,7 +36,7 @@ func TestMCodeExecutionOptInIsVersionBound(t *testing.T) { // The declaration must retain the complete baseline capability descriptor. func TestDeclaredCapabilityBaseline(t *testing.T) { - expected := map[string]bool{"Streaming": true, "Permissions": true, "Resume": true} + expected := map[string]bool{"Streaming": true, "Resume": true} value := reflect.ValueOf(Declaration.Info.Capabilities) for i := 0; i < value.NumField(); i++ { name := value.Type().Field(i).Name diff --git a/apps/daemon/internal/agent/mcode/discovery_workspace.go b/apps/daemon/internal/agent/mcode/discovery_workspace.go index 9f6060436..6ee8d78e7 100644 --- a/apps/daemon/internal/agent/mcode/discovery_workspace.go +++ b/apps/daemon/internal/agent/mcode/discovery_workspace.go @@ -27,10 +27,6 @@ func discoverWorkspace(parent context.Context, options agent.DiscoveryOptions, r if binding == nil { return nil } - if !runtime.Info.Available || !SupportsExecution(runtime.Info.Version) { - fail(fmt.Errorf("local execution requires the qualified native version")) - return nil - } root, err := paths.Root() if err != nil { fail(err) @@ -52,8 +48,7 @@ func discoverWorkspace(parent context.Context, options agent.DiscoveryOptions, r caps := &runtime.Info.Capabilities caps.EnvironmentNone = proto.CapabilityUnsupported - caps.Preparation, caps.LocalEnvironment = proto.CapabilitySupported, proto.CapabilitySupported - caps.WorkspaceReadPreparation = proto.CapabilitySupported + caps.LocalEnvironment, caps.WorkspaceReadPreparation = proto.CapabilitySupported, proto.CapabilitySupported return &c } diff --git a/apps/daemon/internal/agent/mcode/events.go b/apps/daemon/internal/agent/mcode/events.go index 97bd1f8cd..a41789fd1 100644 --- a/apps/daemon/internal/agent/mcode/events.go +++ b/apps/daemon/internal/agent/mcode/events.go @@ -7,18 +7,18 @@ import ( "net/url" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - "github.com/google/uuid" ) func decodeComponent(value string) (string, error) { return url.PathUnescape(value) } func (s *Session) handle(frame rpcFrame) error { if len(frame.ID) > 0 { - if frame.Method == "session/request_permission" && s.active { - return s.askPermission(frame) - } - if frame.Method == "elicitation/create" && s.active { - return s.askQuestion(frame) + // Harnesses run unattended, so no client method other than the + // permission answer is offered. Pinned mcode 0.4.12 maps the ACP + // cancelled outcome to deny: the tool call is blocked and the Turn + // continues. + if frame.Method == "session/request_permission" { + return s.write(rpcFrame{JSONRPC: "2.0", ID: frame.ID, Result: json.RawMessage(`{"outcome":{"outcome":"cancelled"}}`)}) } return s.write(rpcFrame{JSONRPC: "2.0", ID: frame.ID, Error: &rpcError{Code: -32601, Message: "ACP method not supported by OpenAgentCore"}}) } @@ -95,35 +95,3 @@ func (s *Session) emitTool(update toolUpdate) error { } return nil } - -func (s *Session) askPermission(frame rpcFrame) error { - var request permissionRequest - if err := json.Unmarshal(frame.Params, &request); err != nil { - return fmt.Errorf("mcode: invalid permission request") - } - if request.SessionID != s.sessionID { - return fmt.Errorf("mcode: permission request belongs to another session") - } - pending := pendingPermission{RPCID: frame.ID} - for _, option := range request.Options { - switch option.Kind { - case "allow_once": - pending.Allow = option.ID - case "reject_once": - pending.Deny = option.ID - } - } - if pending.Allow == "" || pending.Deny == "" { - return fmt.Errorf("mcode: permission request has no one-time allow/deny options") - } - id := "perm_" + uuid.NewString() - s.mu.Lock() - s.permissions[id] = pending - s.mu.Unlock() - tool := request.ToolCall.Name - if tool == "" { - tool = request.ToolCall.Title - } - s.emit(proto.TypePermissionRequest, proto.PermissionRequestPayload{RequestID: id, Tool: tool, Title: request.ToolCall.Title, Payload: request.ToolCall.RawInput}) - return nil -} diff --git a/apps/daemon/internal/agent/mcode/execution.go b/apps/daemon/internal/agent/mcode/execution.go index 7b2a93d93..c8438e680 100644 --- a/apps/daemon/internal/agent/mcode/execution.go +++ b/apps/daemon/internal/agent/mcode/execution.go @@ -7,13 +7,6 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) -// SupportsExecution reports whether the daemon advertises MiniMax Code execution: -// the operator sets OAC_RUNTIME_MCODE_AGENTS_API=1 and the native version is the -// qualified one. Otherwise discovery reports only availability. -func SupportsExecution(version string) bool { - return os.Getenv("OAC_RUNTIME_MCODE_AGENTS_API") == "1" && version == SupportedVersion -} - func validateExecutionRequest(req proto.PromptRequestPayload) error { if !req.DisableExecutionEnvironment || req.AgentStateKey == "" || req.LocalEnvironment != nil || req.RequireExistingNativeSession || len(req.FunctionTools) != 0 || (req.MCPHTTPServers != nil && len(*req.MCPHTTPServers) != 0) { return fmt.Errorf("mcode: unsupported execution configuration") diff --git a/apps/daemon/internal/agent/mcode/executor.go b/apps/daemon/internal/agent/mcode/executor.go index ac22cced5..d40b8964c 100644 --- a/apps/daemon/internal/agent/mcode/executor.go +++ b/apps/daemon/internal/agent/mcode/executor.go @@ -70,7 +70,7 @@ func startExecutor(ctx context.Context, req proto.PromptRequestPayload, binary s } func newExecutor(ctx context.Context, req proto.PromptRequestPayload, opts launchOptions, binary string) (*executor, error) { - bootstrap, err := launch(ctx, req, opts, binary, nil) + bootstrap, err := launch(ctx, req, opts, binary) if err != nil { return nil, err } @@ -133,14 +133,14 @@ func (e *executor) StartTurn(ctx context.Context, runID string, input proto.Mess default: } req := e.req - req.RunID, req.Input = runID, proto.TextInput(text) + req.RunID = runID s := newTurnSession(e.connection.process.Context(), req, e.opts, e.connection, out) s.executor, s.sessionID, s.nativeModel = e, e.nativeSession, e.model s.settled, s.inputDone = make(chan struct{}), make(chan struct{}) s.outputContext, s.outputCancel = context.WithCancel(context.Background()) e.active = s e.connection.setCurrent(s) - go s.runExecutorTurn() + go s.runExecutorTurn(text) go func() { select { case <-ctx.Done(): diff --git a/apps/daemon/internal/agent/mcode/executor_test.go b/apps/daemon/internal/agent/mcode/executor_test.go index a7aba62ed..59a1f5d60 100644 --- a/apps/daemon/internal/agent/mcode/executor_test.go +++ b/apps/daemon/internal/agent/mcode/executor_test.go @@ -209,12 +209,21 @@ func TestExecutorCancellationRetiresOwnerAndLateCancelCannotRetarget(t *testing. func TestExecutorStartFailureOwnership(t *testing.T) { t.Run("validation", func(t *testing.T) { e, record := executorFixture(t, "executor-reuse", true) - out := make(chan proto.Envelope, 1) - turn, err := e.StartTurn(t.Context(), "", proto.TextInput("invalid"), out) - if err == nil || turn != nil { - t.Fatal("invalid Start acquired output") + image := "data:image/png;base64,iVBORw0KGgo=" + for _, invalid := range []struct { + id, reason string + input proto.MessageInput + }{ + {"", "requires live context, identity", proto.TextInput("invalid")}, + {"attachment", "does not support image input", proto.MessageInput{{Content: []proto.InputContent{{Type: "input_image", ImageURL: &image}}}}}, + } { + out := make(chan proto.Envelope, 1) + turn, err := e.StartTurn(t.Context(), invalid.id, invalid.input, out) + if err == nil || turn != nil || !strings.Contains(err.Error(), invalid.reason) { + t.Fatal("invalid Start acquired output", err) + } + close(out) } - close(out) raw, _ := os.ReadFile(record) if strings.Contains(string(raw), "session/prompt") { t.Fatal("invalid input was sent") diff --git a/apps/daemon/internal/agent/mcode/executor_turn.go b/apps/daemon/internal/agent/mcode/executor_turn.go index 1633f5f18..dc24cd17a 100644 --- a/apps/daemon/internal/agent/mcode/executor_turn.go +++ b/apps/daemon/internal/agent/mcode/executor_turn.go @@ -10,10 +10,10 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) -func (s *Session) runExecutorTurn() { +func (s *Session) runExecutorTurn(prompt string) { err := s.captureSubagentBaseline() if err == nil { - err = s.executePrompt() + err = s.executePrompt(prompt) } if errors.Is(err, errTurnCancelled) { err = nil @@ -26,11 +26,7 @@ func (s *Session) runExecutorTurn() { // prompt completion. No successor starts until all callers have settled. s.operations.Wait() if !s.req.DisableSubagents && s.subagentHistoryReady { - childErr := s.settleSubagents() - s.mu.Lock() - s.subagentSettlementError = childErr - s.mu.Unlock() - if childErr != nil { + if childErr := s.settleSubagents(); childErr != nil { err = childErr } } @@ -42,7 +38,7 @@ func (s *Session) runExecutorTurn() { s.mu.Lock() // ACP and native history cancellation do not prove detached tool cleanup. // Retire the owner and settle its workers before acknowledging cancellation. - if s.cancelled || s.inputUncertain || len(s.permissions) != 0 || len(s.questions) != 0 { + if s.cancelled || s.inputUncertain { reusable = false } if s.inputUncertain && err == nil { @@ -51,7 +47,6 @@ func (s *Session) runExecutorTurn() { metadata := map[string]any{proto.DoneMetaAgentSessionType: "mcode", proto.DoneMetaAgentSessionID: s.sessionID} s.outcome = proto.DonePayload{Content: s.content.String(), Metadata: metadata, SourceCompletedAtMS: s.rootCompletedAtMS} outcome := s.outcome - s.permissions, s.questions = map[string]pendingPermission{}, map[string]pendingQuestion{} s.mu.Unlock() if !reusable { // Unknown quiescence invalidates this owner. A successful settlement @@ -99,9 +94,6 @@ func (s *Session) captureSubagentBaseline() error { } func (s *Session) AwaitSettlement(ctx context.Context) (agent.TurnSettlement, error) { - if s.settled == nil { - return agent.TurnSettlement{}, fmt.Errorf("mcode: Turn has no Executor owner") - } select { case <-s.settled: return s.settlement, s.settlementErr @@ -110,7 +102,7 @@ func (s *Session) AwaitSettlement(ctx context.Context) (agent.TurnSettlement, er } } -func (s *Session) cancelTurn(ctx context.Context) error { +func (s *Session) Cancel(ctx context.Context) error { e := s.executor e.mu.Lock() if e.active != s { diff --git a/apps/daemon/internal/agent/mcode/installation.go b/apps/daemon/internal/agent/mcode/installation.go index fe75d9258..5f6937fc1 100644 --- a/apps/daemon/internal/agent/mcode/installation.go +++ b/apps/daemon/internal/agent/mcode/installation.go @@ -12,7 +12,7 @@ import ( func Installation() agent.Installation { return agent.Installation{AgentKind: "mcode", Version: SupportedVersion, Supported: func() bool { return runtime.GOOS == "linux" || runtime.GOOS == "darwin" }, Environment: func(dir, node string) map[string]string { - return map[string]string{"OAC_RUNTIME_MCODE_BIN": filepath.Join(dir, "native", "cli.js"), "OAC_RUNTIME_MCODE_NODE": node, "OAC_RUNTIME_MCODE_WORKSPACE_BRIDGE": filepath.Join(dir, "bridge.mjs"), "OAC_RUNTIME_MCODE_AGENTS_API": "1"} + return map[string]string{"OAC_RUNTIME_MCODE_BIN": filepath.Join(dir, "native", "cli.js"), "OAC_RUNTIME_MCODE_NODE": node, "OAC_RUNTIME_MCODE_WORKSPACE_BRIDGE": filepath.Join(dir, "bridge.mjs")} }, Check: func(ctx context.Context, dir, node string, env []string) error { got, err := installroot.Probe(ctx, node, []string{filepath.Join(dir, "native", "cli.js"), "--version"}, env, dir) diff --git a/apps/daemon/internal/agent/mcode/mcp_observations_test.go b/apps/daemon/internal/agent/mcode/mcp_observations_test.go index 78c94b467..dba30bf7a 100644 --- a/apps/daemon/internal/agent/mcode/mcp_observations_test.go +++ b/apps/daemon/internal/agent/mcode/mcp_observations_test.go @@ -15,7 +15,7 @@ import ( func mcpObservationSession(t *testing.T) (*Session, chan proto.Envelope) { t.Helper() out := make(chan proto.Envelope, 16) - s := &Session{ctx: context.Background(), opts: launchOptions{DataDir: t.TempDir()}, + s := &Session{ctx: context.Background(), outputContext: context.Background(), opts: launchOptions{DataDir: t.TempDir()}, req: proto.PromptRequestPayload{RunID: "run", LocalEnvironment: &proto.LocalEnvironment{NetworkAccess: "enabled", MCP: []proto.EnvironmentMCP{environmentMCPFixture()}}}, out: out, tools: map[string]toolUpdate{}, completedTools: map[string]bool{}, active: true, sessionID: "native-session"} diff --git a/apps/daemon/internal/agent/mcode/native_history_test.go b/apps/daemon/internal/agent/mcode/native_history_test.go index 98c7c6a29..e438e2ff7 100644 --- a/apps/daemon/internal/agent/mcode/native_history_test.go +++ b/apps/daemon/internal/agent/mcode/native_history_test.go @@ -7,12 +7,11 @@ import ( "strings" "testing" "time" - - "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) // A successful public real-model run supplies an actual foreign native ID. -// Neither that history nor a missing ID may silently become a new session. +// Neither that history nor a missing ID may prepare an Executor, so no Turn can +// run against it or silently replace it with a new session. func TestNativeMCodeHistoryIsolation(t *testing.T) { binary, options, foreign := os.Getenv("OAC_RUNTIME_MCODE_BIN"), os.Getenv("OAC_TEST_MCODE_REAL_OPTIONS"), os.Getenv("OAC_TEST_MCODE_FOREIGN_NATIVE_ID") if binary == "" || options == "" || foreign == "" { @@ -28,37 +27,11 @@ func TestNativeMCodeHistoryIsolation(t *testing.T) { if json.Unmarshal(raw, &req) != nil { t.Fatal("invalid private options") } - req.AgentSessionID, req.Input = id, proto.TextInput("This input must never execute.") + req.AgentSessionID = id ctx, cancel := context.WithTimeout(t.Context(), 90*time.Second) defer cancel() - out := make(chan proto.Envelope, 64) - session, err := newSession(ctx, req, out, binary) - if err != nil { - t.Fatal(err) - } - defer func() { - cleanup, stop := context.WithTimeout(context.Background(), 10*time.Second) - defer stop() - if err := session.Cancel(cleanup); err != nil { - t.Error(err) - } - }() - rejected := false - for event := range out { - if event.Type == proto.TypeError { - var failure proto.ErrorPayload - _ = json.Unmarshal(event.Payload, &failure) - rejected = strings.Contains(failure.Error, "session/load:") && !strings.Contains(failure.Error, "deadline exceeded") - } - if event.Type == proto.TypeDone { - var done proto.DonePayload - _ = json.Unmarshal(event.Payload, &done) - if done.Content != "" || done.Metadata[proto.DoneMetaAgentSessionID] != nil { - t.Fatal("unowned history executed or silently replaced") - } - } - } - if !rejected { + e, err := prepareExecutor(t, ctx, req) + if e != nil || err == nil || !strings.Contains(err.Error(), "session/load:") || strings.Contains(err.Error(), "deadline exceeded") { t.Fatal("native history was not explicitly rejected") } }) diff --git a/apps/daemon/internal/agent/mcode/native_test.go b/apps/daemon/internal/agent/mcode/native_test.go index f23a0b65a..eb43e7cf3 100644 --- a/apps/daemon/internal/agent/mcode/native_test.go +++ b/apps/daemon/internal/agent/mcode/native_test.go @@ -23,6 +23,7 @@ func TestNativeMCodeACP(t *testing.T) { t.Skip("set OAC_TEST_MCODE_INTEGRATION_BIN to run native ACP smoke test") } req := testRequest(t) + t.Setenv("OAC_RUNTIME_MCODE_BIN", binary) var mu sync.Mutex var requests []string model := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -45,18 +46,9 @@ func TestNativeMCodeACP(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) t.Cleanup(cancel) out := make(chan proto.Envelope, 64) - session, err := newSession(ctx, req, out, binary) - if err != nil { + if _, err := startTurn(t, ctx, req, out); err != nil { t.Fatal(err) } - t.Cleanup(func() { - _ = session.Cancel(context.Background()) - select { - case <-session.exited: - case <-time.After(5 * time.Second): - t.Error("native CLI did not stop") - } - }) var done proto.DonePayload for event := range out { if event.Type == proto.TypeError { diff --git a/apps/daemon/internal/agent/mcode/options.go b/apps/daemon/internal/agent/mcode/options.go index cd5da0d56..2f3f93414 100644 --- a/apps/daemon/internal/agent/mcode/options.go +++ b/apps/daemon/internal/agent/mcode/options.go @@ -90,9 +90,6 @@ func validateOptions(req proto.PromptRequestPayload) (harnessconfig.PreparedConf if err := validateExecutionRequest(req); err != nil { return prepared, err } - if req.Input.HasImages() { - return prepared, fmt.Errorf("mcode: ACP does not support attachments") - } return prepared, nil } diff --git a/apps/daemon/internal/agent/mcode/options_test.go b/apps/daemon/internal/agent/mcode/options_test.go index 13099f019..59a8ea559 100644 --- a/apps/daemon/internal/agent/mcode/options_test.go +++ b/apps/daemon/internal/agent/mcode/options_test.go @@ -68,9 +68,6 @@ func TestOptionsRejectDroppedContext(t *testing.T) { edit func(*proto.PromptRequestPayload) }{ {"oversized instructions", func(r *proto.PromptRequestPayload) { r.SystemPrompt = strings.Repeat("x", 32*1024+1) }}, - {"attachment", func(r *proto.PromptRequestPayload) { - r.Input = proto.MessageInput{{Content: []proto.InputContent{{Type: "input_image"}}}} - }}, {"missing model", func(r *proto.PromptRequestPayload) { r.Model = "" }}, {"missing provider", func(r *proto.PromptRequestPayload) { r.ModelProvider = nil }}, } @@ -85,25 +82,6 @@ func TestOptionsRejectDroppedContext(t *testing.T) { } } -func TestQuestionContentPreservesTypesAndValidates(t *testing.T) { - pending := pendingQuestion{Properties: map[string]formProperty{"text": {Type: "string"}, "choice": {Type: "string", Options: []formOption{{Value: "eu", Title: "Europe"}}}}, Required: []string{"choice"}} - if _, err := questionContent(pending, proto.PromptForUserChoiceDecisionPayload{}); err == nil { - t.Fatal("required answer accepted empty") - } - decision := proto.PromptForUserChoiceDecisionPayload{QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "text", Answers: []string{"custom"}}, {QuestionID: "choice", Answers: []string{"Europe"}}}} - content, err := questionContent(pending, decision) - if err != nil { - t.Fatal(err) - } - if content["choice"] != "eu" || content["text"] != "custom" { - t.Fatalf("answers=%v", content) - } - decision.QuestionAnswers[1].Answers = []string{"unoffered"} - if _, err := questionContent(pending, decision); err == nil { - t.Fatal("unoffered choice accepted") - } -} - func TestDataDirectoryRequiresAgentState(t *testing.T) { home := t.TempDir() t.Setenv("OAC_RUNTIME_HOME", home) diff --git a/apps/daemon/internal/agent/mcode/protocol.go b/apps/daemon/internal/agent/mcode/protocol.go index 95c9f629a..116d8c024 100644 --- a/apps/daemon/internal/agent/mcode/protocol.go +++ b/apps/daemon/internal/agent/mcode/protocol.go @@ -50,18 +50,3 @@ type sessionUpdate struct { toolUpdate } `json:"update"` } - -type permissionRequest struct { - SessionID string `json:"sessionId"` - ToolCall toolUpdate `json:"toolCall"` - Options []struct { - ID string `json:"optionId"` - Kind string `json:"kind"` - } `json:"options"` -} - -type pendingPermission struct { - RPCID json.RawMessage - Allow string - Deny string -} diff --git a/apps/daemon/internal/agent/mcode/questions.go b/apps/daemon/internal/agent/mcode/questions.go deleted file mode 100644 index b61721d97..000000000 --- a/apps/daemon/internal/agent/mcode/questions.go +++ /dev/null @@ -1,217 +0,0 @@ -package mcode - -import ( - "context" - "encoding/json" - "fmt" - "sort" - "strings" - - "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - "github.com/google/uuid" -) - -type formOption struct { - Value string `json:"const"` - Title string `json:"title"` - Description string `json:"description"` -} - -type formProperty struct { - Type string `json:"type"` - Title string `json:"title"` - Description string `json:"description"` - Options []formOption `json:"oneOf"` - Items struct { - Options []formOption `json:"anyOf"` - } `json:"items"` -} - -type pendingQuestion struct { - RPCID json.RawMessage - Properties map[string]formProperty - Required []string - OtherFields map[string]string -} - -func (s *Session) askQuestion(frame rpcFrame) error { - var request struct { - SessionID string `json:"sessionId"` - Mode string `json:"mode"` - Schema struct { - Type string `json:"type"` - Properties map[string]formProperty `json:"properties"` - Required []string `json:"required"` - } `json:"requestedSchema"` - } - if err := json.Unmarshal(frame.Params, &request); err != nil { - return fmt.Errorf("mcode: invalid input request") - } - if request.SessionID != s.sessionID { - return fmt.Errorf("mcode: input request belongs to another session") - } - if request.Mode != "form" || request.Schema.Type != "object" || len(request.Schema.Properties) == 0 { - return s.write(rpcFrame{JSONRPC: "2.0", ID: frame.ID, Error: &rpcError{Code: -32602, Message: "OpenAgentCore requires a nonempty input form"}}) - } - otherFields := questionnaireOtherFields(request.Schema.Properties) - for _, key := range otherFields { - delete(request.Schema.Properties, key) - } - keys := make([]string, 0, len(request.Schema.Properties)) - for key := range request.Schema.Properties { - keys = append(keys, key) - } - sort.Strings(keys) - questions := make([]proto.PromptForUserChoiceQuestion, 0, len(keys)) - for _, key := range keys { - property := request.Schema.Properties[key] - if property.Type != "string" && property.Type != "array" { - return s.write(rpcFrame{JSONRPC: "2.0", ID: frame.ID, Error: &rpcError{Code: -32602, Message: "OpenAgentCore supports text and choice input fields"}}) - } - question := proto.PromptForUserChoiceQuestion{ID: key, Question: property.Title, MultiSelect: property.Type == "array", Options: []proto.PromptForUserChoiceOption{}} - if question.Question == "" { - question.Question = key - } - if property.Description != "" { - question.Question += "\n" + property.Description - } - options := property.Options - if question.MultiSelect { - options = property.Items.Options - } - labels := map[string]bool{} - for _, option := range options { - label := option.Title - if label == "" { - label = option.Value - } - if labels[label] { - return fmt.Errorf("mcode: input choices have ambiguous labels") - } - labels[label] = true - question.Options = append(question.Options, proto.PromptForUserChoiceOption{Label: label, Description: option.Description}) - } - question.IsOther = len(options) == 0 || otherFields[key] != "" - questions = append(questions, question) - } - id := "ask_" + uuid.NewString() - s.mu.Lock() - s.questions[id] = pendingQuestion{RPCID: frame.ID, Properties: request.Schema.Properties, Required: request.Schema.Required, OtherFields: otherFields} - s.mu.Unlock() - s.emit(proto.TypePromptForUserChoice, proto.PromptForUserChoicePayload{AskID: id, Questions: questions}) - return nil -} - -// Native questionnaires encode Other as a companion field, not a second question. -func questionnaireOtherFields(properties map[string]formProperty) map[string]string { - fields := map[string]string{} - for key, property := range properties { - if len(property.Options) == 0 && len(property.Items.Options) == 0 { - continue - } - for candidate, other := range properties { - if strings.HasPrefix(candidate, key+"__other") && strings.Trim(strings.TrimPrefix(candidate, key+"__other"), "_") == "" && other.Type == "string" && len(other.Options) == 0 && other.Title == property.Title+" — Other" { - fields[key] = candidate - break - } - } - } - return fields -} - -func (s *Session) SubmitPromptForUserChoice(_ context.Context, id string, decision proto.PromptForUserChoiceDecisionPayload) error { - s.mu.Lock() - defer s.mu.Unlock() - pending, ok := s.questions[id] - if !ok { - return agent.ErrUnknownAsk - } - if err := decision.Validate(); err != nil { - return err - } - response := map[string]any{"action": "cancel"} - if !decision.Cancelled { - content, err := questionContent(pending, decision) - if err != nil { - return err - } - response = map[string]any{"action": "accept", "content": content} - } - raw, _ := json.Marshal(response) - if err := s.write(rpcFrame{JSONRPC: "2.0", ID: pending.RPCID, Result: raw}); err != nil { - return err - } - delete(s.questions, id) - return nil -} - -func questionContent(pending pendingQuestion, decision proto.PromptForUserChoiceDecisionPayload) (map[string]any, error) { - ids := make([]string, 0, len(pending.Properties)) - for id := range pending.Properties { - ids = append(ids, id) - } - if _, err := decision.AnswersFor(ids); err != nil { - return nil, err - } - content := map[string]any{} - for _, answer := range decision.QuestionAnswers { - property, ok := pending.Properties[answer.QuestionID] - if !ok { - return nil, fmt.Errorf("mcode: unknown input field") - } - values := append([]string{}, answer.Answers...) - if len(values) == 0 { - continue - } - options := property.Options - if property.Type == "array" { - options = property.Items.Options - } else if len(values) != 1 { - return nil, fmt.Errorf("mcode: input field requires a single answer") - } - selected := make([]string, 0, len(values)) - for _, value := range values { - if len(options) == 0 { - selected = append(selected, value) - continue - } - found := false - for _, option := range options { - label := option.Title - if label == "" { - label = option.Value - } - if value == label { - selected = append(selected, option.Value) - found = true - break - } - } - if !found { - other := pending.OtherFields[answer.QuestionID] - if other == "" { - return nil, fmt.Errorf("mcode: answer is not an offered choice") - } - if _, exists := content[other]; exists { - return nil, fmt.Errorf("mcode: input field requires a single custom answer") - } - content[other] = value - } - } - if len(selected) == 0 { - continue - } - if property.Type == "array" { - content[answer.QuestionID] = selected - } else { - content[answer.QuestionID] = selected[0] - } - } - for _, key := range pending.Required { - if _, ok := content[key]; !ok { - return nil, fmt.Errorf("mcode: required input is missing") - } - } - return content, nil -} diff --git a/apps/daemon/internal/agent/mcode/questions_test.go b/apps/daemon/internal/agent/mcode/questions_test.go deleted file mode 100644 index c46562977..000000000 --- a/apps/daemon/internal/agent/mcode/questions_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package mcode - -import ( - "encoding/json" - "reflect" - "testing" - - "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" -) - -func TestQuestionnaireOtherUsesOneQuestion(t *testing.T) { - for _, multiple := range []bool{false, true} { - property := formProperty{Type: "string", Title: "Region?", Options: []formOption{{Value: "eu", Title: "Europe"}}} - if multiple { - property.Type = "array" - property.Items.Options = property.Options - property.Options = nil - } - properties := map[string]formProperty{"region": property, "region__other": {Type: "string", Title: "Region? — Other"}} - params, _ := json.Marshal(map[string]any{"sessionId": "native-1", "mode": "form", "requestedSchema": map[string]any{"type": "object", "properties": properties}}) - out := make(chan proto.Envelope, 1) - session := &Session{ctx: t.Context(), sessionID: "native-1", out: out, questions: map[string]pendingQuestion{}} - if err := session.askQuestion(rpcFrame{ID: json.RawMessage(`1`), Params: params}); err != nil { - t.Fatal(err) - } - var request proto.PromptForUserChoicePayload - _ = json.Unmarshal((<-out).Payload, &request) - if len(request.Questions) != 1 || !request.Questions[0].IsOther || request.Questions[0].MultiSelect != multiple { - t.Fatalf("unexpected questions: %#v", request.Questions) - } - pending := session.questions[request.AskID] - for _, custom := range []bool{false, true} { - answer := "Europe" - want := map[string]any{"region": "eu"} - if multiple { - want["region"] = []string{"eu"} - } - if custom { - answer = "Asia" - want = map[string]any{"region__other": "Asia"} - } - got, err := questionContent(pending, proto.PromptForUserChoiceDecisionPayload{QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "region", Answers: []string{answer}}}}) - if err != nil || !reflect.DeepEqual(got, want) { - t.Fatalf("multiple=%t custom=%t: got=%v err=%v", multiple, custom, got, err) - } - } - if multiple { - got, err := questionContent(pending, proto.PromptForUserChoiceDecisionPayload{QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "region", Answers: []string{"Europe", "Asia"}}}}) - want := map[string]any{"region": []string{"eu"}, "region__other": "Asia"} - if err != nil || !reflect.DeepEqual(got, want) { - t.Fatalf("combined choice and custom: got=%v err=%v", got, err) - } - } - } -} diff --git a/apps/daemon/internal/agent/mcode/session.go b/apps/daemon/internal/agent/mcode/session.go index 91766f1d5..ec43b1482 100644 --- a/apps/daemon/internal/agent/mcode/session.go +++ b/apps/daemon/internal/agent/mcode/session.go @@ -3,7 +3,6 @@ package mcode import ( "context" "encoding/json" - "errors" "fmt" "net/url" "slices" @@ -21,65 +20,39 @@ type Session struct { req proto.PromptRequestPayload opts launchOptions *connection - executor *executor - settlement agent.TurnSettlement - settlementErr error - settled chan struct{} - inputDone chan struct{} - outputCancel context.CancelFunc - operations sync.WaitGroup - closing bool - cancelled bool - inputUncertain bool - out chan<- proto.Envelope - frames chan rpcFrame - finished chan struct{} - mu sync.Mutex - sessionID string - nativeModel string - outputContext context.Context - outcome proto.DonePayload - permissions map[string]pendingPermission - questions map[string]pendingQuestion - steeringReady bool - steeringTurn string - sequence uint64 - active bool - content strings.Builder - tools map[string]toolUpdate - completedTools map[string]bool - previousNativeTurns map[string]bool - subagentSettlementError error - rootCompletedAtMS *int64 - subagentHistoryReady bool + executor *executor + settlement agent.TurnSettlement + settlementErr error + settled chan struct{} + inputDone chan struct{} + outputCancel context.CancelFunc + operations sync.WaitGroup + closing bool + cancelled bool + inputUncertain bool + out chan<- proto.Envelope + frames chan rpcFrame + finished chan struct{} + mu sync.Mutex + sessionID string + nativeModel string + outputContext context.Context + outcome proto.DonePayload + steeringReady bool + steeringTurn string + sequence uint64 + active bool + content strings.Builder + tools map[string]toolUpdate + completedTools map[string]bool + previousNativeTurns map[string]bool + rootCompletedAtMS *int64 + subagentHistoryReady bool } var _ agent.Session = (*Session)(nil) -func Factory(ctx context.Context, req proto.PromptRequestPayload, out chan<- proto.Envelope) (agent.Session, error) { - return newSession(ctx, req, out, defaultBinary()) -} - -func newSession(ctx context.Context, req proto.PromptRequestPayload, out chan<- proto.Envelope, binary string) (*Session, error) { - if ctx == nil { - ctx = context.Background() - } - if out == nil { - return nil, fmt.Errorf("mcode: output channel is required") - } - opts, err := prepareOptions(req) - if err != nil { - return nil, err - } - s, err := launch(ctx, req, opts, binary, out) - if err != nil { - return nil, err - } - go s.run() - return s, nil -} - -func launch(ctx context.Context, req proto.PromptRequestPayload, opts launchOptions, binary string, out chan<- proto.Envelope) (*Session, error) { +func launch(ctx context.Context, req proto.PromptRequestPayload, opts launchOptions, binary string) (*Session, error) { start, args := opts.start, []string{"acp"} if start == nil { start = clirunner.Start @@ -92,77 +65,14 @@ func launch(ctx context.Context, req proto.PromptRequestPayload, opts launchOpti return nil, err } c := &connection{process: process, exited: make(chan struct{}), responses: map[string]chan rpcFrame{}} - s := newTurnSession(ctx, req, opts, c, out) + s := newTurnSession(ctx, req, opts, c, nil) c.current = s go c.read() return s, nil } func newTurnSession(ctx context.Context, req proto.PromptRequestPayload, opts launchOptions, c *connection, out chan<- proto.Envelope) *Session { - return &Session{ctx: ctx, req: req, opts: opts, connection: c, out: out, frames: make(chan rpcFrame, 32), finished: make(chan struct{}), permissions: map[string]pendingPermission{}, questions: map[string]pendingQuestion{}, tools: map[string]toolUpdate{}, completedTools: map[string]bool{}} -} - -func (s *Session) run() { - defer func() { - if s.out != nil { - close(s.out) - } - }() - defer close(s.finished) - err := s.prepareNative() - if err == nil { - if !s.req.DisableSubagents { - var snapshot nativeSubagentSnapshot - snapshot, err = s.readSubagents(s.ctx) - s.subagentHistoryReady = err == nil - s.previousNativeTurns = map[string]bool{} - for _, session := range snapshot.Sessions { - if session.ID == s.sessionID { - for _, turn := range session.Turns { - s.previousNativeTurns[turn.ID] = true - } - } - } - } - } - if err == nil { - err = s.executePrompt() - } - if !s.req.DisableSubagents && s.subagentHistoryReady && s.out != nil { - observationErr := s.settleSubagents() - s.mu.Lock() - s.subagentSettlementError = observationErr - s.mu.Unlock() - if observationErr != nil { - err = observationErr - } - } - if err != nil { - s.process.Cancel() - <-s.exited - } - s.finishEnvironmentMCP() - if s.out == nil { - return - } - s.mu.Lock() - s.steeringReady = false - s.mu.Unlock() - if err != nil { - s.process.Cancel() - s.emit(proto.TypeError, proto.ErrorPayload{Error: err.Error()}) - } - s.mu.Lock() - sessionID := s.sessionID - s.permissions = map[string]pendingPermission{} - s.questions = map[string]pendingQuestion{} - s.mu.Unlock() - metadata := map[string]any{proto.DoneMetaAgentSessionType: "mcode"} - if sessionID != "" { - metadata[proto.DoneMetaAgentSessionID] = sessionID - } - // ACP context usage is not per-turn token usage; do not record it as spend. - s.emit(proto.TypeDone, proto.DonePayload{Content: s.content.String(), Metadata: metadata, SourceCompletedAtMS: s.rootCompletedAtMS}) + return &Session{ctx: ctx, req: req, opts: opts, connection: c, out: out, frames: make(chan rpcFrame, 32), finished: make(chan struct{}), tools: map[string]toolUpdate{}, completedTools: map[string]bool{}} } func (s *Session) prepareNative() error { @@ -175,7 +85,7 @@ func (s *Session) prepareNative() error { } `json:"oac/subagents"` } `json:"_meta"` } - if err := s.call("initialize", map[string]any{"protocolVersion": 1, "clientInfo": map[string]string{"name": "oac", "version": "1"}, "clientCapabilities": map[string]any{"elicitation": map[string]any{"form": map[string]any{}}}}, &initialized, false); err != nil { + if err := s.call("initialize", map[string]any{"protocolVersion": 1, "clientInfo": map[string]string{"name": "oac", "version": "1"}}, &initialized, false); err != nil { return err } if initialized.ProtocolVersion != 1 { @@ -219,16 +129,12 @@ func (s *Session) prepareNative() error { return nil } -func (s *Session) executePrompt() error { - prompt, err := s.req.Input.TextOnly() - if err != nil { - return err - } +func (s *Session) executePrompt(prompt string) error { s.active = true var result struct { StopReason string `json:"stopReason"` } - err = s.call("session/prompt", map[string]any{"sessionId": s.sessionID, "prompt": promptContent(prompt)}, &result, true) + err := s.call("session/prompt", map[string]any{"sessionId": s.sessionID, "prompt": promptContent(prompt)}, &result, true) s.active = false s.mu.Lock() s.steeringReady = false @@ -282,7 +188,7 @@ func (s *Session) call(method string, params any, result any, prompt bool) error if err != nil { return err } - if prompt && s.executor != nil { + if prompt { // Serialize the admission check with the wire, but release the owner // mutex before a pipe write so cancellation and Close can stop it. s.connection.writeMu.Lock() @@ -344,10 +250,6 @@ func (s *Session) call(method string, params any, result any, prompt bool) error } func (s *Session) emit(kind string, payload any) { - ctx := s.ctx - if s.outputContext != nil { - ctx = s.outputContext - } env, err := proto.NewEnvelope(kind, s.req.RunID, payload) if err != nil { return @@ -359,55 +261,6 @@ func (s *Session) emit(kind string, payload any) { } select { case s.out <- env: - case <-ctx.Done(): - } -} - -func (s *Session) Cancel(ctx context.Context) error { - if s.executor != nil { - return s.cancelTurn(ctx) - } - if !s.req.DisableSubagents { - if err := s.cancelSubagents(ctx); err != nil { - s.process.Cancel() - return err - } + case <-s.outputContext.Done(): } - s.process.Cancel() - select { - case <-s.exited: - case <-ctx.Done(): - return ctx.Err() - } - select { - case <-s.finished: - return nil - case <-ctx.Done(): - return ctx.Err() - } -} - -func (s *Session) SubmitPermission(_ context.Context, id string, decision proto.PermissionDecisionPayload) error { - s.mu.Lock() - defer s.mu.Unlock() - pending, ok := s.permissions[id] - if !ok { - return agent.ErrUnknownPermission - } - choice := pending.Deny - if decision.Approved { - choice = pending.Allow - } - if choice == "" { - return errors.New("mcode: ACP permission option is unavailable") - } - if len(decision.UpdatedInput) > 0 { - return errors.New("mcode: edited permission input is not supported") - } - raw, _ := json.Marshal(map[string]any{"outcome": map[string]string{"outcome": "selected", "optionId": choice}}) - if err := s.write(rpcFrame{JSONRPC: "2.0", ID: pending.RPCID, Result: raw}); err != nil { - return err - } - delete(s.permissions, id) - return nil } diff --git a/apps/daemon/internal/agent/mcode/session_test.go b/apps/daemon/internal/agent/mcode/session_test.go index 7e72086b8..40d3b443d 100644 --- a/apps/daemon/internal/agent/mcode/session_test.go +++ b/apps/daemon/internal/agent/mcode/session_test.go @@ -4,14 +4,12 @@ import ( "bufio" "context" "encoding/json" - "errors" "os" "path/filepath" "strings" "testing" "time" - "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" "github.com/MiniMax-AI/OpenAgentCore/internal/modelprovider" ) @@ -25,7 +23,8 @@ func testRequest(t *testing.T) proto.PromptRequestPayload { DisableExecutionEnvironment: true, DisableSubagents: true, ExecutionControls: &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "medium"}} } -func helperSession(t *testing.T, scenario string, resume bool) (*Session, <-chan proto.Envelope) { +// helperRequest selects a protocol fixture scenario as the native CLI. +func helperRequest(t *testing.T, scenario string, resume bool) proto.PromptRequestPayload { t.Helper() req := testRequest(t) if resume { @@ -41,21 +40,54 @@ func helperSession(t *testing.T, scenario string, resume bool) (*Session, <-chan if err := os.WriteFile(binary, []byte(script), 0700); err != nil { t.Fatal(err) } + t.Setenv("OAC_RUNTIME_MCODE_BIN", binary) + return req +} + +// prepareExecutor prepares req without its Turn input; cleanup closes the +// Executor and reaps its CLI. +func prepareExecutor(t *testing.T, ctx context.Context, req proto.PromptRequestPayload) (*executor, error) { + t.Helper() + req.RunID, req.Input = "", nil + value, err := NewExecutorFactory(nil)(ctx, req) + if value == nil { + return nil, err + } + e := value.(*executor) + t.Cleanup(func() { + cleanup, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + if err := e.Close(cleanup); err != nil { + t.Error("CLI was not reaped:", err) + } + }) + return e, err +} + +// startTurn prepares an Executor for req and starts req.Input as its Turn. +func startTurn(t *testing.T, ctx context.Context, req proto.PromptRequestPayload, out chan<- proto.Envelope) (*Session, error) { + t.Helper() + e, err := prepareExecutor(t, ctx, req) + if err != nil { + return nil, err + } + turn, err := e.StartTurn(ctx, req.RunID, req.Input, out) + if err != nil { + return nil, err + } + return turn.(*Session), nil +} + +func helperSession(t *testing.T, scenario string, resume bool) (*Session, <-chan proto.Envelope) { + t.Helper() + req := helperRequest(t, scenario, resume) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) out := make(chan proto.Envelope, 32) - session, err := newSession(ctx, req, out, binary) + session, err := startTurn(t, ctx, req, out) if err != nil { t.Fatal(err) } - t.Cleanup(func() { - _ = session.Cancel(context.Background()) - select { - case <-session.exited: - case <-time.After(3 * time.Second): - t.Error("CLI was not reaped") - } - }) return session, out } @@ -97,39 +129,20 @@ func TestSessionStreamsCurrentTurnAndResumes(t *testing.T) { } } -func TestSessionInteractionRoundTrip(t *testing.T) { - for _, approved := range []bool{true, false} { - t.Run(map[bool]string{true: "allow", false: "deny"}[approved], func(t *testing.T) { - session, out := helperSession(t, "interaction", false) - permissions, questions := 0, 0 - for event := range out { - switch event.Type { - case proto.TypeError: - t.Fatalf("error: %s", event.Payload) - case proto.TypePermissionRequest: - permissions++ - var p proto.PermissionRequestPayload - _ = json.Unmarshal(event.Payload, &p) - if err := session.SubmitPermission(context.Background(), p.RequestID, proto.PermissionDecisionPayload{Approved: approved}); err != nil { - t.Fatal(err) - } - if err := session.SubmitPermission(context.Background(), p.RequestID, proto.PermissionDecisionPayload{Approved: true}); !errors.Is(err, agent.ErrUnknownPermission) { - t.Fatalf("duplicate approval: %v", err) - } - case proto.TypePromptForUserChoice: - questions++ - var p proto.PromptForUserChoicePayload - _ = json.Unmarshal(event.Payload, &p) - decision := proto.PromptForUserChoiceDecisionPayload{QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "region", Answers: []string{"Europe"}}}} - if err := session.SubmitPromptForUserChoice(context.Background(), p.AskID, decision); err != nil { - t.Fatal(err) - } - } - } - if permissions != 1 || questions != 1 { - t.Fatalf("permissions=%d questions=%d", permissions, questions) - } - }) +// Harnesses run unattended: native asks are declined in the adapter and the turn completes. +func TestSessionDeclinesNativeAsks(t *testing.T) { + _, out := helperSession(t, "unattended", false) + done := 0 + for event := range out { + switch event.Type { + case proto.TypeError: + t.Fatalf("error: %s", event.Payload) + case proto.TypeDone: + done++ + } + } + if done != 1 { + t.Fatalf("done=%d", done) } } @@ -150,34 +163,41 @@ func TestResumeSelectsModelWhenNativeSelectorIsMissing(t *testing.T) { } func TestSessionFailuresAreReported(t *testing.T) { - for _, scenario := range []string{"malformed", "exit", "rpc-error", "unknown-model"} { + for _, scenario := range []string{"malformed", "exit", "unknown-model"} { t.Run(scenario, func(t *testing.T) { - _, out := helperSession(t, scenario, false) - reported := false - for event := range out { - if event.Type == proto.TypeError { - reported = true - } - } - if !reported { - t.Fatal("failure was not emitted") + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) + defer cancel() + if e, err := prepareExecutor(t, ctx, helperRequest(t, scenario, false)); err == nil || e != nil { + t.Fatal("preparation failure was not reported", err) } }) } + t.Run("rpc-error", func(t *testing.T) { + _, out := helperSession(t, "rpc-error", false) + reported := false + for event := range out { + if event.Type == proto.TypeError { + reported = true + } + } + if !reported { + t.Fatal("failure was not emitted") + } + }) } -func TestCancelStopsWaitingCLI(t *testing.T) { - session, out := helperSession(t, "hang", false) - if err := session.Cancel(context.Background()); err != nil { - t.Fatal(err) +func TestPreparationCancellationStopsWaitingCLI(t *testing.T) { + req := helperRequest(t, "hang", false) + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + time.AfterFunc(100*time.Millisecond, cancel) + started := time.Now() + if e, err := prepareExecutor(t, ctx, req); err == nil || e != nil { + t.Fatal("cancelled preparation retained the CLI", err) } - select { - case <-session.exited: - case <-time.After(3 * time.Second): + if time.Since(started) > 3*time.Second { t.Fatal("cancel hung") } - for range out { - } } func TestMCodeProcess(t *testing.T) { @@ -233,6 +253,9 @@ func TestMCodeProcess(t *testing.T) { result := any(map[string]any{}) switch frame.Method { case "initialize": + if scenario == "unattended" && strings.Contains(string(frame.Params), "elicitation") { + os.Exit(7) + } result = map[string]int{"protocolVersion": 1} case "session/new", "session/load": if scenario == "prepared-mcp-cancel" { @@ -317,9 +340,9 @@ func TestMCodeProcess(t *testing.T) { send(rpcFrame{JSONRPC: "2.0", ID: frame.ID, Error: &rpcError{Code: -32603, Message: "Fixture provider unavailable"}}) continue } - if scenario == "interaction" { + if scenario == "unattended" { promptID = frame.ID - send(map[string]any{"jsonrpc": "2.0", "id": "permission-1", "method": "session/request_permission", "params": map[string]any{"sessionId": "native-1", "toolCall": map[string]any{"toolCallId": "tool-1", "name": "Bash", "title": "Run fixture", "rawInput": map[string]any{"command": "echo fixture"}}, "options": []map[string]string{{"optionId": "once", "kind": "allow_once"}, {"optionId": "always", "kind": "allow_always"}, {"optionId": "deny", "kind": "reject_once"}}}}) + send(map[string]any{"jsonrpc": "2.0", "id": "permission-1", "method": "session/request_permission", "params": map[string]any{"sessionId": "native-1", "toolCall": map[string]any{"toolCallId": "tool-1", "name": "Bash", "title": "Run fixture", "rawInput": map[string]any{"command": "echo fixture"}}, "options": []map[string]string{{"optionId": "once", "kind": "allow_once"}, {"optionId": "deny", "kind": "reject_once"}}}}) continue } update("agent_message_chunk", map[string]any{"content": map[string]string{"type": "text", "text": "Hello "}}) @@ -358,21 +381,15 @@ func TestMCodeProcess(t *testing.T) { if string(frame.ID) == `"permission-1"` { var reply struct { Outcome struct { - Option string `json:"optionId"` + Outcome string `json:"outcome"` } `json:"outcome"` } - _ = json.Unmarshal(frame.Result, &reply) - if reply.Outcome.Option != "once" && reply.Outcome.Option != "deny" { + if json.Unmarshal(frame.Result, &reply) != nil || reply.Outcome.Outcome != "cancelled" { os.Exit(5) } - send(map[string]any{"jsonrpc": "2.0", "id": "question-1", "method": "elicitation/create", "params": map[string]any{"sessionId": "native-1", "mode": "form", "requestedSchema": map[string]any{"type": "object", "required": []string{"region"}, "properties": map[string]any{"region": map[string]any{"type": "string", "title": "Region?", "oneOf": []map[string]string{{"const": "eu", "title": "Europe"}, {"const": "us", "title": "America"}}}}}}}) + send(map[string]any{"jsonrpc": "2.0", "id": "question-1", "method": "elicitation/create", "params": map[string]any{"sessionId": "native-1", "mode": "form", "requestedSchema": map[string]any{"type": "object", "properties": map[string]any{"region": map[string]any{"type": "string"}}}}}) } else { - var reply struct { - Action string `json:"action"` - Content map[string]string `json:"content"` - } - _ = json.Unmarshal(frame.Result, &reply) - if reply.Action != "accept" || reply.Content["region"] != "eu" { + if frame.Error == nil || frame.Error.Code != -32601 { os.Exit(6) } raw, _ := json.Marshal(map[string]string{"stopReason": "end_turn"}) diff --git a/apps/daemon/internal/agent/mcode/steering.go b/apps/daemon/internal/agent/mcode/steering.go index bff6f079f..005f5c859 100644 --- a/apps/daemon/internal/agent/mcode/steering.go +++ b/apps/daemon/internal/agent/mcode/steering.go @@ -18,16 +18,14 @@ func (s *Session) Steer(ctx context.Context, input proto.PromptSteerPayload) err // Native acceptance belongs to the active ACP Turn; it does not promise model consumption. func (s *Session) SteerWithReceipt(ctx context.Context, input proto.PromptSteerPayload, written func()) error { - if s.executor != nil { - s.mu.Lock() - if s.closing || s.cancelled { - s.mu.Unlock() - return agent.ErrSteeringInactive - } - s.operations.Add(1) + s.mu.Lock() + if s.closing || s.cancelled { s.mu.Unlock() - defer s.operations.Done() + return agent.ErrSteeringInactive } + s.operations.Add(1) + s.mu.Unlock() + defer s.operations.Done() text, err := input.Input.TextOnly() if strings.TrimSpace(input.InputID) == "" || err != nil { return agent.ErrSteeringRejected @@ -62,7 +60,7 @@ func (s *Session) SteerWithReceipt(ctx context.Context, input proto.PromptSteerP var stopped error select { case frame = <-response: - case <-s.inputSettlementDone(): + case <-s.inputDone: stopped = fmt.Errorf("mcode: run ended with unknown input outcome") case <-s.exited: stopped = fmt.Errorf("mcode: input transport closed") @@ -103,13 +101,6 @@ func (s *Session) SteerWithReceipt(ctx context.Context, input proto.PromptSteerP return nil } -func (s *Session) inputSettlementDone() <-chan struct{} { - if s.inputDone != nil { - return s.inputDone - } - return s.finished -} - func (s *Session) CancellationOutcome() proto.DonePayload { s.mu.Lock() defer s.mu.Unlock() diff --git a/apps/daemon/internal/agent/mcode/subagent_cancel.go b/apps/daemon/internal/agent/mcode/subagent_cancel.go index bd1b88e68..5efa5a031 100644 --- a/apps/daemon/internal/agent/mcode/subagent_cancel.go +++ b/apps/daemon/internal/agent/mcode/subagent_cancel.go @@ -6,21 +6,6 @@ import ( "fmt" ) -func (s *Session) cancelSubagents(ctx context.Context) error { - if err := s.stopSubagents(ctx); err != nil { - return err - } - select { - case <-s.finished: - s.mu.Lock() - err := s.subagentSettlementError - s.mu.Unlock() - return err - case <-ctx.Done(): - return ctx.Err() - } -} - func (s *Session) stopSubagents(ctx context.Context) error { select { case <-s.finished: diff --git a/apps/daemon/internal/agent/mcode/subagents_test.go b/apps/daemon/internal/agent/mcode/subagents_test.go index bccb15597..25a1773af 100644 --- a/apps/daemon/internal/agent/mcode/subagents_test.go +++ b/apps/daemon/internal/agent/mcode/subagents_test.go @@ -27,7 +27,7 @@ func childSnapshot(t *testing.T) nativeSubagentSnapshot { func TestSubagentSnapshotsKeepOwnHistoryAndStableNativeIdentity(t *testing.T) { project := func(snapshot nativeSubagentSnapshot) []proto.Envelope { out := make(chan proto.Envelope, 32) - s := &Session{ctx: context.Background(), out: out, previousNativeTurns: map[string]bool{}} + s := &Session{ctx: context.Background(), outputContext: context.Background(), out: out, previousNativeTurns: map[string]bool{}} if err := s.projectSubagents(snapshot); err != nil { t.Fatal(err) } @@ -63,7 +63,7 @@ func TestSubagentSnapshotsKeepOwnHistoryAndStableNativeIdentity(t *testing.T) { func TestSubagentSnapshotRejectsMissingParentProvenance(t *testing.T) { snapshot := childSnapshot(t) snapshot.Sessions[0].Tasks = nil - s := &Session{ctx: context.Background(), out: make(chan proto.Envelope, 32)} + s := &Session{ctx: context.Background(), outputContext: context.Background(), out: make(chan proto.Envelope, 32)} if err := s.projectSubagents(snapshot); err == nil { t.Fatal("accepted child without original spawning provenance") } diff --git a/apps/daemon/internal/agent/mcode/tool_observations_test.go b/apps/daemon/internal/agent/mcode/tool_observations_test.go index c7d1fa6da..703e3cb90 100644 --- a/apps/daemon/internal/agent/mcode/tool_observations_test.go +++ b/apps/daemon/internal/agent/mcode/tool_observations_test.go @@ -12,7 +12,7 @@ func TestWorkspaceCommandObservationsWaitForArgumentsAndRetainOutcome(t *testing for _, status := range []string{"completed", "failed"} { t.Run(status, func(t *testing.T) { out := make(chan proto.Envelope, 8) - s := &Session{ctx: context.Background(), req: proto.PromptRequestPayload{RunID: "run"}, out: out, tools: map[string]toolUpdate{}, completedTools: map[string]bool{}} + s := &Session{ctx: context.Background(), outputContext: context.Background(), req: proto.PromptRequestPayload{RunID: "run"}, out: out, tools: map[string]toolUpdate{}, completedTools: map[string]bool{}} s.emitTool(toolUpdate{ID: "call", Name: "mcp__oac_workspace__workspace_bash"}) if len(out) != 0 { t.Fatal("command item emitted before native arguments") diff --git a/apps/daemon/internal/agent/mcode/view_test.go b/apps/daemon/internal/agent/mcode/view_test.go index 7b6d3a953..f21a17dcb 100644 --- a/apps/daemon/internal/agent/mcode/view_test.go +++ b/apps/daemon/internal/agent/mcode/view_test.go @@ -48,7 +48,7 @@ func viewFixture(t *testing.T) (viewInstall, agent.View, proto.PromptRequestPayl registry := agent.NewRegistry() info := Declaration.Info info.Available = true - registry.Register(Declaration, agent.Runtime{Info: info, Session: Factory, View: &declared}) + registry.Register(Declaration, agent.Runtime{Info: info, View: &declared}) view, err := registry.ResolveView("mcode") if err != nil { t.Fatal(err) diff --git a/apps/daemon/internal/agent/registry.go b/apps/daemon/internal/agent/registry.go index f64f74fa3..19265955b 100644 --- a/apps/daemon/internal/agent/registry.go +++ b/apps/daemon/internal/agent/registry.go @@ -10,23 +10,12 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/internal/harnessconfig" ) -// ErrUnknownPermission is returned by PermissionResponder.SubmitPermission when -// the permID doesn't match any outstanding request. The router uses -// this to distinguish a benign race from a real forwarding failure. -var ErrUnknownPermission = errors.New("agent: unknown permission id") - -// ErrUnknownAsk is returned by UserChoiceResponder.SubmitPromptForUserChoice when -// the askID doesn't match any outstanding ask. Same race semantics as -// ErrUnknownPermission. -var ErrUnknownAsk = errors.New("agent: unknown ask id") - var ErrUnsupportedKind = errors.New("agent: unsupported agent_kind") -// Registry maps agent_kind → Factory and keeps the daemon-advertised -// capability descriptor for each kind. Safe for concurrent use. +// Registry keeps the daemon-advertised capability descriptor and execution +// factories for each agent_kind. Safe for concurrent use. type Registry struct { mu sync.RWMutex - factories map[string]Factory executors map[string]ExecutorFactory views map[string]View kinds map[string]proto.SupportedAgentKind @@ -35,7 +24,6 @@ type Registry struct { func NewRegistry() *Registry { return &Registry{ - factories: make(map[string]Factory), executors: make(map[string]ExecutorFactory), views: make(map[string]View), kinds: make(map[string]proto.SupportedAgentKind), @@ -43,37 +31,13 @@ func NewRegistry() *Registry { } } -// Resolve returns the factory for kind, or wraps ErrUnsupportedKind. -func (r *Registry) Resolve(kind string) (Factory, error) { - r.mu.RLock() - defer r.mu.RUnlock() - f, ok := r.factories[kind] - if !ok { - return nil, fmt.Errorf("%w: %q", ErrUnsupportedKind, kind) - } - return f, nil -} - -// Kinds returns the list of registered kinds sorted lexicographically. -func (r *Registry) Kinds() []string { - r.mu.RLock() - defer r.mu.RUnlock() - out := make([]string, 0, len(r.factories)) - for k := range r.factories { - out = append(out, k) - } - slices.Sort(out) - return out -} - // SupportedAgentKinds returns the daemon-advertised capability // descriptors sorted by kind so heartbeat payloads are stable. func (r *Registry) SupportedAgentKinds() []proto.SupportedAgentKind { r.mu.RLock() defer r.mu.RUnlock() - out := make([]proto.SupportedAgentKind, 0, len(r.factories)) - for kind := range r.factories { - info := r.kinds[kind] + out := make([]proto.SupportedAgentKind, 0, len(r.kinds)) + for _, info := range r.kinds { out = append(out, info) } slices.SortFunc(out, func(a, b proto.SupportedAgentKind) int { diff --git a/apps/daemon/internal/agent/registry_test.go b/apps/daemon/internal/agent/registry_test.go index 9614a7f7e..e2538065a 100644 --- a/apps/daemon/internal/agent/registry_test.go +++ b/apps/daemon/internal/agent/registry_test.go @@ -1,10 +1,11 @@ package agent_test +import "github.com/MiniMax-AI/OpenAgentCore/internal/harnessconfig" + import ( "context" "errors" "reflect" - "slices" "testing" "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" @@ -12,77 +13,14 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto/prototest" ) -func stubFactory(marker string) agent.Factory { - return func(_ context.Context, _ proto.PromptRequestPayload, _ chan<- proto.Envelope) (agent.Session, error) { - return stubSession{marker: marker}, nil - } -} - -type stubSession struct{ marker string } - -func (stubSession) CancellationOutcome() proto.DonePayload { return proto.DonePayload{} } - -func (stubSession) Cancel(context.Context) error { return nil } -func (stubSession) SubmitPermission(context.Context, string, proto.PermissionDecisionPayload) error { - return nil -} -func (stubSession) SubmitPromptForUserChoice(context.Context, string, proto.PromptForUserChoiceDecisionPayload) error { - return nil -} - -func TestRegistryResolveReturnsRegisteredFactory(t *testing.T) { - reg := agent.NewRegistry() - reg.RegisterKind(proto.SupportedAgentKind{Kind: "fake_alpha", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, prototest.ModelConfiguration(), stubFactory("cc")) - - f, err := reg.Resolve("fake_alpha") - if err != nil { - t.Fatalf("Resolve: %v", err) - } - sess, err := f(context.Background(), prototest.WithModel(proto.PromptRequestPayload{AgentKind: "fake_alpha"}), nil) - if err != nil { - t.Fatalf("factory: %v", err) - } - stub, ok := sess.(stubSession) - if !ok || stub.marker != "cc" { - t.Errorf("resolved factory returned %#v, want stubSession{marker:\"cc\"}", sess) - } -} - -func TestRegistryResolveUnknownKindReturnsTypedError(t *testing.T) { +func TestRegistryRegisterOverwritesDescriptor(t *testing.T) { reg := agent.NewRegistry() - reg.RegisterKind(proto.SupportedAgentKind{Kind: "fake_alpha", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, prototest.ModelConfiguration(), stubFactory("cc")) + reg.RegisterKind(proto.SupportedAgentKind{Kind: "k", Available: true, Version: "v1", Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, harnessconfig.Configuration{}) + reg.RegisterKind(proto.SupportedAgentKind{Kind: "k", Available: true, Version: "v2", Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, harnessconfig.Configuration{}) - _, err := reg.Resolve("fake_beta") - if !errors.Is(err, agent.ErrUnsupportedKind) { - t.Errorf("Resolve unknown = %v, want ErrUnsupportedKind chain", err) - } -} - -func TestRegistryRegisterOverwrites(t *testing.T) { - reg := agent.NewRegistry() - reg.RegisterKind(proto.SupportedAgentKind{Kind: "k", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, prototest.ModelConfiguration(), stubFactory("v1")) - reg.RegisterKind(proto.SupportedAgentKind{Kind: "k", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, prototest.ModelConfiguration(), stubFactory("v2")) - - f, err := reg.Resolve("k") - if err != nil { - t.Fatalf("Resolve: %v", err) - } - sess, _ := f(context.Background(), prototest.WithModel(proto.PromptRequestPayload{}), nil) - if got := sess.(stubSession).marker; got != "v2" { - t.Errorf("overwrite: marker = %q, want v2", got) - } -} - -func TestRegistryKindsReportsRegistered(t *testing.T) { - reg := agent.NewRegistry() - reg.RegisterKind(proto.SupportedAgentKind{Kind: "fake_alpha", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, prototest.ModelConfiguration(), stubFactory("cc")) - reg.RegisterKind(proto.SupportedAgentKind{Kind: "fake_beta", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, prototest.ModelConfiguration(), stubFactory("oc")) - - got := reg.Kinds() - slices.Sort(got) - want := []string{"fake_alpha", "fake_beta"} - if !slices.Equal(got, want) { - t.Errorf("Kinds = %v, want %v", got, want) + got := reg.SupportedAgentKinds() + if len(got) != 1 || got[0].Version != "v2" { + t.Errorf("overwrite: descriptors = %#v, want one v2 descriptor", got) } } @@ -92,16 +30,22 @@ func TestRegistryRegisterPanicsOnEmptyKind(t *testing.T) { t.Fatal("Register(\"\", ...) did not panic") } }() - agent.NewRegistry().RegisterKind(proto.SupportedAgentKind{Kind: "", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, prototest.ModelConfiguration(), stubFactory("x")) + agent.NewRegistry().RegisterKind(proto.SupportedAgentKind{Kind: "", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, harnessconfig.Configuration{}) } -func TestRegistryRegisterPanicsOnNilFactory(t *testing.T) { +func TestRegistryRegisterRejectsFactoriesForUnavailableRuntime(t *testing.T) { + info := proto.SupportedAgentKind{Kind: "k", Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{LocalEnvironment: proto.CapabilitySupported})} + executor := func(context.Context, proto.PromptRequestPayload) (agent.Executor, error) { return nil, nil } + registry := agent.NewRegistry() defer func() { - if r := recover(); r == nil { - t.Fatal("Register(kind, nil) did not panic") + if recover() == nil { + t.Fatal("unavailable runtime registered factories") + } + if len(registry.SupportedAgentKinds()) != 0 { + t.Fatal("rejected runtime changed registry") } }() - agent.NewRegistry().RegisterKind(proto.SupportedAgentKind{Kind: "k", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, prototest.ModelConfiguration(), nil) + registry.Register(agent.Declaration{Info: info}, agent.Runtime{Info: info, Executor: executor}) } func TestRegistrySupportedAgentKindsReportsDescriptors(t *testing.T) { @@ -113,18 +57,17 @@ func TestRegistrySupportedAgentKindsReportsDescriptors(t *testing.T) { Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{ Streaming: proto.CapabilitySupported, }), - }, prototest.ModelConfiguration(), stubFactory("oc")) + }, harnessconfig.Configuration{}) reg.RegisterKind(proto.SupportedAgentKind{ Kind: "fake_alpha", Available: true, Version: "1.2.3", Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{ - Streaming: proto.CapabilitySupported, - Permissions: proto.CapabilitySupported, - Usage: proto.CapabilitySupported, - Resume: proto.CapabilitySupported, + Streaming: proto.CapabilitySupported, + Usage: proto.CapabilitySupported, + Resume: proto.CapabilitySupported, }), - }, prototest.ModelConfiguration(), stubFactory("cc")) + }, harnessconfig.Configuration{}) got := reg.SupportedAgentKinds() if len(got) != 2 { @@ -133,7 +76,7 @@ func TestRegistrySupportedAgentKindsReportsDescriptors(t *testing.T) { if got[0].Kind != "fake_alpha" || got[1].Kind != "fake_beta" { t.Fatalf("SupportedAgentKinds sort = %#v, want fake_alpha then fake_beta", got) } - if !got[0].Available || got[0].Version != "1.2.3" || !got[0].Capabilities.Permissions.IsSupported() || !got[0].Capabilities.Resume.IsSupported() { + if !got[0].Available || got[0].Version != "1.2.3" || !got[0].Capabilities.Usage.IsSupported() || !got[0].Capabilities.Resume.IsSupported() { t.Fatalf("fake_alpha descriptor not preserved: %#v", got[0]) } if got[1].Available || got[1].Version != "missing" || !got[1].Capabilities.Streaming.IsSupported() { @@ -143,9 +86,9 @@ func TestRegistrySupportedAgentKindsReportsDescriptors(t *testing.T) { func TestRegistryExecutorRequiresExplicitRegistration(t *testing.T) { registry := agent.NewRegistry() - registry.RegisterKind(proto.SupportedAgentKind{Kind: "native", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, prototest.ModelConfiguration(), stubFactory("native")) + registry.RegisterKind(proto.SupportedAgentKind{Kind: "native", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, prototest.ModelConfiguration()) if _, err := registry.ResolveExecutor("native"); err == nil { - t.Fatal("legacy factory implied reusable execution") + t.Fatal("kind registration implied reusable execution") } expected := errors.New("executor factory") registry.RegisterExecutor("native", func(context.Context, proto.PromptRequestPayload) (agent.Executor, error) { return nil, expected }) @@ -156,7 +99,7 @@ func TestRegistryExecutorRequiresExplicitRegistration(t *testing.T) { if _, err := factory(t.Context(), prototest.WithModel(proto.PromptRequestPayload{})); !errors.Is(err, expected) { t.Fatal(err) } - registry.RegisterKind(proto.SupportedAgentKind{Kind: "native", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, prototest.ModelConfiguration(), stubFactory("replacement")) + registry.RegisterKind(proto.SupportedAgentKind{Kind: "native", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, harnessconfig.Configuration{}) if _, err := registry.ResolveExecutor("native"); err == nil { t.Fatal("replacing a kind retained its old executor capability") } @@ -167,7 +110,10 @@ func TestRegistryRejectsEveryOmittedCapabilityBeforeReplacement(t *testing.T) { for i := 0; i < reflect.TypeOf(valid).NumField(); i++ { t.Run(reflect.TypeOf(valid).Field(i).Name, func(t *testing.T) { registry := agent.NewRegistry() - registry.RegisterKind(proto.SupportedAgentKind{Kind: "fixture", Available: true, Capabilities: valid}, prototest.ModelConfiguration(), stubFactory("original")) + original := proto.SupportedAgentKind{Kind: "fixture", Available: true, Capabilities: valid} + registry.RegisterKind(original, harnessconfig.Configuration{}) + registry.RegisterExecutor("fixture", func(context.Context, proto.PromptRequestPayload) (agent.Executor, error) { return nil, nil }) + original.Capabilities.Preparation = proto.CapabilitySupported missing := valid reflect.ValueOf(&missing).Elem().Field(i).Set(reflect.ValueOf(proto.CapabilityUnspecified)) func() { @@ -176,14 +122,9 @@ func TestRegistryRejectsEveryOmittedCapabilityBeforeReplacement(t *testing.T) { t.Error("incomplete declaration registered") } }() - registry.RegisterKind(proto.SupportedAgentKind{Kind: "fixture", Available: false, Capabilities: missing}, prototest.ModelConfiguration(), stubFactory("replacement")) + registry.RegisterKind(proto.SupportedAgentKind{Kind: "fixture", Available: false, Capabilities: missing}, harnessconfig.Configuration{}) }() - factory, err := registry.Resolve("fixture") - if err != nil { - t.Fatal(err) - } - session, err := factory(t.Context(), prototest.WithModel(proto.PromptRequestPayload{}), nil) - if err != nil || session.(stubSession).marker != "original" { + if _, err := registry.ResolveExecutor("fixture"); err != nil || !reflect.DeepEqual(registry.SupportedAgentKinds(), []proto.SupportedAgentKind{original}) { t.Fatal("failed declaration changed registry") } }) diff --git a/apps/daemon/internal/agent/view_test.go b/apps/daemon/internal/agent/view_test.go index 70f5ae267..04f1332e9 100644 --- a/apps/daemon/internal/agent/view_test.go +++ b/apps/daemon/internal/agent/view_test.go @@ -54,7 +54,7 @@ func TestRegistryResolvesOnlyDeclaredViews(t *testing.T) { declared := validView(t) for kind, view := range map[string]*agent.View{"with_view": &declared, "without_view": nil} { info := proto.SupportedAgentKind{Kind: kind, Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})} - reg.Register(agent.Declaration{Info: info}, agent.Runtime{Info: info, Session: stubFactory(kind), View: view}) + reg.Register(agent.Declaration{Info: info}, agent.Runtime{Info: info, View: view}) } if _, err := reg.ResolveView("without_view"); !errors.Is(err, agent.ErrUnsupportedOperation) { t.Fatalf("ResolveView without a view = %v, want ErrUnsupportedOperation", err) @@ -76,7 +76,7 @@ func TestViewExecutorReceivesOnlyGatewayConnections(t *testing.T) { Info: info, Configuration: harnessconfig.Configuration{Providers: []harnessconfig.Provider{{Protocol: string(modelprovider.Responses)}}}, } - reg.Register(declaration, agent.Runtime{Info: info, Session: stubFactory("viewed"), View: &declared}) + reg.Register(declaration, agent.Runtime{Info: info, View: &declared}) view, err := reg.ResolveView("viewed") if err != nil { t.Fatal(err) diff --git a/apps/daemon/internal/agenthost/agenthost_linux_test.go b/apps/daemon/internal/agenthost/agenthost_linux_test.go index 09655db85..2cce3512f 100644 --- a/apps/daemon/internal/agenthost/agenthost_linux_test.go +++ b/apps/daemon/internal/agenthost/agenthost_linux_test.go @@ -74,10 +74,7 @@ func register(reg *agent.Registry, kind string, view *agent.View) { MCPHTTPTools: proto.CapabilitySupported, MCPHTTPBearerAuth: proto.CapabilitySupported, WorkspaceReadPreparation: proto.CapabilitySupported})} declaration := agent.Declaration{Info: info, Configuration: harnessconfig.Configuration{Providers: []harnessconfig.Provider{{Protocol: string(modelprovider.Anthropic)}}}} - reg.Register(declaration, agent.Runtime{Info: info, View: view, - Session: func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, errors.New("not used") - }}) + reg.Register(declaration, agent.Runtime{Info: info, View: view}) } // declared declares every view capability as s. diff --git a/apps/daemon/internal/agenthost/executor_linux.go b/apps/daemon/internal/agenthost/executor_linux.go index e6a156746..ce6555c9c 100644 --- a/apps/daemon/internal/agenthost/executor_linux.go +++ b/apps/daemon/internal/agenthost/executor_linux.go @@ -30,8 +30,7 @@ type deps struct { // with Info that describes how views run it. Its Executor factory prepares an // Executor of the Session that bind binds the request to, as the package // documentation describes, and bind's error fails the preparation. The Router -// that runs it sets dispatch.Config.SessionEnvironments. A direct prompt run -// is unsupported. +// that runs it sets dispatch.Config.SessionEnvironments. func (h *Host) Registry(bind func(proto.PromptRequestPayload) (Binding, Environment, error)) *agent.Registry { d := deps{dial: relayDial(h.cfg), tasks: taskUIDs} return registry(h.cfg.Harnesses, func(ctx context.Context, req proto.PromptRequestPayload) (agent.Executor, error) { @@ -49,9 +48,7 @@ func registry(harnesses *agent.Registry, factory agent.ExecutorFactory) *agent.R if err != nil || viewErr != nil { continue } - reg.RegisterKind(viewInfo(info, view.Capabilities), configuration, func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, unsupported("a direct prompt run") - }) + reg.RegisterKind(viewInfo(info, view.Capabilities), configuration) reg.RegisterExecutor(info.Kind, factory) } return reg diff --git a/apps/daemon/internal/agenthostqualify/qualify_linux_test.go b/apps/daemon/internal/agenthostqualify/qualify_linux_test.go index f7e4b915d..c6b21f4ca 100644 --- a/apps/daemon/internal/agenthostqualify/qualify_linux_test.go +++ b/apps/daemon/internal/agenthostqualify/qualify_linux_test.go @@ -326,12 +326,11 @@ func withMCP(t *testing.T, reg *agent.Registry, mcp []proto.EnvironmentMCP) *age wrapped := agent.NewRegistry() for _, info := range reg.SupportedAgentKinds() { configuration, err := reg.Configuration(info.Kind) - direct, directErr := reg.Resolve(info.Kind) factory, factoryErr := reg.ResolveExecutor(info.Kind) - if err := errors.Join(err, directErr, factoryErr); err != nil { + if err := errors.Join(err, factoryErr); err != nil { t.Fatal(err) } - wrapped.RegisterKind(info, configuration, direct) + wrapped.RegisterKind(info, configuration) wrapped.RegisterExecutor(info.Kind, func(ctx context.Context, req proto.PromptRequestPayload) (agent.Executor, error) { local := *req.LocalEnvironment local.MCP = mcp diff --git a/apps/daemon/internal/cli/claude_sdk_live_linux_test.go b/apps/daemon/internal/cli/claude_sdk_live_linux_test.go index 19741a255..c2e8dd5a7 100644 --- a/apps/daemon/internal/cli/claude_sdk_live_linux_test.go +++ b/apps/daemon/internal/cli/claude_sdk_live_linux_test.go @@ -16,6 +16,7 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/dispatch" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto/prototest" + "github.com/MiniMax-AI/OpenAgentCore/internal/modelprovider" "github.com/google/uuid" ) @@ -49,10 +50,10 @@ func TestLiveRegisteredClaudeSDK(t *testing.T) { if err != nil { t.Fatal(err) } + provider := &modelprovider.Provider{Protocol: modelprovider.Anthropic, BaseURL: "https://api.minimax.cn/anthropic", APIKey: strings.TrimSpace(string(key))} for name, value := range map[string]string{ - "ANTHROPIC_BASE_URL": "https://api.minimax.cn/anthropic", "ANTHROPIC_AUTH_TOKEN": strings.TrimSpace(string(key)), - "ANTHROPIC_API_KEY": "", "CLAUDE_CODE_OAUTH_TOKEN": "", "CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS": "1", - "ANTHROPIC_DEFAULT_SONNET_MODEL": "MiniMax-M3", "ANTHROPIC_DEFAULT_OPUS_MODEL": "MiniMax-M3", "ANTHROPIC_DEFAULT_HAIKU_MODEL": "MiniMax-M3", + "CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS": "1", + "ANTHROPIC_DEFAULT_SONNET_MODEL": "MiniMax-M3", "ANTHROPIC_DEFAULT_OPUS_MODEL": "MiniMax-M3", "ANTHROPIC_DEFAULT_HAIKU_MODEL": "MiniMax-M3", } { t.Setenv(name, value) } @@ -89,7 +90,7 @@ func TestLiveRegisteredClaudeSDK(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), 120*time.Second) defer cancel() id := uuid.NewString() - request := proto.PromptRequestPayload{AgentKind: "claude_sdk", AgentStateKey: prototest.StateKey, AgentSessionID: resume, RequireExistingNativeSession: resume != "", ObserveMessages: true, DisableExecutionEnvironment: true, DisableSubagents: true, ExecutionControls: &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "medium"}, Model: "MiniMax-M3"} + request := proto.PromptRequestPayload{AgentKind: "claude_sdk", AgentStateKey: prototest.StateKey, AgentSessionID: resume, RequireExistingNativeSession: resume != "", ObserveMessages: true, DisableExecutionEnvironment: true, DisableSubagents: true, ExecutionControls: &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "medium"}, Model: "MiniMax-M3", ModelProvider: provider} if callFunction { request.FunctionTools = []proto.FunctionTool{{Name: "lookup", Description: "Return a verification value.", Parameters: json.RawMessage(`{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":false}`)}} } diff --git a/apps/daemon/internal/cli/connect.go b/apps/daemon/internal/cli/connect.go index f920318b3..afdea1a96 100644 --- a/apps/daemon/internal/cli/connect.go +++ b/apps/daemon/internal/cli/connect.go @@ -151,7 +151,7 @@ func spawnBackground(ctx context.Context, rc *runContext, profile string, argv [ } else if !errors.Is(err, os.ErrNotExist) && !errors.Is(err, daemonize.ErrStaleOrCorrupt) { return fmt.Errorf("connect: check pidfile: %w", err) } - // Stale pidfile → remove so WritePIDFile starts clean. + // Stale pidfile → remove so Spawn starts clean. _ = daemonize.RemovePIDFile(pidPath) if err := daemonize.EnsureLogFile(logPath); err != nil { diff --git a/apps/daemon/internal/cli/connect_cleanup_test.go b/apps/daemon/internal/cli/connect_cleanup_test.go index e7d44e052..3a20c2e16 100644 --- a/apps/daemon/internal/cli/connect_cleanup_test.go +++ b/apps/daemon/internal/cli/connect_cleanup_test.go @@ -113,10 +113,7 @@ func testDisconnectedPumpCleanup(t *testing.T, suspend bool) { } owner := &cleanupExecutor{retry: make(chan struct{}), confirm: make(chan struct{})} registry := agent.NewRegistry() - registry.RegisterKind(proto.SupportedAgentKind{Kind: "cleanup", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{EnvironmentNone: proto.CapabilitySupported})}, - prototest.ModelConfiguration(), func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, errors.New("unexpected legacy factory") - }) + registry.RegisterKind(proto.SupportedAgentKind{Kind: "cleanup", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{EnvironmentNone: proto.CapabilitySupported})}, prototest.ModelConfiguration()) var factories atomic.Int32 registry.RegisterExecutor("cleanup", func(context.Context, proto.PromptRequestPayload) (agent.Executor, error) { factories.Add(1) diff --git a/apps/daemon/internal/cli/connect_environment_binding.go b/apps/daemon/internal/cli/connect_environment_binding.go index 9fa651798..b661092e0 100644 --- a/apps/daemon/internal/cli/connect_environment_binding.go +++ b/apps/daemon/internal/cli/connect_environment_binding.go @@ -95,17 +95,6 @@ func bindEnvironmentRuntime(remote string, bound environmentEnrollment, credenti return nil } -// Neither selecting private state nor selecting its parent grants workspace access. -func environmentPathsOverlap(first, second string) bool { - for _, pair := range [][2]string{{first, second}, {second, first}} { - relative, err := filepath.Rel(pair[0], pair[1]) - if err == nil && (relative == "." || filepath.IsLocal(relative)) { - return true - } - } - return false -} - func saveEnvironmentBinding(root string, want environmentBinding) error { // Store the binding with the other daemon state. dir := filepath.Join(root, "daemon") diff --git a/apps/daemon/internal/cli/native_discovery_test.go b/apps/daemon/internal/cli/native_discovery_test.go index e6d9e0dee..5f3592c9d 100644 --- a/apps/daemon/internal/cli/native_discovery_test.go +++ b/apps/daemon/internal/cli/native_discovery_test.go @@ -21,9 +21,7 @@ func TestDiscoveryAndRegistration(t *testing.T) { called = append(called, info.Kind) info.Available = true info.Version = "test" - return &agent.Runtime{Info: info, Session: func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, errors.New("test") - }} + return &agent.Runtime{Info: info} } } rc := &runContext{stdout: io.Discard, stderr: io.Discard} @@ -41,10 +39,11 @@ func TestDiscoveryAndRegistration(t *testing.T) { } registry := agent.NewRegistry() registerAgentKinds(registry, discovery) - if len(registry.Kinds()) != len(expected) { - t.Fatal(registry.Kinds()) + kinds := registry.SupportedAgentKinds() + if len(kinds) != len(expected) { + t.Fatal(kinds) } - for _, info := range registry.SupportedAgentKinds() { + for _, info := range kinds { if !info.Available || info.Version != "test" { t.Fatal(info) } diff --git a/apps/daemon/internal/daemonize/logfile.go b/apps/daemon/internal/daemonize/logfile.go index 80124e1fc..a74f0dcfd 100644 --- a/apps/daemon/internal/daemonize/logfile.go +++ b/apps/daemon/internal/daemonize/logfile.go @@ -1,14 +1,14 @@ package daemonize import ( - "bufio" "errors" "fmt" - "github.com/MiniMax-AI/OpenAgentCore/internal/runtimefs" "io" "os" "path/filepath" "time" + + "github.com/MiniMax-AI/OpenAgentCore/internal/runtimefs" ) // TailOptions configures Tail. @@ -149,27 +149,6 @@ func EnsureLogFile(path string) error { return f.Close() } -// MustWriteLine appends one line to path with 0o600 mode, adding a -// trailing newline if missing. Returns errors despite the name — -// kept short because it's used in startup hot paths. -func MustWriteLine(path string, line string) error { - f, err := openPrivateLog(path) - if err != nil { - return err - } - defer f.Close() - bw := bufio.NewWriter(f) - if _, err := bw.WriteString(line); err != nil { - return err - } - if len(line) == 0 || line[len(line)-1] != '\n' { - if _, err := bw.WriteString("\n"); err != nil { - return err - } - } - return bw.Flush() -} - func openPrivateLog(path string) (*os.File, error) { root, err := os.OpenRoot(filepath.Dir(path)) if err != nil { diff --git a/apps/daemon/internal/daemonize/logfile_test.go b/apps/daemon/internal/daemonize/logfile_test.go index a13bdca48..5d1caeb6e 100644 --- a/apps/daemon/internal/daemonize/logfile_test.go +++ b/apps/daemon/internal/daemonize/logfile_test.go @@ -182,21 +182,6 @@ func TestEnsureLogFileCreatesMissingParentDir(t *testing.T) { } } -func TestMustWriteLineAppendsTrailingNewline(t *testing.T) { - dir := privateTempDir(t) - path := filepath.Join(dir, "ml.log") - if err := MustWriteLine(path, "no-newline"); err != nil { - t.Fatalf("MustWriteLine: %v", err) - } - if err := MustWriteLine(path, "has-newline\n"); err != nil { - t.Fatalf("MustWriteLine: %v", err) - } - body, _ := os.ReadFile(path) - if string(body) != "no-newline\nhas-newline\n" { - t.Errorf("body = %q", string(body)) - } -} - // safeBuf wraps bytes.Buffer with a mutex so concurrent reads/writes // during Tail's poll loop don't race. type safeBuf struct { diff --git a/apps/daemon/internal/daemonize/pidfile.go b/apps/daemon/internal/daemonize/pidfile.go index 2224c8542..b6ad008c1 100644 --- a/apps/daemon/internal/daemonize/pidfile.go +++ b/apps/daemon/internal/daemonize/pidfile.go @@ -19,15 +19,6 @@ type processIdentity struct { StopEvent string `json:"stop_event,omitempty"` } -func WritePIDFile(path string, pid int) error { - identity, err := identifyProcess(pid) - if err != nil { - return err - } - identity.StopEvent = os.Getenv(stopEventEnv) - return writeIdentity(path, identity) -} - func writeIdentity(path string, identity processIdentity) error { if path == "" { return errors.New("daemonize: process record path required") @@ -71,8 +62,6 @@ func ReadPIDFile(path string) (int, error) { return identity.PID, err } -func IsAlive(pid int) error { _, err := identifyProcess(pid); return err } - // StopPIDFile never removes ownership before the exact process has exited. // A cleanup timeout leaves the record available for observation and a later stop. func StopPIDFile(path string, timeout time.Duration) error { diff --git a/apps/daemon/internal/daemonize/pidfile_test.go b/apps/daemon/internal/daemonize/pidfile_test.go index 4aec5a769..5f83362d0 100644 --- a/apps/daemon/internal/daemonize/pidfile_test.go +++ b/apps/daemon/internal/daemonize/pidfile_test.go @@ -11,7 +11,11 @@ import ( func TestProcessRecordIdentity(t *testing.T) { path := filepath.Join(privateTempDir(t), "connect.pid") - if err := WritePIDFile(path, os.Getpid()); err != nil { + record, err := identifyProcess(os.Getpid()) + if err == nil { + err = writeIdentity(path, record) + } + if err != nil { t.Fatal(err) } pid, err := ReadPIDFile(path) @@ -34,7 +38,7 @@ func TestProcessRecordIdentity(t *testing.T) { if err = StopPIDFile(path, time.Second); !errors.Is(err, ErrStaleOrCorrupt) { t.Fatal("stale identity was accepted", err) } - if err = IsAlive(os.Getpid()); err != nil { + if _, err = identifyProcess(os.Getpid()); err != nil { t.Fatal("unrelated process was affected", err) } if _, err = os.Stat(path); err != nil { diff --git a/apps/daemon/internal/dispatch/assignment.go b/apps/daemon/internal/dispatch/assignment.go index ae5b82b41..de307618b 100644 --- a/apps/daemon/internal/dispatch/assignment.go +++ b/apps/daemon/internal/dispatch/assignment.go @@ -120,7 +120,7 @@ func (r *Router) handleAssignmentRelease(ctx context.Context, env proto.Envelope go func() { defer r.shutdownWG.Done() for _, p := range preparations { - r.releasePreparation(p, "failed", proto.AssignmentStale, true, false) + r.releasePreparation(p, "failed", proto.AssignmentStale, true) } work.Wait() state, code := proto.AssignmentReleased, "" @@ -154,7 +154,7 @@ func (r *Router) fenceSessionWorkLocked(sessionID string) []*preparationState { } var preparations []*preparationState for _, p := range r.preparations { - if p.executor == nil && p.workspaceReadOnly && p.owns && p.request.Assignment.SessionID == sessionID { + if p.executor == nil && p.owns && p.request.Assignment.SessionID == sessionID { preparations = append(preparations, p) } } diff --git a/apps/daemon/internal/dispatch/capability_admission_test.go b/apps/daemon/internal/dispatch/capability_admission_test.go index 8cdb2240d..823aa22d3 100644 --- a/apps/daemon/internal/dispatch/capability_admission_test.go +++ b/apps/daemon/internal/dispatch/capability_admission_test.go @@ -52,50 +52,3 @@ func TestSteeringUsesAdmittedDeclarationAndDoesNotReplayUnsupportedImplementatio }) } } - -func TestInteractionDeclarationPrecedesResponderMethods(t *testing.T) { - for _, supported := range []bool{false, true} { - for _, ask := range []bool{false, true} { - t.Run(fmt.Sprintf("supported=%v/ask=%v", supported, ask), func(t *testing.T) { - h := newHarness(t) - defer h.router.Shutdown(context.Background()) - var session *fakeSession - info := proto.SupportedAgentKind{Kind: "fixture", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{Permissions: proto.CapabilityFromBool(supported)})} - registerSession(h.reg, info, func(_ context.Context, _ proto.PromptRequestPayload, out chan<- proto.Envelope) (agent.Session, error) { - session = &fakeSession{out: out, closeOutOnCancel: true, submitErr: agent.ErrUnsupportedOperation, askErr: agent.ErrUnsupportedOperation} - return session, nil - }) - startRun(t, h.router, h.sender, "fixture", "run") - event := mustEnv(t, proto.TypePermissionRequest, "run", proto.PermissionRequestPayload{RequestID: "interaction", Tool: "fixture"}) - decision := scoped(t, "run", proto.TypePermissionDecision, "interaction", proto.PermissionDecisionPayload{DeliveryID: "decision", Approved: true}) - if ask { - event = mustEnv(t, proto.TypePromptForUserChoice, "run", proto.PromptForUserChoicePayload{AskID: "interaction"}) - decision = scoped(t, "run", proto.TypePromptForUserChoiceDecision, "interaction", proto.PromptForUserChoiceDecisionPayload{DeliveryID: "decision"}) - } - session.out <- event - waitFor(t, func() bool { return hasFrame(h.sender, event.Type, "run") }, "interaction indexed") - if err := h.router.Handle(t.Context(), decision); err != nil { - t.Fatal(err) - } - code := "unsupported" - if supported { - code = "contract_violation" - } - assertDecisionAck(t, h.sender, "decision", false, code) - count := len(session.submissions()) - if ask { - session.askMu.Lock() - count = len(session.askCalls) - session.askMu.Unlock() - } - want := 0 - if supported { - want = 1 - } - if count != want { - t.Fatalf("native calls %d, want %d", count, want) - } - }) - } - } -} diff --git a/apps/daemon/internal/dispatch/executor.go b/apps/daemon/internal/dispatch/executor.go index 9bf45b9e6..2fd2a18f5 100644 --- a/apps/daemon/internal/dispatch/executor.go +++ b/apps/daemon/internal/dispatch/executor.go @@ -161,7 +161,7 @@ func (r *Router) handleExecutorPrepare(ctx context.Context, env proto.Envelope, p.status = proto.PreparationStatusPayload{Handle: uuid.NewString(), ExecutorID: owner.id, Revision: 1, State: state, Reused: reused, ExpiresAt: p.deadline.UnixMilli()} owner.admission = p r.preparations[p.status.Handle], r.preparationRequests[env.ID] = p, p - p.timer = time.AfterFunc(r.preparationTimeout, func() { r.releasePreparation(p, "expired", "", true, true) }) + p.timer = time.AfterFunc(r.preparationTimeout, func() { r.releasePreparation(p, "expired", "", true) }) if !reused { r.shutdownWG.Add(1) } diff --git a/apps/daemon/internal/dispatch/executor_cancel_receipt_test.go b/apps/daemon/internal/dispatch/executor_cancel_receipt_test.go index 496e4dd4b..590d8bc0b 100644 --- a/apps/daemon/internal/dispatch/executor_cancel_receipt_test.go +++ b/apps/daemon/internal/dispatch/executor_cancel_receipt_test.go @@ -125,9 +125,7 @@ func TestExecutorCancellationReachesNativeBeforeDurableReceiptJoin(t *testing.T) sender := &receiptCancelSender{recSender: &recSender{}, entered: make(chan struct{}), release: make(chan struct{})} owner := &receiptCancelExecutor{turn: make(chan *receiptCancelTurn, 2), cancelFails: mode == "cancel_failure" || mode == "close_failure_retry", closeFailsFirst: mode == "close_failure_retry", closeEntered: make(chan struct{}), closeRelease: make(chan struct{})} reg := agent.NewRegistry() - reg.RegisterKind(proto.SupportedAgentKind{Kind: "reusable", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{EnvironmentNone: proto.CapabilitySupported, DurableInputReceipts: proto.CapabilitySupported})}, prototest.ModelConfiguration(), func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, errors.New("legacy factory forbidden") - }) + reg.RegisterKind(proto.SupportedAgentKind{Kind: "reusable", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{EnvironmentNone: proto.CapabilitySupported, DurableInputReceipts: proto.CapabilitySupported})}, prototest.ModelConfiguration()) reg.RegisterExecutor("reusable", func(context.Context, proto.PromptRequestPayload) (agent.Executor, error) { return owner, nil }) r, err := dispatch.New(dispatch.Config{Registry: reg, Sender: sender, IdleTimeout: time.Hour}) if err != nil { diff --git a/apps/daemon/internal/dispatch/executor_handoff_test.go b/apps/daemon/internal/dispatch/executor_handoff_test.go index 3db03edad..b7f89a573 100644 --- a/apps/daemon/internal/dispatch/executor_handoff_test.go +++ b/apps/daemon/internal/dispatch/executor_handoff_test.go @@ -91,10 +91,7 @@ func TestPreparedDonePublishesAfterExecutorHandoff(t *testing.T) { } owner := &terminalHandoffExecutor{turns: make(chan *terminalHandoffTurn, 3)} registry := agent.NewRegistry() - registry.RegisterKind(proto.SupportedAgentKind{Kind: "handoff", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{EnvironmentNone: proto.CapabilitySupported})}, - prototest.ModelConfiguration(), func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, errors.New("legacy path forbidden") - }) + registry.RegisterKind(proto.SupportedAgentKind{Kind: "handoff", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{EnvironmentNone: proto.CapabilitySupported})}, prototest.ModelConfiguration()) var creates atomic.Int32 registry.RegisterExecutor("handoff", func(context.Context, proto.PromptRequestPayload) (agent.Executor, error) { creates.Add(1) diff --git a/apps/daemon/internal/dispatch/executor_test.go b/apps/daemon/internal/dispatch/executor_test.go index 0a8080804..52941687b 100644 --- a/apps/daemon/internal/dispatch/executor_test.go +++ b/apps/daemon/internal/dispatch/executor_test.go @@ -79,9 +79,7 @@ func executorRequest() proto.ExecutionPreparePayload { // registerExecutorKind registers info for prepared execution only. func registerExecutorKind(reg *agent.Registry, info proto.SupportedAgentKind, factory agent.ExecutorFactory) { - reg.RegisterKind(info, prototest.ModelConfiguration(), func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, errors.New("prepared execution must not use the direct Session factory") - }) + reg.RegisterKind(info, prototest.ModelConfiguration()) reg.RegisterExecutor(info.Kind, factory) } func executorRouter(t *testing.T, owner *reusableExecutor, idle time.Duration) (*dispatch.Router, *recSender, *atomic.Int32) { @@ -230,9 +228,7 @@ func TestExecutorPreInputFailureConfirmsCloseBeforeRetrySignal(t *testing.T) { func poolRouter(t *testing.T, factory agent.ExecutorFactory) (*dispatch.Router, *recSender) { t.Helper() registry := agent.NewRegistry() - registry.RegisterKind(proto.SupportedAgentKind{Kind: "reusable", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{EnvironmentNone: proto.CapabilitySupported, DurableInputReceipts: proto.CapabilitySupported})}, prototest.ModelConfiguration(), func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, errors.New("unexpected legacy factory") - }) + registry.RegisterKind(proto.SupportedAgentKind{Kind: "reusable", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{EnvironmentNone: proto.CapabilitySupported, DurableInputReceipts: proto.CapabilitySupported})}, prototest.ModelConfiguration()) registry.RegisterExecutor("reusable", factory) sender := &recSender{} router, err := dispatch.New(dispatch.Config{Registry: registry, Sender: sender, IdleTimeout: time.Minute}) diff --git a/apps/daemon/internal/dispatch/export_test.go b/apps/daemon/internal/dispatch/export_test.go index 05d7011d7..6ee90d36e 100644 --- a/apps/daemon/internal/dispatch/export_test.go +++ b/apps/daemon/internal/dispatch/export_test.go @@ -7,29 +7,6 @@ func (r *Router) PreparationOwnershipForTest(handle string) (bool, bool) { return p != nil && p.owns, p != nil && p.busy } -// Test-only re-exports so router_test.go (package dispatch_test) can -// peek into internal state without widening the public surface. - -// AskIndexLenForTest returns the number of asks still indexed at the -// router level. Used to assert cleanup paths. -func (r *Router) AskIndexLenForTest() int { - r.mu.Lock() - defer r.mu.Unlock() - return len(r.askIndex) -} - -// PendingAsksLenForTest reports how many asks the named run still has -// outstanding. Returns -1 if the run is unknown. -func (r *Router) PendingAsksLenForTest(runID string) int { - r.mu.Lock() - defer r.mu.Unlock() - s, ok := r.sessions[runID] - if !ok { - return -1 - } - return len(s.pendingAsks) -} - func (r *Router) SteeringClosedForTest(runID string) bool { r.mu.Lock() defer r.mu.Unlock() diff --git a/apps/daemon/internal/dispatch/functions.go b/apps/daemon/internal/dispatch/functions.go index 6aed14c06..569a26aaf 100644 --- a/apps/daemon/internal/dispatch/functions.go +++ b/apps/daemon/internal/dispatch/functions.go @@ -5,6 +5,8 @@ import ( "crypto/sha256" "encoding/json" "errors" + "fmt" + "time" "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" @@ -29,9 +31,19 @@ func (r *Router) handleFunctionResult(ctx context.Context, env proto.Envelope) e } fingerprint := sha256.Sum256(encoded) // Scope receipt replay to both identities, even when native call IDs repeat across Runs. - kind := proto.TypeFunctionResult + "\x00" + result.CallID - if handled, err := r.replayAppliedInteractionDecision(ctx, env, result.DeliveryID, kind, fingerprint); handled { - return err + key := env.ID + "\x00" + result.CallID + r.mu.Lock() + applied, replay := r.applied[key] + r.mu.Unlock() + if replay { + // The receipt answers only the assignment that applied the result. + if applied.assignment != env.Assignment { + return r.sendInteractionDecisionAck(ctx, env, result.DeliveryID, false, proto.AssignmentConflict, "The result was applied under another assignment.") + } + if applied.fingerprint != fingerprint { + return r.sendInteractionDecisionAck(ctx, env, result.DeliveryID, false, "decision_conflict", "request was already applied with a different decision") + } + return r.sendInteractionDecisionAck(ctx, env, result.DeliveryID, true, "", "") } r.mu.Lock() state := r.sessions[env.ID] @@ -75,6 +87,43 @@ func (r *Router) handleFunctionResult(ctx context.Context, env proto.Envelope) e } return r.sendInteractionDecisionAck(ctx, env, result.DeliveryID, false, code, "function result was not applied") } - r.rememberAppliedInteractionDecision(env, kind, fingerprint) + r.rememberAppliedFunctionResult(key, env.Assignment, fingerprint) return r.sendInteractionDecisionAck(ctx, env, result.DeliveryID, true, "", "") } + +func (r *Router) rememberAppliedFunctionResult(key string, assignment proto.AssignmentRef, fingerprint [32]byte) { + now := time.Now().UTC() + r.mu.Lock() + if len(r.applied) >= 1024 { + cutoff := now.Add(-time.Hour) + for id, entry := range r.applied { + if entry.recordedAt.Before(cutoff) { + delete(r.applied, id) + } + } + } + if len(r.applied) >= 1024 { + for id := range r.applied { + delete(r.applied, id) + break + } + } + r.applied[key] = appliedFunctionResult{fingerprint: fingerprint, assignment: assignment, recordedAt: now} + r.mu.Unlock() +} + +func (r *Router) sendInteractionDecisionAck(ctx context.Context, request proto.Envelope, deliveryID string, applied bool, errorCode, message string) error { + env, err := request.Reply(proto.TypeInteractionDecisionAck, proto.InteractionDecisionAckPayload{ + DeliveryID: deliveryID, + Applied: applied, + ErrorCode: errorCode, + Error: message, + }) + if err != nil { + return fmt.Errorf("dispatch: build interaction decision ack: %w", err) + } + if err := r.sender.Send(ctx, env); err != nil { + return fmt.Errorf("dispatch: send interaction decision ack: %w", err) + } + return nil +} diff --git a/apps/daemon/internal/dispatch/interaction_decisions.go b/apps/daemon/internal/dispatch/interaction_decisions.go deleted file mode 100644 index 8bfb3337d..000000000 --- a/apps/daemon/internal/dispatch/interaction_decisions.go +++ /dev/null @@ -1,340 +0,0 @@ -package dispatch - -import ( - "context" - "crypto/sha256" - "encoding/json" - "errors" - "fmt" - "strings" - "time" - - "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" -) - -func (r *Router) handlePermissionDecision(ctx context.Context, env proto.Envelope) error { - if env.ID == "" { - return errors.New("dispatch: permission_decision missing perm id (Envelope.ID empty)") - } - var payload proto.PermissionDecisionPayload - if err := env.DecodeRequest(&payload); err != nil { - return fmt.Errorf("dispatch: decode permission_decision: %w", err) - } - if payload.DeliveryID == "" { - return errors.New("dispatch: permission_decision missing delivery_id") - } - fingerprint, err := interactionDecisionFingerprint(payload) - if err != nil { - return fmt.Errorf("dispatch: fingerprint permission_decision: %w", err) - } - if handled, err := r.replayAppliedInteractionDecision(ctx, env, payload.DeliveryID, proto.TypePermissionDecision, fingerprint); handled { - return err - } - - r.mu.Lock() - runID, known := r.permIndex[env.ID] - var state *sessionState - var session agent.Session - var finishOperation func() - if known { - state = r.sessions[runID] - } - if state != nil { - if code := r.admitRunLocked(env.Assignment, state); code != "" { - r.mu.Unlock() - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, code, "The run's assignment does not admit this decision.") - } - session, finishOperation, _ = r.preparedOperationLocked(state) - } - r.mu.Unlock() - - if !known || state == nil { - // Server's perm timeout / cancel race; common enough that info - // is right. - r.log.InfoContext(ctx, "permission_decision for unknown perm (run gone)", "perm_id", env.ID) - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "not_pending", "permission request is no longer pending") - } - if session == nil { - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "not_ready", "permission request is waiting for the native session") - } - defer finishOperation() - ctx, stop := r.shutdownContext(ctx) - defer stop() - - if !state.capabilities.Permissions.IsSupported() { - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "unsupported", "The runtime declaration does not support interaction decisions.") - } - responder, supported := session.(agent.PermissionResponder) - if !supported { - r.dropPermission(state, env.ID) - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "contract_violation", "Declared runtime capability does not implement permission responses") - } - if err := responder.SubmitPermission(ctx, env.ID, payload); err != nil { - if errors.Is(err, agent.ErrUnsupportedOperation) { - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "contract_violation", "Declared permission capability has no implementation.") - } - if errors.Is(err, agent.ErrUnknownPermission) { - r.log.InfoContext(ctx, "agent reports unknown perm (race with cancel)", "perm_id", env.ID, "run_id", runID) - r.dropPermission(state, env.ID) - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "not_pending", err.Error()) - } - r.log.WarnContext(ctx, "agent rejected permission decision", "perm_id", env.ID, "run_id", runID, "err", err) - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "runtime_error", err.Error()) - } - r.dropPermission(state, env.ID) - r.rememberAppliedInteractionDecision(env, proto.TypePermissionDecision, fingerprint) - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, true, "", "") -} - -func (r *Router) dropPermission(s *sessionState, permissionID string) { - r.mu.Lock() - delete(r.permIndex, permissionID) - delete(s.pendingIDs, permissionID) - r.mu.Unlock() -} - -// handlePromptForUserChoiceDecision is the ask-side twin of -// handlePermissionDecision. The server forwards the human's answer -// here; we look up the owning session via askIndex and ask the agent -// to write a matching tool_result back into its CLI. -// -// On both success and ErrUnknownAsk we drop the ask from askIndex / -// pendingAsks so a stale retry can't waste cycles. Timer-fired cancels -// inside the session don't currently call back into the router, so -// those entries linger until cleanupSession — acceptable because they -// can't double-fire (the session's own pendingAskTable.Take already -// guards that); cleanupSession removes the routing entry when the run -// stream closes. -func (r *Router) handlePromptForUserChoiceDecision(ctx context.Context, env proto.Envelope) error { - if env.ID == "" { - return errors.New("dispatch: prompt_for_user_choice_decision missing ask id (Envelope.ID empty)") - } - var payload proto.PromptForUserChoiceDecisionPayload - if err := env.DecodeRequest(&payload); err != nil { - return fmt.Errorf("dispatch: decode prompt_for_user_choice_decision: %w", err) - } - if payload.DeliveryID == "" { - return errors.New("dispatch: prompt_for_user_choice_decision missing delivery_id") - } - fingerprint, err := interactionDecisionFingerprint(payload) - if err != nil { - return fmt.Errorf("dispatch: fingerprint prompt_for_user_choice_decision: %w", err) - } - if handled, err := r.replayAppliedInteractionDecision(ctx, env, payload.DeliveryID, proto.TypePromptForUserChoiceDecision, fingerprint); handled { - return err - } - - r.mu.Lock() - runID, known := r.askIndex[env.ID] - var state *sessionState - var session agent.Session - var finishOperation func() - if known { - state = r.sessions[runID] - } - if state != nil { - if code := r.admitRunLocked(env.Assignment, state); code != "" { - r.mu.Unlock() - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, code, "The run's assignment does not admit this decision.") - } - session, finishOperation, _ = r.preparedOperationLocked(state) - } - r.mu.Unlock() - - if !known || state == nil { - r.log.InfoContext(ctx, "prompt_for_user_choice_decision for unknown ask (run gone)", "ask_id", env.ID) - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "not_pending", "user-input request is no longer pending") - } - if session == nil { - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "not_ready", "user-input request is waiting for the native session") - } - defer finishOperation() - ctx, stop := r.shutdownContext(ctx) - defer stop() - - if !state.capabilities.Permissions.IsSupported() { - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "unsupported", "The runtime declaration does not support interaction decisions.") - } - responder, supported := session.(agent.UserChoiceResponder) - if !supported { - r.dropAsk(state, env.ID) - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "contract_violation", "Declared runtime capability does not implement user-choice responses") - } - err = responder.SubmitPromptForUserChoice(ctx, env.ID, payload) - if err != nil { - if errors.Is(err, agent.ErrUnsupportedOperation) { - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "contract_violation", "Declared user-choice capability has no implementation.") - } - if errors.Is(err, agent.ErrUnknownAsk) { - r.log.InfoContext(ctx, "agent reports unknown ask (race with cancel)", "ask_id", env.ID, "run_id", runID) - r.dropAsk(state, env.ID) - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "not_pending", err.Error()) - } - // Keep the routing entry for transient runtime failures. Codex, for - // example, restores its pending request when a JSON-RPC reply write - // fails, so dropping the ask here would turn a retryable error into a - // permanent not_pending response on the next attempt. - r.log.WarnContext(ctx, "agent rejected user-input decision", "ask_id", env.ID, "run_id", runID, "err", err) - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, false, "runtime_error", err.Error()) - } - r.dropAsk(state, env.ID) - r.rememberAppliedInteractionDecision(env, proto.TypePromptForUserChoiceDecision, fingerprint) - return r.sendInteractionDecisionAck(ctx, env, payload.DeliveryID, true, "", "") -} - -func (r *Router) replayAppliedInteractionDecision(ctx context.Context, env proto.Envelope, deliveryID, kind string, fingerprint [32]byte) (bool, error) { - key := appliedInteractionDecisionKey(env.ID, kind) - r.mu.Lock() - applied, ok := r.applied[key] - r.mu.Unlock() - if !ok { - return false, nil - } - // The receipt answers only the assignment that applied the decision. - if applied.assignment != env.Assignment { - return true, r.sendInteractionDecisionAck(ctx, env, deliveryID, false, proto.AssignmentConflict, "The decision was applied under another assignment.") - } - if applied.requestID != env.ID || applied.kind != kind || applied.fingerprint != fingerprint { - return true, r.sendInteractionDecisionAck(ctx, env, deliveryID, false, "decision_conflict", "request was already applied with a different decision") - } - return true, r.sendInteractionDecisionAck(ctx, env, deliveryID, true, "", "") -} - -func (r *Router) rememberAppliedInteractionDecision(env proto.Envelope, kind string, fingerprint [32]byte) { - now := time.Now().UTC() - key := appliedInteractionDecisionKey(env.ID, kind) - r.mu.Lock() - if len(r.applied) >= 1024 { - cutoff := now.Add(-time.Hour) - for id, entry := range r.applied { - if entry.recordedAt.Before(cutoff) { - delete(r.applied, id) - } - } - } - if len(r.applied) >= 1024 { - for id := range r.applied { - delete(r.applied, id) - break - } - } - r.applied[key] = appliedInteractionDecision{ - requestID: env.ID, kind: kind, fingerprint: fingerprint, assignment: env.Assignment, recordedAt: now, - } - r.mu.Unlock() -} - -func appliedInteractionDecisionKey(requestID, kind string) string { - return kind + "\x00" + requestID -} - -// interactionDecisionFingerprint excludes the transport delivery id. Each -// retry gets a fresh delivery id so a late ack cannot satisfy a newer waiter, -// while the request plus decision content remains stable for daemon replay. -func interactionDecisionFingerprint(payload any) ([32]byte, error) { - switch decision := payload.(type) { - case proto.PermissionDecisionPayload: - decision.DeliveryID = "" - encoded, err := json.Marshal(decision) - if err != nil { - return [32]byte{}, err - } - return sha256.Sum256(encoded), nil - case proto.PromptForUserChoiceDecisionPayload: - decision.DeliveryID = "" - encoded, err := json.Marshal(decision) - if err != nil { - return [32]byte{}, err - } - return sha256.Sum256(encoded), nil - default: - return [32]byte{}, fmt.Errorf("unsupported interaction decision %T", payload) - } -} - -func (r *Router) sendInteractionDecisionAck(ctx context.Context, request proto.Envelope, deliveryID string, applied bool, errorCode, message string) error { - env, err := request.Reply(proto.TypeInteractionDecisionAck, proto.InteractionDecisionAckPayload{ - DeliveryID: deliveryID, - Applied: applied, - ErrorCode: errorCode, - Error: message, - }) - if err != nil { - return fmt.Errorf("dispatch: build interaction decision ack: %w", err) - } - if err := r.sender.Send(ctx, env); err != nil { - return fmt.Errorf("dispatch: send interaction decision ack: %w", err) - } - return nil -} - -// dropAsk clears askID from both the router-level askIndex and the -// session's pendingAsks set. Safe to call with an askID that's already -// gone — both deletes are no-ops then. -func (r *Router) dropAsk(s *sessionState, askID string) { - r.mu.Lock() - delete(r.askIndex, askID) - delete(s.pendingAsks, askID) - r.mu.Unlock() -} - -// indexPermissionFrame records interaction identities before forwarding them. -// Prepared output may arrive before successful Session publication; decisions -// remain retryable until state.session becomes available. -func (r *Router) indexPermissionFrame(s *sessionState, env proto.Envelope) { - switch env.Type { - case proto.TypePermissionRequest: - var p proto.PermissionRequestPayload - if err := env.DecodePayload(&p); err != nil { - return - } - requestID := strings.TrimSpace(p.RequestID) - if requestID == "" { - return - } - r.mu.Lock() - if r.interactionRouteOpenLocked(s) { - r.permIndex[requestID] = s.runID - s.pendingIDs[requestID] = struct{}{} - } - r.mu.Unlock() - case proto.TypePermissionCancel: - if env.ID == "" { - return - } - r.mu.Lock() - delete(r.permIndex, env.ID) - delete(s.pendingIDs, env.ID) - r.mu.Unlock() - case proto.TypePromptForUserChoice: - // env.ID is the run id (so the server-side dispatch can fan - // this frame to the run's subscriber); the ask id rides on - // the payload. Decode just enough to seed the index. - var p proto.PromptForUserChoicePayload - if err := env.DecodePayload(&p); err != nil || p.AskID == "" { - return - } - r.mu.Lock() - if r.interactionRouteOpenLocked(s) { - r.askIndex[p.AskID] = s.runID - s.pendingAsks[p.AskID] = struct{}{} - } - r.mu.Unlock() - } -} - -func (r *Router) clearInteractionRoutesLocked(s *sessionState) { - for permissionID := range s.pendingIDs { - delete(r.permIndex, permissionID) - delete(s.pendingIDs, permissionID) - } - for askID := range s.pendingAsks { - delete(r.askIndex, askID) - delete(s.pendingAsks, askID) - } -} - -// drain consumes everything left on ch until the agent closes it. -// Events are dropped — by the time we're draining, either transport -// is dead or the router is shutting down. diff --git a/apps/daemon/internal/dispatch/local_directory.go b/apps/daemon/internal/dispatch/local_directory.go deleted file mode 100644 index 57e72d8e9..000000000 --- a/apps/daemon/internal/dispatch/local_directory.go +++ /dev/null @@ -1,7 +0,0 @@ -package dispatch - -// localDirectoryPreparation is a ready read-only preparation. The bound local -// workspace serves its reads, so it holds no native resource. -type localDirectoryPreparation struct{} - -func (localDirectoryPreparation) Close() error { return nil } diff --git a/apps/daemon/internal/dispatch/local_directory_test.go b/apps/daemon/internal/dispatch/local_directory_test.go index e354b668b..d042e7854 100644 --- a/apps/daemon/internal/dispatch/local_directory_test.go +++ b/apps/daemon/internal/dispatch/local_directory_test.go @@ -22,16 +22,13 @@ func TestLocalDirectoryPreparationNeedsNoHarnessAndRejectsOtherOwners(t *testing workspace := t.TempDir() environment, session := uuid.NewString(), preparationSessionID - binding, err := localworkspace.New(environment, session, workspace) + binding, err := localworkspace.NewWithCapabilityDirectory(environment, session, workspace, t.TempDir()) if err != nil { t.Fatal(err) } var harnessCalls atomic.Int32 reg := agent.NewRegistry() - reg.RegisterKind(proto.SupportedAgentKind{Kind: "native", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{LocalEnvironment: proto.CapabilitySupported, WorkspaceReadPreparation: proto.CapabilitySupported})}, harnessconfig.Configuration{}, func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - harnessCalls.Add(1) - return nil, errors.New("must not start a model") - }) + reg.RegisterKind(proto.SupportedAgentKind{Kind: "native", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{LocalEnvironment: proto.CapabilitySupported, WorkspaceReadPreparation: proto.CapabilitySupported})}, harnessconfig.Configuration{}) reg.RegisterExecutor("native", func(context.Context, proto.PromptRequestPayload) (agent.Executor, error) { harnessCalls.Add(1) return nil, errors.New("must not prepare a harness") @@ -98,14 +95,12 @@ func TestLocalDirectoryKeepsNotDirectorySeparateFromFailures(t *testing.T) { t.Fatal(err) } environment, session := uuid.NewString(), preparationSessionID - binding, err := localworkspace.New(environment, session, workspace) + binding, err := localworkspace.NewWithCapabilityDirectory(environment, session, workspace, t.TempDir()) if err != nil { t.Fatal(err) } reg := agent.NewRegistry() - reg.RegisterKind(proto.SupportedAgentKind{Kind: "native", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{LocalEnvironment: proto.CapabilitySupported, WorkspaceReadPreparation: proto.CapabilitySupported})}, harnessconfig.Configuration{}, func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, errors.New("must not start a model") - }) + reg.RegisterKind(proto.SupportedAgentKind{Kind: "native", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{LocalEnvironment: proto.CapabilitySupported, WorkspaceReadPreparation: proto.CapabilitySupported})}, harnessconfig.Configuration{}) reg.RegisterExecutor("native", func(context.Context, proto.PromptRequestPayload) (agent.Executor, error) { return nil, errors.New("must not prepare a harness") }) diff --git a/apps/daemon/internal/dispatch/mcp_http_test.go b/apps/daemon/internal/dispatch/mcp_http_test.go index f0e8b9112..0d8b8cadd 100644 --- a/apps/daemon/internal/dispatch/mcp_http_test.go +++ b/apps/daemon/internal/dispatch/mcp_http_test.go @@ -111,10 +111,7 @@ func TestLocalMCPOriginAndCapabilityAdmission(t *testing.T) { req.Configuration.MCPHTTPServers = nil } entered := make(chan struct{}, 1) - h.reg.RegisterKind(proto.SupportedAgentKind{Kind: "prepared", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{LocalEnvironment: proto.CapabilitySupported, MCPHTTPTools: proto.CapabilityFromBool(mode != "environment missing capability"), MCPHTTPBearerAuth: proto.CapabilitySupported, MCPHTTPRequired: proto.CapabilitySupported})}, prototest.ModelConfiguration(), func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - t.Error("ordinary factory called") - return nil, errors.New("unexpected") - }) + h.reg.RegisterKind(proto.SupportedAgentKind{Kind: "prepared", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{LocalEnvironment: proto.CapabilitySupported, MCPHTTPTools: proto.CapabilityFromBool(mode != "environment missing capability"), MCPHTTPBearerAuth: proto.CapabilitySupported, MCPHTTPRequired: proto.CapabilitySupported})}, prototest.ModelConfiguration()) h.reg.RegisterExecutor("prepared", func(context.Context, proto.PromptRequestPayload) (agent.Executor, error) { entered <- struct{}{} return nil, errors.New("controlled stop") diff --git a/apps/daemon/internal/dispatch/optional_interactions_test.go b/apps/daemon/internal/dispatch/optional_interactions_test.go deleted file mode 100644 index b799480bc..000000000 --- a/apps/daemon/internal/dispatch/optional_interactions_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package dispatch_test - -import ( - "context" - "testing" - - "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto/prototest" -) - -// Wrapping Session proves that no responder stubs are required for a Session. -type lifecycleOnly struct{ agent.Session } - -func TestOptionalInteractionResponders(t *testing.T) { - for _, ask := range []bool{false, true} { - t.Run(map[bool]string{false: "permission", true: "user choice"}[ask], func(t *testing.T) { - h := newHarness(t) - defer h.router.Shutdown(context.Background()) - var output chan<- proto.Envelope - registerSession(h.reg, proto.SupportedAgentKind{Kind: "minimal", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, func(_ context.Context, _ proto.PromptRequestPayload, out chan<- proto.Envelope) (agent.Session, error) { - output = out - s := &fakeSession{out: out, closeOutOnCancel: true} - return lifecycleOnly{Session: s}, nil - }) - startRun(t, h.router, h.sender, "minimal", "run") - event := mustEnv(t, proto.TypePermissionRequest, "run", proto.PermissionRequestPayload{RequestID: "interaction", Tool: "fixture"}) - decision := scoped(t, "run", proto.TypePermissionDecision, "interaction", proto.PermissionDecisionPayload{DeliveryID: "decision", Approved: true}) - if ask { - event = mustEnv(t, proto.TypePromptForUserChoice, "run", proto.PromptForUserChoicePayload{AskID: "interaction"}) - decision = scoped(t, "run", proto.TypePromptForUserChoiceDecision, "interaction", proto.PromptForUserChoiceDecisionPayload{DeliveryID: "decision"}) - } - // An inconsistent adapter emitted an interaction it cannot answer: reject it, - // never acknowledge application or call a fabricated responder. - output <- event - waitFor(t, func() bool { return hasFrame(h.sender, event.Type, "run") }, "interaction indexed") - if err := h.router.Handle(t.Context(), decision); err != nil { - t.Fatal(err) - } - assertDecisionAck(t, h.sender, "decision", false, "unsupported") - }) - } -} diff --git a/apps/daemon/internal/dispatch/preparation.go b/apps/daemon/internal/dispatch/preparation.go index 3dea80ef8..81e51fabc 100644 --- a/apps/daemon/internal/dispatch/preparation.go +++ b/apps/daemon/internal/dispatch/preparation.go @@ -5,7 +5,6 @@ import ( "crypto/sha256" "encoding/json" "errors" - "io" "strings" "time" @@ -23,21 +22,19 @@ type preparationState struct { executor *executorState // request is the execution_prepare's ID, trace and assignment, which // every status echoes. - request proto.Envelope - fingerprint [32]byte - startFingerprint [32]byte - status proto.PreparationStatusPayload - deadline time.Time - timer *time.Timer - ctx context.Context - cancel context.CancelFunc - prepared io.Closer - environmentID string - busy bool - owns bool - closeErr error - workspaceReadOnly bool - handoff *preparedHandoff + request proto.Envelope + fingerprint [32]byte + startFingerprint [32]byte + status proto.PreparationStatusPayload + deadline time.Time + timer *time.Timer + ctx context.Context + cancel context.CancelFunc + environmentID string + busy bool + owns bool + closeErr error + handoff *preparedHandoff } func (r *Router) handleExecutionPrepare(ctx context.Context, env proto.Envelope) error { @@ -109,10 +106,10 @@ func (r *Router) handleExecutionPrepare(ctx context.Context, env proto.Envelope) return r.rejectPreparation(env, "preparation_capacity") } owner, cancel := context.WithCancel(context.WithoutCancel(ctx)) - p := &preparationState{capabilities: caps, request: proto.Envelope{ID: env.ID, Trace: env.Trace, Assignment: env.Assignment}, fingerprint: fingerprint, ctx: owner, cancel: cancel, environmentID: req.EnvironmentID(), workspaceReadOnly: req.WorkspaceReadOnly, busy: true, owns: true, deadline: time.Now().Add(r.preparationTimeout)} + p := &preparationState{capabilities: caps, request: proto.Envelope{ID: env.ID, Trace: env.Trace, Assignment: env.Assignment}, fingerprint: fingerprint, ctx: owner, cancel: cancel, environmentID: req.EnvironmentID(), busy: true, owns: true, deadline: time.Now().Add(r.preparationTimeout)} p.status = proto.PreparationStatusPayload{Handle: uuid.NewString(), Revision: 1, State: "preparing", ExpiresAt: p.deadline.UnixMilli()} r.preparations[p.status.Handle], r.preparationRequests[p.request.ID] = p, p - p.timer = time.AfterFunc(r.preparationTimeout, func() { r.releasePreparation(p, "expired", "", true, true) }) + p.timer = time.AfterFunc(r.preparationTimeout, func() { r.releasePreparation(p, "expired", "", true) }) r.shutdownWG.Add(1) r.mu.Unlock() go r.prepareExecution(p) @@ -123,30 +120,30 @@ func (r *Router) handleExecutionPrepare(ctx context.Context, env proto.Envelope) func (r *Router) prepareExecution(p *preparationState) { defer r.shutdownWG.Done() if !r.sendPreparation(p.request, proto.PreparationStatusPayload{Handle: p.status.Handle, Revision: 1, State: "preparing", ExpiresAt: p.deadline.UnixMilli()}) { - r.releasePreparation(p, "failed", "status_delivery_failed", false, false) + r.releasePreparation(p, "failed", "status_delivery_failed", false) } r.mu.Lock() p.busy = false ready := p.status.State == "preparing" && p.ctx.Err() == nil && !r.closed if ready { - p.prepared = localDirectoryPreparation{} p.status.State, p.status.Revision = "ready", p.status.Revision+1 - } else if p.status.State == "preparing" { - p.status.State, p.status.ErrorCode, p.status.Revision = "failed", "preparation_failed", p.status.Revision+1 - } - status := p.status - if !ready { - p.busy = true + } else { + if p.status.State == "preparing" { + p.status.State, p.status.ErrorCode, p.status.Revision = "failed", "preparation_failed", p.status.Revision+1 + } + // A release or shutdown during readiness left ownership to this return. + p.owns = false p.cancel() p.timer.Stop() } + status := p.status r.mu.Unlock() if !ready { - r.closePreparationResource(p) + r.publishPreparation(p, status) return } if !r.sendPreparation(p.request, status) { - r.releasePreparation(p, "failed", "status_delivery_failed", false, false) + r.releasePreparation(p, "failed", "status_delivery_failed", false) } } @@ -162,11 +159,11 @@ func (r *Router) handleExecutionRelease(_ context.Context, env proto.Envelope) e if !valid { return r.rejectPreparation(env, "unknown_preparation") } - r.releasePreparation(p, "released", "", true, true) + r.releasePreparation(p, "released", "", true) return nil } -func (r *Router) releasePreparation(p *preparationState, state, code string, publish, retryHandoff bool) { +func (r *Router) releasePreparation(p *preparationState, state, code string, publish bool) { r.mu.Lock() if r.closed || r.suspension != nil { r.mu.Unlock() @@ -177,48 +174,21 @@ func (r *Router) releasePreparation(p *preparationState, state, code string, pub r.abandonExecutorAdmission(p, state, code, publish) return } - if p.handoff != nil { - if active := r.sessions[p.status.RunID]; active != nil && active.preparedHandoff == p.handoff { - if state == "expired" && p.handoff.published { - r.mu.Unlock() - return - } - switch p.status.State { - case "preparing", "ready", "starting", "started": - p.status.State, p.status.ErrorCode, p.status.Revision = state, code, p.status.Revision+1 - p.timer.Stop() - } - r.claimPreparedReleaseLocked(active, true, "", retryHandoff) - status := p.status - r.mu.Unlock() - if publish { - r.publishPreparation(p, status) - } - return - } - } - closeResource := false switch p.status.State { - case "preparing", "ready", "starting": + case "preparing", "ready": p.status.State, p.status.ErrorCode, p.status.Revision = state, code, p.status.Revision+1 p.cancel() p.timer.Stop() } - if p.owns && !p.busy { - if p.workspaceReadOnly && state == "released" && p.status.ErrorCode == "cleanup_unconfirmed" { - p.status.State, p.status.ErrorCode, p.status.Revision = state, "", p.status.Revision+1 - } - p.busy = true - closeResource = true - r.shutdownWG.Add(1) + // A busy preparation drops ownership and reports when readiness returns. + // Dropping ownership here always reports it. + report := !p.busy && (publish || p.owns) + if !p.busy { + p.owns = false } status := p.status - settled := !p.owns && !p.busy r.mu.Unlock() - if closeResource { - go func() { defer r.shutdownWG.Done(); r.closePreparationResource(p) }() - } - if publish && (!p.workspaceReadOnly || settled) { + if report { r.publishPreparation(p, status) } } @@ -244,11 +214,9 @@ func (r *Router) prunePreparationsLocked() { func (r *Router) publishPreparation(p *preparationState, status proto.PreparationStatusPayload) { r.mu.Lock() - if p.workspaceReadOnly { - if status.Revision != p.status.Revision || (p.owns && (p.busy || status.State == "released" || status.State == "expired") && status.State != "preparing" && status.State != "ready") { - r.mu.Unlock() - return - } + if p.executor == nil && status.Revision != p.status.Revision { + r.mu.Unlock() + return } if r.closed || r.suspension != nil { r.mu.Unlock() @@ -259,8 +227,8 @@ func (r *Router) publishPreparation(p *preparationState, status proto.Preparatio go func() { defer r.shutdownWG.Done() // A failed terminal notification must not restart incomplete cleanup. - if !r.sendPreparation(p.request, status) && (!p.workspaceReadOnly || status.State == "preparing" || status.State == "ready") { - r.releasePreparation(p, "failed", "status_delivery_failed", false, false) + if !r.sendPreparation(p.request, status) && (p.executor != nil || status.State == "preparing" || status.State == "ready") { + r.releasePreparation(p, "failed", "status_delivery_failed", false) } }() } diff --git a/apps/daemon/internal/dispatch/preparation_cancel_test.go b/apps/daemon/internal/dispatch/preparation_cancel_test.go index 5ee5e3cf3..5804650cb 100644 --- a/apps/daemon/internal/dispatch/preparation_cancel_test.go +++ b/apps/daemon/internal/dispatch/preparation_cancel_test.go @@ -86,8 +86,6 @@ func TestPreparedCancellationWaitsForOutputAndCleanup(t *testing.T) { session = &fakeSession{out: out, closeOutOnCancel: true, postCancelEnvelopes: []proto.Envelope{mustEnv(t, proto.TypeDone, id, p.outcome)}} out <- mustEnv(t, proto.TypeDelta, id, proto.DeltaPayload{Delta: "observed"}) - out <- mustEnv(t, proto.TypePermissionRequest, id, proto.PermissionRequestPayload{RequestID: "permission"}) - out <- mustEnv(t, proto.TypePromptForUserChoice, id, proto.PromptForUserChoicePayload{AskID: "ask"}) out <- mustEnv(t, proto.TypeUsage, id, proto.UsagePayload{Usage: p.outcome.Usage}) close(startEntered) <-startReturn @@ -124,16 +122,6 @@ func TestPreparedCancellationWaitsForOutputAndCleanup(t *testing.T) { if len(cancellationAcks(sender.recSender)) != 0 || r.ActiveRuns() != 1 { t.Fatal("cleanup released ownership early") } - for _, decision := range []proto.Envelope{ - mustEnv(t, proto.TypePermissionDecision, "permission", proto.PermissionDecisionPayload{DeliveryID: "permission-reply", Approved: true}), - mustEnv(t, proto.TypePromptForUserChoiceDecision, "ask", proto.PromptForUserChoiceDecisionPayload{DeliveryID: "ask-reply", QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "q0", Answers: []string{"yes"}}}}), - } { - if err := r.Handle(t.Context(), decision); err != nil { - t.Fatal(err) - } - } - assertDecisionAck(t, sender.recSender, "permission-reply", false, "not_pending") - assertDecisionAck(t, sender.recSender, "ask-reply", false, "not_pending") close(cleanupReturn) waitFor(t, func() bool { return len(cancellationAcks(sender.recSender)) == 2 && r.ActiveRuns() == 0 }, "prepared cancellation receipts") if p.calls.Load() != 1 || session.cancels() != 1 { @@ -145,7 +133,7 @@ func TestPreparedCancellationWaitsForOutputAndCleanup(t *testing.T) { } } got := sender.typesFor("run") - want := []string{proto.TypeDelta, proto.TypePermissionRequest, proto.TypePromptForUserChoice, proto.TypeUsage, proto.TypeDone, proto.TypeInteractionDecisionAck, proto.TypeInteractionDecisionAck} + want := []string{proto.TypeDelta, proto.TypeUsage, proto.TypeDone, proto.TypeInteractionDecisionAck, proto.TypeInteractionDecisionAck} if !reflect.DeepEqual(got, want) { t.Fatalf("output/receipt order = %v", got) } diff --git a/apps/daemon/internal/dispatch/preparation_cleanup.go b/apps/daemon/internal/dispatch/preparation_cleanup.go index 2a51c0e8c..97b77dbc6 100644 --- a/apps/daemon/internal/dispatch/preparation_cleanup.go +++ b/apps/daemon/internal/dispatch/preparation_cleanup.go @@ -1,35 +1,6 @@ package dispatch -func (r *Router) closePreparationResource(p *preparationState) error { - // Only the operation that owns busy calls this; other paths cancel its owner. - var err error - if p.prepared != nil { - err = p.prepared.Close() - } - r.mu.Lock() - p.busy, p.closeErr = false, err - if err == nil { - p.prepared, p.owns = nil, false - } - if p.workspaceReadOnly { - p.status.Revision++ - if err != nil { - p.status.State, p.status.ErrorCode = "failed", "cleanup_unconfirmed" - } - } - status := p.status - r.mu.Unlock() - if p.workspaceReadOnly { - r.publishPreparation(p, status) - } - if err != nil { - r.log.Warn("preparation cleanup incomplete", "handle", p.status.Handle) - } - return err -} - -func (r *Router) closePendingPreparationsLocked() []*preparationState { - var closeNow []*preparationState +func (r *Router) closePendingPreparationsLocked() { for _, p := range r.preparations { p.timer.Stop() if p.executor != nil { @@ -38,19 +9,14 @@ func (r *Router) closePendingPreparationsLocked() []*preparationState { if !p.owns { continue } - if p.handoff != nil { - continue - } p.cancel() switch p.status.State { - case "preparing", "ready", "starting": + case "preparing", "ready": p.status.State, p.status.ErrorCode, p.status.Revision = "failed", "connection_closed", p.status.Revision+1 } + // A busy preparation drops ownership when readiness returns. if !p.busy { - p.busy = true - r.shutdownWG.Add(1) - closeNow = append(closeNow, p) + p.owns = false } } - return closeNow } diff --git a/apps/daemon/internal/dispatch/preparation_executor_fixture_test.go b/apps/daemon/internal/dispatch/preparation_executor_fixture_test.go index a5966ce69..5e9e778d2 100644 --- a/apps/daemon/internal/dispatch/preparation_executor_fixture_test.go +++ b/apps/daemon/internal/dispatch/preparation_executor_fixture_test.go @@ -122,15 +122,3 @@ func (t *preparationTurn) SteerWithReceipt(ctx context.Context, p proto.PromptSt } return agent.ErrSteeringInactive } -func (t *preparationTurn) SubmitPermission(ctx context.Context, id string, p proto.PermissionDecisionPayload) error { - if target, ok := t.Session.(agent.PermissionResponder); ok { - return target.SubmitPermission(ctx, id, p) - } - return agent.ErrUnknownPermission -} -func (t *preparationTurn) SubmitPromptForUserChoice(ctx context.Context, id string, p proto.PromptForUserChoiceDecisionPayload) error { - if target, ok := t.Session.(agent.UserChoiceResponder); ok { - return target.SubmitPromptForUserChoice(ctx, id, p) - } - return agent.ErrUnknownAsk -} diff --git a/apps/daemon/internal/dispatch/preparation_start.go b/apps/daemon/internal/dispatch/preparation_start.go index 2c09428c0..dd803d938 100644 --- a/apps/daemon/internal/dispatch/preparation_start.go +++ b/apps/daemon/internal/dispatch/preparation_start.go @@ -41,12 +41,12 @@ func (r *Router) handleExecutionStart(_ context.Context, env proto.Envelope) err r.mu.Unlock() return r.rejectPreparation(env, code) } - if p.workspaceReadOnly { + if p.executor == nil { r.mu.Unlock() return r.rejectPreparation(env, "read_only_preparation") } owner := p.executor - if owner == nil || owner.id != input.ExecutorID { + if owner.id != input.ExecutorID { r.mu.Unlock() return r.rejectPreparation(env, "unknown_executor") } @@ -69,7 +69,7 @@ func (r *Router) handleExecutionStart(_ context.Context, env proto.Envelope) err } if !time.Now().Before(p.deadline) { r.mu.Unlock() - r.releasePreparation(p, "expired", "", true, true) + r.releasePreparation(p, "expired", "", true) return nil } if r.sessions[input.RunID] != nil { @@ -78,7 +78,7 @@ func (r *Router) handleExecutionStart(_ context.Context, env proto.Envelope) err } p.status.State, p.status.RunID, p.status.Revision = "starting", input.RunID, p.status.Revision+1 p.startFingerprint, p.busy = fingerprint, true - state := &sessionState{assignment: env.Assignment, capabilities: p.capabilities, runID: input.RunID, environmentID: p.environmentID, out: make(chan proto.Envelope, 64), ctx: p.ctx, pendingIDs: make(map[string]struct{}), pendingAsks: make(map[string]struct{}), traceparent: env.Trace} + state := &sessionState{assignment: env.Assignment, capabilities: p.capabilities, runID: input.RunID, environmentID: p.environmentID, out: make(chan proto.Envelope, 64), ctx: p.ctx, traceparent: env.Trace} state.preparedHandoff = newPreparedHandoff(p, owner.native) p.handoff, owner.run = state.preparedHandoff, state r.sessions[input.RunID] = state diff --git a/apps/daemon/internal/dispatch/preparation_test.go b/apps/daemon/internal/dispatch/preparation_test.go index bd4fbe3ea..bb39c151f 100644 --- a/apps/daemon/internal/dispatch/preparation_test.go +++ b/apps/daemon/internal/dispatch/preparation_test.go @@ -121,9 +121,7 @@ func preparationRequest() proto.ExecutionPreparePayload { func preparationRouter(t *testing.T, sender dispatch.Sender, timeout time.Duration, factory preparationFactory) *dispatch.Router { t.Helper() reg := agent.NewRegistry() - reg.RegisterKind(proto.SupportedAgentKind{Kind: "prepared", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{LocalEnvironment: proto.CapabilitySupported, WorkspaceReadPreparation: proto.CapabilitySupported, Permissions: proto.CapabilitySupported, FunctionTools: proto.CapabilitySupported, Steering: proto.CapabilitySupported, DurableInputReceipts: proto.CapabilitySupported})}, prototest.ModelConfiguration(), func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, errors.New("ordinary Factory must not be used for preparation") - }) + reg.RegisterKind(proto.SupportedAgentKind{Kind: "prepared", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{LocalEnvironment: proto.CapabilitySupported, WorkspaceReadPreparation: proto.CapabilitySupported, FunctionTools: proto.CapabilitySupported, Steering: proto.CapabilitySupported, DurableInputReceipts: proto.CapabilitySupported})}, prototest.ModelConfiguration()) reg.RegisterExecutor("prepared", preparationExecutorFixture(factory)) r, err := dispatch.New(dispatch.Config{Registry: reg, Sender: sender, PreparationTimeout: timeout, LocalWorkspace: preparationWorkspace(t)}) if err != nil { diff --git a/apps/daemon/internal/dispatch/prepared_handoff.go b/apps/daemon/internal/dispatch/prepared_handoff.go index 16bed1669..772cce4a5 100644 --- a/apps/daemon/internal/dispatch/prepared_handoff.go +++ b/apps/daemon/internal/dispatch/prepared_handoff.go @@ -89,7 +89,7 @@ func (r *Router) preparedOperationLocked(state *sessionState) (agent.Turn, func( return state.session, handoff.operations.RUnlock, true } -func (r *Router) interactionRouteOpenLocked(state *sessionState) bool { +func (r *Router) runRouteOpenLocked(state *sessionState) bool { return state != nil && !r.closed && state.ctx.Err() == nil && !state.steeringClosed && state.preparedHandoff.release == nil } @@ -104,7 +104,6 @@ func (r *Router) claimPreparedReleaseLocked(state *sessionState, abort bool, fai handoff.release = release state.steeringClosed = true state.session = nil - r.clearInteractionRoutesLocked(state) } if abort && !release.aborted() { close(release.abort) @@ -328,10 +327,6 @@ func (r *Router) forwardPreparedOutput(state *sessionState) { if drainOnly { continue } - switch env.Type { - case proto.TypePermissionRequest, proto.TypePermissionCancel, proto.TypePromptForUserChoice: - r.indexPermissionFrame(state, env) - } if err := r.sendSessionOutput(pumpCtx, state, env); err != nil { r.mu.Lock() if r.closed { diff --git a/apps/daemon/internal/dispatch/prepared_handoff_mutation_test.go b/apps/daemon/internal/dispatch/prepared_handoff_mutation_test.go index 7689961ed..0a04ea8e2 100644 --- a/apps/daemon/internal/dispatch/prepared_handoff_mutation_test.go +++ b/apps/daemon/internal/dispatch/prepared_handoff_mutation_test.go @@ -71,7 +71,7 @@ func (s *blockingPreparedReceiptSender) matches(env proto.Envelope) bool { } func TestPreparedHandoffReleaseWaitsForMutationReceipt(t *testing.T) { - for _, operation := range []string{"function", "permission", "choice", "steering"} { + for _, operation := range []string{"function", "steering"} { t.Run(operation, func(t *testing.T) { sender := &blockingPreparedReceiptSender{ recSender: &recSender{}, deliveryID: operation + "-delivery", inputID: operation + "-input", @@ -91,14 +91,6 @@ func TestPreparedHandoffReleaseWaitsForMutationReceipt(t *testing.T) { switch operation { case "function": mutation = mustEnv(t, proto.TypeFunctionResult, "run", proto.FunctionResultPayload{CallID: "call", Success: true, Content: functionResultContent("answer"), DeliveryID: sender.deliveryID}) - case "permission": - session.out <- mustEnv(t, proto.TypePermissionRequest, "run", proto.PermissionRequestPayload{RequestID: "permission"}) - waitFor(t, func() bool { return hasFrame(sender.recSender, proto.TypePermissionRequest, "run") }, "permission request") - mutation = mustEnv(t, proto.TypePermissionDecision, "permission", proto.PermissionDecisionPayload{DeliveryID: sender.deliveryID, Approved: true}) - case "choice": - session.out <- mustEnv(t, proto.TypePromptForUserChoice, "run", proto.PromptForUserChoicePayload{AskID: "ask"}) - waitFor(t, func() bool { return hasFrame(sender.recSender, proto.TypePromptForUserChoice, "run") }, "choice request") - mutation = mustEnv(t, proto.TypePromptForUserChoiceDecision, "ask", proto.PromptForUserChoiceDecisionPayload{DeliveryID: sender.deliveryID, QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "q0", Answers: []string{"yes"}}}}) case "steering": mutation = mustEnv(t, proto.TypePromptSteer, "run", proto.PromptSteerPayload{InputID: sender.inputID, Input: proto.TextInput("continue")}) } @@ -119,28 +111,6 @@ func TestPreparedHandoffReleaseWaitsForMutationReceipt(t *testing.T) { t.Fatal(err) } assertDecisionAck(t, sender.recSender, "late-function", false, "not_ready") - case "permission": - late := mustEnv(t, proto.TypePermissionDecision, "permission", proto.PermissionDecisionPayload{DeliveryID: "late-permission", Approved: true}) - if err := r.Handle(t.Context(), late); err != nil { - t.Fatal(err) - } - assertDecisionAck(t, sender.recSender, "late-permission", true, "") - newRequest := mustEnv(t, proto.TypePermissionDecision, "new-permission", proto.PermissionDecisionPayload{DeliveryID: "new-permission", Approved: true}) - if err := r.Handle(t.Context(), newRequest); err != nil { - t.Fatal(err) - } - assertDecisionAck(t, sender.recSender, "new-permission", false, "not_pending") - case "choice": - late := mustEnv(t, proto.TypePromptForUserChoiceDecision, "ask", proto.PromptForUserChoiceDecisionPayload{DeliveryID: "late-choice", QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "q0", Answers: []string{"yes"}}}}) - if err := r.Handle(t.Context(), late); err != nil { - t.Fatal(err) - } - assertDecisionAck(t, sender.recSender, "late-choice", true, "") - newRequest := mustEnv(t, proto.TypePromptForUserChoiceDecision, "new-choice", proto.PromptForUserChoiceDecisionPayload{DeliveryID: "new-choice", QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "q0", Answers: []string{"yes"}}}}) - if err := r.Handle(t.Context(), newRequest); err != nil { - t.Fatal(err) - } - assertDecisionAck(t, sender.recSender, "new-choice", false, "not_pending") case "steering": late := mustEnv(t, proto.TypePromptSteer, "run", proto.PromptSteerPayload{InputID: "late-steering", Input: proto.TextInput("late")}) if err := r.Handle(t.Context(), late); err != nil { @@ -181,17 +151,6 @@ func TestPreparedHandoffReleaseWaitsForMutationReceipt(t *testing.T) { if session.functions.Load() != 1 { t.Fatalf("function native calls = %d, want 1", session.functions.Load()) } - case "permission": - if len(session.submissions()) != 1 { - t.Fatalf("permission native calls = %d, want 1", len(session.submissions())) - } - case "choice": - session.askMu.Lock() - calls := len(session.askCalls) - session.askMu.Unlock() - if calls != 1 { - t.Fatalf("choice native calls = %d, want 1", calls) - } case "steering": if session.steers.Load() != 1 { t.Fatalf("steering native calls = %d, want 1", session.steers.Load()) @@ -228,7 +187,7 @@ func assertReceiptBeforeDone(t *testing.T, sender *recSender, operation string) } func TestPreparedHandoffRouterShutdownWaitsForReceiptAttempt(t *testing.T) { - for _, operation := range []string{"function", "permission", "choice", "steering"} { + for _, operation := range []string{"function", "steering"} { t.Run(operation, func(t *testing.T) { sender := &blockingPreparedReceiptSender{recSender: &recSender{}, deliveryID: operation + "-delivery", inputID: operation + "-input", entered: make(chan struct{}), release: make(chan struct{}), exited: make(chan struct{})} defer close(sender.release) @@ -246,14 +205,6 @@ func TestPreparedHandoffRouterShutdownWaitsForReceiptAttempt(t *testing.T) { switch operation { case "function": mutation = mustEnv(t, proto.TypeFunctionResult, "run", proto.FunctionResultPayload{CallID: "call", Success: true, Content: functionResultContent("answer"), DeliveryID: sender.deliveryID}) - case "permission": - session.out <- mustEnv(t, proto.TypePermissionRequest, "run", proto.PermissionRequestPayload{RequestID: "permission"}) - waitFor(t, func() bool { return hasFrame(sender.recSender, proto.TypePermissionRequest, "run") }, "permission request") - mutation = mustEnv(t, proto.TypePermissionDecision, "permission", proto.PermissionDecisionPayload{DeliveryID: sender.deliveryID, Approved: true}) - case "choice": - session.out <- mustEnv(t, proto.TypePromptForUserChoice, "run", proto.PromptForUserChoicePayload{AskID: "ask"}) - waitFor(t, func() bool { return hasFrame(sender.recSender, proto.TypePromptForUserChoice, "run") }, "choice request") - mutation = mustEnv(t, proto.TypePromptForUserChoiceDecision, "ask", proto.PromptForUserChoiceDecisionPayload{DeliveryID: sender.deliveryID, QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "q0", Answers: []string{"yes"}}}}) case "steering": mutation = mustEnv(t, proto.TypePromptSteer, "run", proto.PromptSteerPayload{InputID: sender.inputID, Input: proto.TextInput("continue")}) } diff --git a/apps/daemon/internal/dispatch/prepared_handoff_test.go b/apps/daemon/internal/dispatch/prepared_handoff_test.go index c4cf45c91..3a76fbac6 100644 --- a/apps/daemon/internal/dispatch/prepared_handoff_test.go +++ b/apps/daemon/internal/dispatch/prepared_handoff_test.go @@ -161,8 +161,6 @@ func TestPreparedHandoffDuplicateStartDoesNotReexecuteDuringPublication(t *testi p := &controlledPreparation{closed: make(chan struct{})} p.start = func(_ context.Context, _ string, _ proto.MessageInput, out chan<- proto.Envelope) (agent.Session, error) { session.out = out - out <- mustEnv(t, proto.TypePermissionRequest, "run", proto.PermissionRequestPayload{RequestID: "publication-permission"}) - out <- mustEnv(t, proto.TypePromptForUserChoice, "run", proto.PromptForUserChoicePayload{AskID: "publication-choice"}) return session, nil } r := preparationRouter(t, sender, time.Minute, func(context.Context, proto.PromptRequestPayload) (preparedFixture, error) { return p, nil }) @@ -172,21 +170,11 @@ func TestPreparedHandoffDuplicateStartDoesNotReexecuteDuringPublication(t *testi case <-time.After(2 * time.Second): t.Fatal("started status did not reach publication boundary") } - waitFor(t, func() bool { - return hasFrame(sender.recSender, proto.TypePermissionRequest, "run") && hasFrame(sender.recSender, proto.TypePromptForUserChoice, "run") - }, "pre-publication interaction routes") - for _, mutation := range []proto.Envelope{ - mustEnv(t, proto.TypeFunctionResult, "run", proto.FunctionResultPayload{CallID: "call", Success: true, Content: functionResultContent("answer"), DeliveryID: "publication-function"}), - mustEnv(t, proto.TypePermissionDecision, "publication-permission", proto.PermissionDecisionPayload{DeliveryID: "publication-permission", Approved: true}), - mustEnv(t, proto.TypePromptForUserChoiceDecision, "publication-choice", proto.PromptForUserChoiceDecisionPayload{DeliveryID: "publication-choice", QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "q0", Answers: []string{"yes"}}}}), - } { - if err := r.Handle(t.Context(), mutation); err != nil { - t.Fatal(err) - } + result := mustEnv(t, proto.TypeFunctionResult, "run", proto.FunctionResultPayload{CallID: "call", Success: true, Content: functionResultContent("answer"), DeliveryID: "publication-function"}) + if err := r.Handle(t.Context(), result); err != nil { + t.Fatal(err) } assertDecisionAck(t, sender.recSender, "publication-function", false, "not_ready") - assertDecisionAck(t, sender.recSender, "publication-permission", false, "not_ready") - assertDecisionAck(t, sender.recSender, "publication-choice", false, "not_ready") if err := r.Handle(t.Context(), mustEnv(t, proto.TypePromptSteer, "run", proto.PromptSteerPayload{InputID: "publication-steering", Input: proto.TextInput("continue")})); err != nil { t.Fatal(err) } @@ -202,10 +190,7 @@ func TestPreparedHandoffDuplicateStartDoesNotReexecuteDuringPublication(t *testi if len(frames) == 0 || frames[len(frames)-1].Type != proto.TypeWorkspaceReadResult || frames[len(frames)-1].DecodePayload(&readResult) != nil || readResult.ErrorCode != "resource_unavailable" { t.Fatalf("pre-publication workspace read = %+v", frames) } - session.askMu.Lock() - askCalls := len(session.askCalls) - session.askMu.Unlock() - if session.functions.Load() != 0 || session.steers.Load() != 0 || len(session.submissions()) != 0 || askCalls != 0 { + if session.functions.Load() != 0 || session.steers.Load() != 0 { t.Fatal("private Session accepted work before started publication") } duplicate := mustEnv(t, proto.TypeExecutionStart, "request", proto.ExecutionStartPayload{Handle: ready.Handle, ExecutorID: ready.ExecutorID, RunID: "run", Input: proto.TextInput("input")}) diff --git a/apps/daemon/internal/dispatch/router.go b/apps/daemon/internal/dispatch/router.go index a9c59e368..39cbf3065 100644 --- a/apps/daemon/internal/dispatch/router.go +++ b/apps/daemon/internal/dispatch/router.go @@ -1,8 +1,7 @@ // Package dispatch wires inbound WebSocket frames to the agent layer. // It owns the Session assignments of its connection, one Turn per active -// RunID with a goroutine that forwards the Turn's events to the transport, -// and a permission_id → run_id index so permission_decision frames route -// back to the right run. +// RunID and a per-Turn output goroutine that forwards the agent's events to +// the transport. // // Concurrency: Handle is safe for one goroutine (typically the read // loop). Each run has its own goroutines. Internal state is @@ -38,11 +37,9 @@ type Router struct { suspension *proto.EnvironmentSuspendPayload suspendedBy proto.AssignmentRef // the assignment that quiesced mu sync.Mutex - assignments map[string]*assignmentState // SessionID → assignment - sessions map[string]*sessionState // RunID → state - permIndex map[string]string // permID → RunID - askIndex map[string]string // askID → RunID - applied map[string]appliedInteractionDecision + assignments map[string]*assignmentState // SessionID → assignment + sessions map[string]*sessionState // RunID → state + applied map[string]appliedFunctionResult // RunID and call ID → applied result shutdownAttempt *shutdownAttempt shutdownCh chan struct{} // closed by Shutdown shutdownWG dispatchWork // waits for all pump goroutines @@ -61,9 +58,7 @@ type Router struct { removeHome func(sessionID string) error } -type appliedInteractionDecision struct { - requestID string - kind string +type appliedFunctionResult struct { fingerprint [32]byte assignment proto.AssignmentRef recordedAt time.Time @@ -82,8 +77,6 @@ type sessionState struct { session agent.Turn out chan proto.Envelope ctx context.Context - pendingIDs map[string]struct{} - pendingAsks map[string]struct{} traceparent string steering map[string]steeringReceipt steerBusy bool @@ -141,9 +134,7 @@ func New(cfg Config) (*Router, error) { log: log, assignments: make(map[string]*assignmentState), sessions: make(map[string]*sessionState), - permIndex: make(map[string]string), - askIndex: make(map[string]string), - applied: make(map[string]appliedInteractionDecision), + applied: make(map[string]appliedFunctionResult), shutdownCh: make(chan struct{}), idleTimeout: idleTimeout, executors: make(map[string]*executorState), @@ -204,10 +195,6 @@ func (r *Router) Handle(ctx context.Context, env proto.Envelope) error { return r.handleFunctionResult(ctx, env) case proto.TypePromptSteer: return r.handlePromptSteer(ctx, env) - case proto.TypePermissionDecision: - return r.handlePermissionDecision(ctx, env) - case proto.TypePromptForUserChoiceDecision: - return r.handlePromptForUserChoiceDecision(ctx, env) default: r.log.WarnContext(ctx, "unsupported envelope type", "type", env.Type, "id", env.ID) return r.reply(ctx, env, proto.TypeProtocolError, proto.ProtocolErrorPayload{Type: env.Type, ErrorCode: proto.UnsupportedOperation}) @@ -223,7 +210,7 @@ func adoptEnvelopeTrace(ctx context.Context, env proto.Envelope) context.Context return obslog.WithTrace(ctx, carrier) } } - ctx, _ = obslog.StartBackgroundTrace(ctx, "daemon.envelope") + ctx, _ = obslog.StartBackgroundTrace(ctx) return ctx } @@ -251,11 +238,5 @@ func (r *Router) drain(ch <-chan proto.Envelope) { func (r *Router) cleanupSession(s *sessionState) { r.mu.Lock() delete(r.sessions, s.runID) - for permID := range s.pendingIDs { - delete(r.permIndex, permID) - } - for askID := range s.pendingAsks { - delete(r.askIndex, askID) - } r.mu.Unlock() } diff --git a/apps/daemon/internal/dispatch/router_test.go b/apps/daemon/internal/dispatch/router_test.go index 1d755905b..5b19337a9 100644 --- a/apps/daemon/internal/dispatch/router_test.go +++ b/apps/daemon/internal/dispatch/router_test.go @@ -62,12 +62,7 @@ func (s *recSender) typesFor(runID string) []string { // channel and manipulates outbound traffic / cancel observability. type fakeSession struct { cancelCalls int - submitCalls []permCall - askCalls []askCall - submitErr error - askErr error - cancelMu, submitMu sync.Mutex - askMu sync.Mutex + cancelMu sync.Mutex closeOutOnCancel bool out chan<- proto.Envelope closeOutOnCancelMu sync.Once @@ -75,16 +70,6 @@ type fakeSession struct { ctx context.Context } -type permCall struct { - id string - decision proto.PermissionDecisionPayload -} - -type askCall struct { - id string - decision proto.PromptForUserChoiceDecisionPayload -} - func (s *fakeSession) CancellationOutcome() proto.DonePayload { return proto.DonePayload{} } func (s *fakeSession) Cancel(context.Context) error { @@ -102,34 +87,12 @@ func (s *fakeSession) Cancel(context.Context) error { return nil } -func (s *fakeSession) SubmitPermission(_ context.Context, permID string, dec proto.PermissionDecisionPayload) error { - s.submitMu.Lock() - s.submitCalls = append(s.submitCalls, permCall{id: permID, decision: dec}) - s.submitMu.Unlock() - return s.submitErr -} - -func (s *fakeSession) SubmitPromptForUserChoice(_ context.Context, askID string, dec proto.PromptForUserChoiceDecisionPayload) error { - s.askMu.Lock() - s.askCalls = append(s.askCalls, askCall{id: askID, decision: dec}) - s.askMu.Unlock() - return s.askErr -} - func (s *fakeSession) cancels() int { s.cancelMu.Lock() defer s.cancelMu.Unlock() return s.cancelCalls } -func (s *fakeSession) submissions() []permCall { - s.submitMu.Lock() - defer s.submitMu.Unlock() - out := make([]permCall, len(s.submitCalls)) - copy(out, s.submitCalls) - return out -} - // --------------------------------------------------------------------- // helpers // --------------------------------------------------------------------- @@ -151,7 +114,7 @@ func newHarness(t *testing.T) *harness { gotReq: make(chan proto.PromptRequestPayload, 16), gotSess: make(chan *fakeSession, 16), } - registerSession(h.reg, proto.SupportedAgentKind{Kind: "fake_alpha", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{Permissions: proto.CapabilitySupported})}, func(ctx context.Context, req proto.PromptRequestPayload, out chan<- proto.Envelope) (agent.Session, error) { + registerSession(h.reg, proto.SupportedAgentKind{Kind: "fake_alpha", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{})}, func(ctx context.Context, req proto.PromptRequestPayload, out chan<- proto.Envelope) (agent.Session, error) { sess := &fakeSession{out: out, ctx: ctx, closeOutOnCancel: true} h.gotReq <- req h.gotSess <- sess @@ -167,9 +130,9 @@ func newHarness(t *testing.T) *harness { // registerSession declares info, without an Environment, and starts each // Turn of the kind with factory. -func registerSession(reg *agent.Registry, info proto.SupportedAgentKind, factory agent.Factory) { +func registerSession(reg *agent.Registry, info proto.SupportedAgentKind, factory func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error)) { info.Capabilities.EnvironmentNone = proto.CapabilitySupported - reg.RegisterKind(info, prototest.ModelConfiguration(), factory) + reg.RegisterKind(info, prototest.ModelConfiguration()) reg.RegisterExecutor(info.Kind, preparationExecutorFixture(func(_ context.Context, req proto.PromptRequestPayload) (preparedFixture, error) { return &controlledPreparation{start: func(ctx context.Context, id string, input proto.MessageInput, out chan<- proto.Envelope) (agent.Session, error) { req.RunID, req.Input = id, input @@ -332,187 +295,6 @@ func TestHandlePromptCancelUnknownRunIsNoop(t *testing.T) { } } -func TestPermissionRequestIsIndexedAndDecisionRoutes(t *testing.T) { - h := newHarness(t) - defer h.router.Shutdown(context.Background()) - - startRun(t, h.router, h.sender, "fake_alpha", "run_p") - <-h.gotReq - sess := <-h.gotSess - - // Session emits a permission_request; the Router indexes it before it forwards it. - sess.out <- mustEnv(t, proto.TypePermissionRequest, "run_p", proto.PermissionRequestPayload{ - RequestID: "perm_abcd1234", Tool: "Bash", Title: "rm -rf /", - }) - waitFor(t, func() bool { return hasFrame(h.sender, proto.TypePermissionRequest, "run_p") }, "permission_request to be forwarded") - - dec := scoped(t, "run_p", proto.TypePermissionDecision, "perm_abcd1234", proto.PermissionDecisionPayload{DeliveryID: "delivery-perm-1", Approved: true}) - if err := h.router.Handle(context.Background(), dec); err != nil { - t.Fatalf("permission_decision: %v", err) - } - calls := sess.submissions() - if len(calls) != 1 || calls[0].id != "perm_abcd1234" || !calls[0].decision.Approved { - t.Errorf("submissions = %+v, want one approved perm_abcd1234", calls) - } - assertDecisionAck(t, h.sender, "delivery-perm-1", true, "") - retry := scoped(t, "run_p", proto.TypePermissionDecision, "perm_abcd1234", proto.PermissionDecisionPayload{DeliveryID: "delivery-perm-2", Approved: true}) - if err := h.router.Handle(context.Background(), retry); err != nil { - t.Fatalf("idempotent permission replay: %v", err) - } - if calls := sess.submissions(); len(calls) != 1 { - t.Fatalf("idempotent replay reached agent twice: %+v", calls) - } - assertDecisionAck(t, h.sender, "delivery-perm-2", true, "") - conflict := scoped(t, "run_p", proto.TypePermissionDecision, "perm_abcd1234", proto.PermissionDecisionPayload{DeliveryID: "delivery-perm-3", Approved: false}) - if err := h.router.Handle(context.Background(), conflict); err != nil { - t.Fatalf("conflicting permission replay: %v", err) - } - assertDecisionAck(t, h.sender, "delivery-perm-3", false, "decision_conflict") -} - -func TestPermissionDecisionUnknownPermIsNoop(t *testing.T) { - h := newHarness(t) - defer h.router.Shutdown(context.Background()) - - if err := h.router.Handle(context.Background(), mustEnv(t, proto.TypePermissionDecision, "perm_unknown", proto.PermissionDecisionPayload{DeliveryID: "delivery-unknown-perm"})); err != nil { - t.Errorf("decision for unknown perm = %v, want nil", err) - } - assertDecisionAck(t, h.sender, "delivery-unknown-perm", false, "not_pending") -} - -func TestPromptForUserChoiceDecisionRoutesToSession(t *testing.T) { - h := newHarness(t) - defer h.router.Shutdown(context.Background()) - - startRun(t, h.router, h.sender, "fake_alpha", "run_ask") - <-h.gotReq - sess := <-h.gotSess - - // Envelope.ID is the run id (server-side dispatch fans on it); the - // ask id rides on the payload. Daemon's indexPermissionFrame reads - // payload.AskID to seed askIndex. - sess.out <- mustEnv(t, proto.TypePromptForUserChoice, "run_ask", proto.PromptForUserChoicePayload{ - AskID: "ask_abcd1234", - Questions: []proto.PromptForUserChoiceQuestion{{Question: "?", Options: []proto.PromptForUserChoiceOption{{Label: "yes"}, {Label: "no"}}}}, - ToolUseID: "toolu_42", - }) - waitFor(t, func() bool { return hasFrame(h.sender, proto.TypePromptForUserChoice, "run_ask") }, "prompt_for_user_choice forwarded") - - dec := scoped(t, "run_ask", proto.TypePromptForUserChoiceDecision, "ask_abcd1234", proto.PromptForUserChoiceDecisionPayload{ - DeliveryID: "delivery-ask-1", QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "q0", Answers: []string{"yes"}}}, - }) - if err := h.router.Handle(context.Background(), dec); err != nil { - t.Fatalf("prompt_for_user_choice_decision: %v", err) - } - - sess.askMu.Lock() - calls := append([]askCall(nil), sess.askCalls...) - sess.askMu.Unlock() - if len(calls) != 1 || calls[0].id != "ask_abcd1234" { - t.Fatalf("askCalls = %+v, want one ask_abcd1234", calls) - } - if len(calls[0].decision.QuestionAnswers[0].Answers) != 1 || calls[0].decision.QuestionAnswers[0].Answers[0] != "yes" { - t.Errorf("answer payload mismatch: %+v", calls[0].decision) - } - assertDecisionAck(t, h.sender, "delivery-ask-1", true, "") - - // Cleanup contract: a successful decision drops the ask from both - // the router-level index and the session's pendingAsks set, so a - // stale retry short-circuits as "run gone". - if got := h.router.AskIndexLenForTest(); got != 0 { - t.Errorf("askIndex len = %d, want 0 after decision", got) - } - if got := h.router.PendingAsksLenForTest("run_ask"); got != 0 { - t.Errorf("pendingAsks len = %d, want 0 after decision", got) - } -} - -// TestPromptForUserChoiceDecisionClearsIndexOnAgentUnknown locks in the -// cleanup branch: when the session returns ErrUnknownAsk (timer already -// consumed the entry), the router still drops the index so a retry doesn't -// loop into Submit again. -func TestPromptForUserChoiceDecisionClearsIndexOnAgentUnknown(t *testing.T) { - h := newHarness(t) - defer h.router.Shutdown(context.Background()) - - startRun(t, h.router, h.sender, "fake_alpha", "run_ask_u") - <-h.gotReq - sess := <-h.gotSess - sess.askErr = agent.ErrUnknownAsk - - sess.out <- mustEnv(t, proto.TypePromptForUserChoice, "run_ask_u", proto.PromptForUserChoicePayload{ - AskID: "ask_xxxxxxxx", - Questions: []proto.PromptForUserChoiceQuestion{{Question: "?", Options: []proto.PromptForUserChoiceOption{{Label: "yes"}}}}, - ToolUseID: "toolu_y", - }) - waitFor(t, func() bool { return hasFrame(h.sender, proto.TypePromptForUserChoice, "run_ask_u") }, "prompt_for_user_choice forwarded") - - dec := scoped(t, "run_ask_u", proto.TypePromptForUserChoiceDecision, "ask_xxxxxxxx", proto.PromptForUserChoiceDecisionPayload{ - DeliveryID: "delivery-ask-gone", QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "q0", Answers: []string{"yes"}}}, - }) - if err := h.router.Handle(context.Background(), dec); err != nil { - t.Fatalf("prompt_for_user_choice_decision: %v", err) - } - - if got := h.router.AskIndexLenForTest(); got != 0 { - t.Errorf("askIndex len = %d, want 0 after ErrUnknownAsk", got) - } - if got := h.router.PendingAsksLenForTest("run_ask_u"); got != 0 { - t.Errorf("pendingAsks len = %d, want 0 after ErrUnknownAsk", got) - } - assertDecisionAck(t, h.sender, "delivery-ask-gone", false, "not_pending") -} - -func TestPromptForUserChoiceDecisionKeepsIndexOnTransientAgentError(t *testing.T) { - h := newHarness(t) - defer h.router.Shutdown(context.Background()) - - startRun(t, h.router, h.sender, "fake_alpha", "run_ask_retry") - <-h.gotReq - sess := <-h.gotSess - sess.askErr = errors.New("temporary stdin failure") - - sess.out <- mustEnv(t, proto.TypePromptForUserChoice, "run_ask_retry", proto.PromptForUserChoicePayload{ - AskID: "ask_retry", Questions: []proto.PromptForUserChoiceQuestion{{ID: "q0", Question: "Retry?"}}, - }) - waitFor(t, func() bool { return hasFrame(h.sender, proto.TypePromptForUserChoice, "run_ask_retry") }, "prompt_for_user_choice forwarded") - - decision := scoped(t, "run_ask_retry", proto.TypePromptForUserChoiceDecision, "ask_retry", proto.PromptForUserChoiceDecisionPayload{ - DeliveryID: "delivery-ask-retry", QuestionAnswers: []proto.PromptForUserChoiceQuestionAnswer{{QuestionID: "q0", Answers: []string{"yes"}}}, - }) - if err := h.router.Handle(context.Background(), decision); err != nil { - t.Fatalf("first decision: %v", err) - } - assertDecisionAck(t, h.sender, "delivery-ask-retry", false, "runtime_error") - if got := h.router.AskIndexLenForTest(); got != 1 { - t.Fatalf("askIndex len = %d, want 1 after transient error", got) - } - if got := h.router.PendingAsksLenForTest("run_ask_retry"); got != 1 { - t.Fatalf("pendingAsks len = %d, want 1 after transient error", got) - } - - sess.askMu.Lock() - sess.askErr = nil - sess.askMu.Unlock() - if err := h.router.Handle(context.Background(), decision); err != nil { - t.Fatalf("retry decision: %v", err) - } - assertDecisionAck(t, h.sender, "delivery-ask-retry", true, "") - if got := h.router.AskIndexLenForTest(); got != 0 { - t.Fatalf("askIndex len = %d, want 0 after successful retry", got) - } -} - -func TestPromptForUserChoiceDecisionUnknownAskIsNoop(t *testing.T) { - h := newHarness(t) - defer h.router.Shutdown(context.Background()) - - if err := h.router.Handle(context.Background(), mustEnv(t, proto.TypePromptForUserChoiceDecision, "ask_unknown", proto.PromptForUserChoiceDecisionPayload{DeliveryID: "delivery-unknown-ask"})); err != nil { - t.Errorf("decision for unknown ask = %v, want nil", err) - } - assertDecisionAck(t, h.sender, "delivery-unknown-ask", false, "not_pending") -} - func assertDecisionAck(t *testing.T, sender *recSender, deliveryID string, applied bool, errorCode string) { t.Helper() frames := sender.snapshot() diff --git a/apps/daemon/internal/dispatch/runtime_preparation_test.go b/apps/daemon/internal/dispatch/runtime_preparation_test.go index 6dde480cd..ca4497430 100644 --- a/apps/daemon/internal/dispatch/runtime_preparation_test.go +++ b/apps/daemon/internal/dispatch/runtime_preparation_test.go @@ -36,7 +36,7 @@ var capabilityRef = proto.AssignmentRef{SessionID: "0b6f1f3e-6f0a-4d38-9c1e-2f5d func capabilitiesTestRouter(t *testing.T) (*Router, *capabilitiesTestSender, string, string) { t.Helper() environment, session := uuid.NewString(), capabilityRef.SessionID - binding, err := localworkspace.New(environment, session, t.TempDir()) + binding, err := localworkspace.NewWithCapabilityDirectory(environment, session, t.TempDir(), t.TempDir()) if err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/dispatch/shutdown.go b/apps/daemon/internal/dispatch/shutdown.go index ef27133b4..b09de4c70 100644 --- a/apps/daemon/internal/dispatch/shutdown.go +++ b/apps/daemon/internal/dispatch/shutdown.go @@ -37,7 +37,7 @@ func (r *Router) Shutdown(ctx context.Context) error { // Prepared release claims exist before this signal can interrupt output. close(r.shutdownCh) } - preparations := r.closePendingPreparationsLocked() + r.closePendingPreparationsLocked() executors := r.closeIdleExecutorsLocked() attempt := &shutdownAttempt{done: make(chan struct{})} r.shutdownAttempt = attempt @@ -46,9 +46,6 @@ func (r *Router) Shutdown(ctx context.Context) error { r.mu.Unlock() r.closeIdleExecutors(executors) - for _, p := range preparations { - go func() { defer r.shutdownWG.Done(); r.closePreparationResource(p) }() - } go r.runShutdownAttempt(attempt, victims) return waitShutdown(ctx, attempt) } @@ -116,7 +113,6 @@ func waitShutdown(ctx context.Context, attempt *shutdownAttempt) error { type sessionCancellation struct { runID string - handoff *preparedHandoff release *preparedRelease attempt *preparedReleaseAttempt } @@ -127,7 +123,7 @@ func (r *Router) sessionCancellationsLocked() []sessionCancellation { victims := make([]sessionCancellation, 0, len(r.sessions)) for _, state := range r.sessions { release, attempt := r.claimPreparedReleaseLocked(state, true, "", true) - victims = append(victims, sessionCancellation{runID: state.runID, handoff: state.preparedHandoff, release: release, attempt: attempt}) + victims = append(victims, sessionCancellation{runID: state.runID, release: release, attempt: attempt}) } return victims } diff --git a/apps/daemon/internal/dispatch/suspend.go b/apps/daemon/internal/dispatch/suspend.go index 9220b7c6a..fccb8d4ee 100644 --- a/apps/daemon/internal/dispatch/suspend.go +++ b/apps/daemon/internal/dispatch/suspend.go @@ -42,7 +42,7 @@ func (r *Router) Quiesce(ctx context.Context, ref proto.AssignmentRef, request p r.mu.Unlock() return AssignmentError(code) } - if r.runtimePreparation != nil || len(r.sessions) != 0 || len(r.workspaceReads) != 0 || r.workspaceWrite != nil || r.workspaceExport != nil || len(r.permIndex) != 0 || len(r.askIndex) != 0 { + if r.runtimePreparation != nil || len(r.sessions) != 0 || len(r.workspaceReads) != 0 || r.workspaceWrite != nil || r.workspaceExport != nil { r.mu.Unlock() return ErrRouterBusy } diff --git a/apps/daemon/internal/dispatch/suspend_test.go b/apps/daemon/internal/dispatch/suspend_test.go index b71577e7a..db35925ff 100644 --- a/apps/daemon/internal/dispatch/suspend_test.go +++ b/apps/daemon/internal/dispatch/suspend_test.go @@ -49,14 +49,12 @@ func suspensionRouter(t *testing.T, sender Sender) *Router { func TestQuiesceRejectsEveryUnsettledResource(t *testing.T) { cases := map[string]func(*Router){ - "active": func(r *Router) { r.sessions["run"] = &sessionState{} }, - "preparing": func(r *Router) { r.preparations["p"] = &preparationState{owns: true} }, - "receipt": func(r *Router) { r.preparations["p"] = &preparationState{busy: true} }, - "read": func(r *Router) { r.workspaceReads = map[string]struct{}{"read": {}} }, - "write": func(r *Router) { r.workspaceWrite = &workspaceUpload{} }, - "export": func(r *Router) { r.workspaceExport = &workspaceExport{} }, - "permission": func(r *Router) { r.permIndex["permission"] = "run" }, - "choice": func(r *Router) { r.askIndex["choice"] = "run" }, + "active": func(r *Router) { r.sessions["run"] = &sessionState{} }, + "preparing": func(r *Router) { r.preparations["p"] = &preparationState{owns: true} }, + "receipt": func(r *Router) { r.preparations["p"] = &preparationState{busy: true} }, + "read": func(r *Router) { r.workspaceReads = map[string]struct{}{"read": {}} }, + "write": func(r *Router) { r.workspaceWrite = &workspaceUpload{} }, + "export": func(r *Router) { r.workspaceExport = &workspaceExport{} }, } for name, setup := range cases { t.Run(name, func(t *testing.T) { diff --git a/apps/daemon/internal/dispatch/workspace_export.go b/apps/daemon/internal/dispatch/workspace_export.go index ca9ebc2c1..724b84841 100644 --- a/apps/daemon/internal/dispatch/workspace_export.go +++ b/apps/daemon/internal/dispatch/workspace_export.go @@ -49,7 +49,7 @@ func (r *Router) handleWorkspaceExport(ctx context.Context, env proto.Envelope) } _, code := r.workspaceResourceLocked(env.Assignment, proto.WorkspaceReadPayload{Handle: request.Handle, EnvironmentID: request.EnvironmentID}) p := r.preparations[request.Handle] - if code == "" && (u != nil || r.workspaceWrite != nil || !r.localWorkspace.CanExport() || p == nil || !p.workspaceReadOnly) { + if code == "" && (u != nil || r.workspaceWrite != nil || !r.localWorkspace.CanExport() || p == nil || p.executor != nil) { code = "resource_unavailable" } if code != "" { diff --git a/apps/daemon/internal/dispatch/workspace_export_test.go b/apps/daemon/internal/dispatch/workspace_export_test.go index 7a1e2e5b7..cb142173f 100644 --- a/apps/daemon/internal/dispatch/workspace_export_test.go +++ b/apps/daemon/internal/dispatch/workspace_export_test.go @@ -51,7 +51,7 @@ func exporterRouter(t *testing.T, program string) (*Router, exportSender, proto. f.Close() } environment, session := uuid.NewString(), capabilityRef.SessionID - binding, err := localworkspace.New(environment, session, workspace) + binding, err := localworkspace.NewWithCapabilityDirectory(environment, session, workspace, t.TempDir()) if err != nil { t.Fatal(err) } @@ -62,7 +62,7 @@ func exporterRouter(t *testing.T, program string) (*Router, exportSender, proto. } bindAssignment(r, capabilityRef, environment) handle := uuid.NewString() - r.preparations[handle] = &preparationState{request: proto.Envelope{Assignment: capabilityRef}, workspaceReadOnly: true, environmentID: environment, owns: true, ctx: context.Background(), deadline: time.Now().Add(time.Hour), status: proto.PreparationStatusPayload{State: "ready"}} + r.preparations[handle] = &preparationState{request: proto.Envelope{Assignment: capabilityRef}, environmentID: environment, owns: true, ctx: context.Background(), deadline: time.Now().Add(time.Hour), status: proto.PreparationStatusPayload{State: "ready"}} t.Cleanup(func() { r.mu.Lock() delete(r.preparations, handle) diff --git a/apps/daemon/internal/dispatch/workspace_preparation_status_test.go b/apps/daemon/internal/dispatch/workspace_preparation_status_test.go index 3e0bbcc25..d748b5bbe 100644 --- a/apps/daemon/internal/dispatch/workspace_preparation_status_test.go +++ b/apps/daemon/internal/dispatch/workspace_preparation_status_test.go @@ -2,14 +2,10 @@ package dispatch import ( "context" - "errors" - "sync" - "sync/atomic" "testing" "time" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - obslog "github.com/MiniMax-AI/OpenAgentCore/internal/obs/log" ) type workspaceStatusSender chan proto.Envelope @@ -19,115 +15,23 @@ func (s workspaceStatusSender) Send(_ context.Context, envelope proto.Envelope) return nil } -type offlineWorkspaceStatusSender struct{} - -func (offlineWorkspaceStatusSender) Send(context.Context, proto.Envelope) error { - return errors.New("observer disconnected") -} - -type unsettledWorkspacePreparation struct { - calls atomic.Int32 - settled atomic.Bool -} - -func (p *unsettledWorkspacePreparation) Close() error { - // Bound a regression so a recursive retry cannot hang the test. - if p.calls.Add(1) >= 100 || p.settled.Load() { - return nil - } - return errors.New("cleanup incomplete") -} - -func TestReadPreparationOfflineStatusDoesNotRetryCleanup(t *testing.T) { - prepared := &unsettledWorkspacePreparation{} +func TestReadPreparationRetryCannotPublishStaleStatus(t *testing.T) { + sender := make(workspaceStatusSender, 4) ctx, cancel := context.WithCancel(context.Background()) defer cancel() timer := time.NewTimer(time.Hour) defer timer.Stop() - r := &Router{sender: offlineWorkspaceStatusSender{}, shutdownCh: make(chan struct{}), log: obslog.Bg()} - p := &preparationState{workspaceReadOnly: true, owns: true, prepared: prepared, - ctx: ctx, cancel: cancel, timer: timer, - status: proto.PreparationStatusPayload{Handle: "reader", Revision: 1, State: "ready"}} - for attempt := int32(1); attempt <= 2; attempt++ { - r.releasePreparation(p, "released", "", true, true) - r.shutdownWG.Wait() - if got := prepared.calls.Load(); got != attempt { - t.Fatalf("explicit release %d caused %d cleanup attempts", attempt, got) - } - if !p.owns || p.busy || p.prepared != prepared || p.status.ErrorCode != "cleanup_unconfirmed" { - t.Fatal("unconfirmed cleanup lost its resource ownership") - } - } - prepared.settled.Store(true) - r.releasePreparation(p, "released", "", true, true) - r.shutdownWG.Wait() - if prepared.calls.Load() != 3 || p.owns || p.prepared != nil || p.status.State != "released" { - t.Fatal("explicit retry did not settle resource ownership") - } -} - -func TestReadPreparationRetryCannotPublishStaleRelease(t *testing.T) { - sender := make(workspaceStatusSender, 4) r := &Router{sender: sender, shutdownCh: make(chan struct{})} - p := &preparationState{workspaceReadOnly: true, owns: true, busy: true, - status: proto.PreparationStatusPayload{Handle: "reader", Revision: 2, State: "released"}} - // A prepare retry captures this snapshot while Close is still running. + p := &preparationState{owns: true, ctx: ctx, cancel: cancel, timer: timer, + status: proto.PreparationStatusPayload{Handle: "reader", Revision: 2, State: "ready"}} + // A prepare retry captures this snapshot before the release settles. snapshot := p.status - // Close fails before the retry reaches publication. - p.busy = false - p.status = proto.PreparationStatusPayload{Handle: "reader", Revision: 3, State: "failed", ErrorCode: "cleanup_unconfirmed"} - r.publishPreparation(p, snapshot) - r.shutdownWG.Wait() - if len(sender) != 0 { - t.Fatal("stale release success escaped after failed Close") - } - r.publishPreparation(p, p.status) + r.releasePreparation(p, "released", "", true) r.shutdownWG.Wait() - var failed proto.PreparationStatusPayload - if len(sender) != 1 || (<-sender).DecodePayload(&failed) != nil || failed.State != "failed" { - t.Fatal("confirmed cleanup failure was suppressed") - } -} - -// Local read-only preparation uses no native factory. Keep the shared close -// settlement regression at its owner boundary instead of a retired remote fixture. -type blockingWorkspacePreparation struct { - entered, release chan struct{} -} - -func (p *blockingWorkspacePreparation) Close() error { - close(p.entered) - <-p.release - return nil -} - -func TestReadPreparationReleaseWaitsForClose(t *testing.T) { - sender := make(workspaceStatusSender, 4) - prepared := &blockingWorkspacePreparation{entered: make(chan struct{}), release: make(chan struct{})} - var release sync.Once - defer release.Do(func() { close(prepared.release) }) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - timer := time.NewTimer(time.Hour) - defer timer.Stop() - r := &Router{sender: sender, shutdownCh: make(chan struct{}), log: obslog.Bg()} - p := &preparationState{workspaceReadOnly: true, owns: true, prepared: prepared, - ctx: ctx, cancel: cancel, timer: timer, - status: proto.PreparationStatusPayload{Handle: "reader", Revision: 1, State: "ready"}} - r.releasePreparation(p, "released", "", true, true) - select { - case <-prepared.entered: - case <-time.After(time.Second): - t.Fatal("close did not start") - } - r.publishPreparation(p, p.status) - if len(sender) != 0 || !p.owns { - t.Fatal("release acknowledged before close settled") - } - release.Do(func() { close(prepared.release) }) + r.publishPreparation(p, snapshot) r.shutdownWG.Wait() - var status proto.PreparationStatusPayload - if p.owns || len(sender) != 1 || (<-sender).DecodePayload(&status) != nil || status.State != "released" { - t.Fatal("settled close did not release ownership and publish status") + var released proto.PreparationStatusPayload + if p.owns || len(sender) != 1 || (<-sender).DecodePayload(&released) != nil || released.State != "released" { + t.Fatal("stale ready snapshot escaped or settled release was not published") } } diff --git a/apps/daemon/internal/dispatch/workspace_read.go b/apps/daemon/internal/dispatch/workspace_read.go index 4ef393ae4..d2ef331c7 100644 --- a/apps/daemon/internal/dispatch/workspace_read.go +++ b/apps/daemon/internal/dispatch/workspace_read.go @@ -79,7 +79,7 @@ func (r *Router) workspaceResourceLocked(ref proto.AssignmentRef, request proto. } s := r.sessions[request.RunID] if s == nil || s.assignment != ref || s.environmentID != request.EnvironmentID || s.session == nil || - !r.interactionRouteOpenLocked(s) { + !r.runRouteOpenLocked(s) { return nil, "resource_unavailable" } if r.localWorkspace != nil { diff --git a/apps/daemon/internal/dispatch/workspace_write_test.go b/apps/daemon/internal/dispatch/workspace_write_test.go index 85e74bbd0..7ee243ed0 100644 --- a/apps/daemon/internal/dispatch/workspace_write_test.go +++ b/apps/daemon/internal/dispatch/workspace_write_test.go @@ -21,7 +21,7 @@ func localWriterRouter(t *testing.T) (*dispatch.Router, *recSender, proto.Worksp t.Helper() workspace := t.TempDir() environment, session := uuid.NewString(), preparationSessionID - binding, err := localworkspace.New(environment, session, workspace) + binding, err := localworkspace.NewWithCapabilityDirectory(environment, session, workspace, t.TempDir()) if err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/localworkspace/binding.go b/apps/daemon/internal/localworkspace/binding.go index 5ee7fbf31..0ead8710a 100644 --- a/apps/daemon/internal/localworkspace/binding.go +++ b/apps/daemon/internal/localworkspace/binding.go @@ -3,11 +3,9 @@ package localworkspace import ( "errors" "os" - "path/filepath" "strings" "sync" - "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/paths" "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" @@ -26,14 +24,6 @@ type Binding struct { capabilityRoot string } -func New(environment, session, workspace string) (*Binding, error) { - root, err := paths.Root() - if err != nil { - return nil, err - } - return newNativeBinding(environment, session, workspace, filepath.Join(root, "capabilities")) -} - // NewWithCapabilityDirectory freezes paths selected by the Runtime operator. func NewWithCapabilityDirectory(environment, session, workspace, directory string) (*Binding, error) { return newNativeBinding(environment, session, workspace, directory) diff --git a/apps/daemon/internal/localworkspace/binding_test.go b/apps/daemon/internal/localworkspace/binding_test.go index 6d5bc80a5..b23ea8c0f 100644 --- a/apps/daemon/internal/localworkspace/binding_test.go +++ b/apps/daemon/internal/localworkspace/binding_test.go @@ -20,12 +20,11 @@ func testBinding(t *testing.T) (*Binding, proto.PromptRequestPayload) { t.Setenv("OAC_RUNTIME_HOME", private) root := t.TempDir() environment, session := uuid.NewString(), uuid.NewString() - b, err := New(environment, session, root) + b, err := NewWithCapabilityDirectory(environment, session, root, t.TempDir()) if err != nil { t.Fatal(err) } b.networkAccess = "disabled" - b.capabilityRoot = t.TempDir() return b, proto.PromptRequestPayload{LocalEnvironment: &proto.LocalEnvironment{ID: environment, NetworkAccess: "disabled", WorkspaceDirectory: "/workspace", CapabilitySources: &agentcapabilities.Input{}}, AgentStateKey: "agents-api-" + session} } diff --git a/apps/daemon/internal/localworkspace/capability_preparation_test.go b/apps/daemon/internal/localworkspace/capability_preparation_test.go index 0d226d55e..dc061b085 100644 --- a/apps/daemon/internal/localworkspace/capability_preparation_test.go +++ b/apps/daemon/internal/localworkspace/capability_preparation_test.go @@ -40,12 +40,11 @@ func TestPreparationFreezesLocalContentsAcrossReconnect(t *testing.T) { t.Fatalf("first preparation: %v", err) } writeSourceSkill(t, source, "second") - reconnect, err := New(b.environment, b.capabilityIdentity().SessionID, b.workspace) + reconnect, err := NewWithCapabilityDirectory(b.environment, b.capabilityIdentity().SessionID, b.workspace, b.capabilityRoot) if err != nil { t.Fatal(err) } reconnect.networkAccess = b.networkAccess - reconnect.capabilityRoot = b.capabilityRoot again, err := reconnect.Prepare(t.Context(), configured) if err != nil || len(again.LocalEnvironment.Skills) != 1 { t.Fatalf("reconnection: %v", err) @@ -206,11 +205,11 @@ func TestPreparationFreezesToolOnlyEnvironmentAcrossReconnect(t *testing.T) { if err := os.WriteFile(source, []byte(`{"LOCAL_ONLY":"changed"}`), 0600); err != nil { t.Fatal(err) } - reconnect, err := New(b.environment, b.capabilityIdentity().SessionID, b.workspace) + reconnect, err := NewWithCapabilityDirectory(b.environment, b.capabilityIdentity().SessionID, b.workspace, b.capabilityRoot) if err != nil { t.Fatal(err) } - reconnect.networkAccess, reconnect.capabilityRoot = b.networkAccess, b.capabilityRoot + reconnect.networkAccess = b.networkAccess if _, err := reconnect.Prepare(t.Context(), configured); err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/paths/paths.go b/apps/daemon/internal/paths/paths.go index 6c835ae3d..accdd85c6 100644 --- a/apps/daemon/internal/paths/paths.go +++ b/apps/daemon/internal/paths/paths.go @@ -88,12 +88,3 @@ func LogFile(profile string) (string, error) { } return filepath.Join(dir, "connect.log"), nil } - -// SessionsFile returns the absolute path to sessions.json. -func SessionsFile(profile string) (string, error) { - dir, err := ProfileDir(profile) - if err != nil { - return "", err - } - return filepath.Join(dir, "sessions.json"), nil -} diff --git a/apps/daemon/internal/paths/paths_test.go b/apps/daemon/internal/paths/paths_test.go index 7f4d8fa9e..cd6fc605f 100644 --- a/apps/daemon/internal/paths/paths_test.go +++ b/apps/daemon/internal/paths/paths_test.go @@ -75,10 +75,9 @@ func TestProfileDirAndFiles(t *testing.T) { } cases := map[string]func(string) (string, error){ - "auth.json": paths.AuthFile, - "connect.pid": paths.PIDFile, - "connect.log": paths.LogFile, - "sessions.json": paths.SessionsFile, + "auth.json": paths.AuthFile, + "connect.pid": paths.PIDFile, + "connect.log": paths.LogFile, } for filename, fn := range cases { got, err := fn("test") diff --git a/apps/daemon/internal/wireconformance/wire_test.go b/apps/daemon/internal/wireconformance/wire_test.go index 22667ed26..6e9bb9394 100644 --- a/apps/daemon/internal/wireconformance/wire_test.go +++ b/apps/daemon/internal/wireconformance/wire_test.go @@ -156,10 +156,7 @@ func connectRuntime(t *testing.T, peer *corePeer, setupErr error) *runtimeSide { peer.accept(t) rt := &runtimeSide{conn: conn, executor: &controlledExecutor{turn: make(chan *controlledTurn, 1)}, stopped: make(chan struct{})} kinds := agent.NewRegistry() - kinds.RegisterKind(proto.SupportedAgentKind{Kind: prototest.HarnessKind, Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{EnvironmentNone: proto.CapabilitySupported})}, - prototest.ModelConfiguration(), func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, errors.New("prepared execution must not use the direct Session factory") - }) + kinds.RegisterKind(proto.SupportedAgentKind{Kind: prototest.HarnessKind, Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{EnvironmentNone: proto.CapabilitySupported})}, prototest.ModelConfiguration()) kinds.RegisterExecutor(prototest.HarnessKind, func(context.Context, proto.PromptRequestPayload) (agent.Executor, error) { return rt.executor, setupErr }) diff --git a/apps/daemon/testdata/onboarding/main.go b/apps/daemon/testdata/onboarding/main.go index 5dbb2bdc3..783c5220e 100644 --- a/apps/daemon/testdata/onboarding/main.go +++ b/apps/daemon/testdata/onboarding/main.go @@ -164,9 +164,7 @@ func run() error { h := &harness{history: map[string]string{}} registry.RegisterKind(proto.SupportedAgentKind{Kind: "mcode", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{ Streaming: proto.CapabilitySupported, Steering: proto.CapabilitySupported, DurableTurns: proto.CapabilitySupported, DurableInputReceipts: proto.CapabilitySupported, ExecutionControls: proto.CapabilitySupported, ToolObservations: proto.CapabilitySupported, SubagentControl: proto.CapabilitySupported, EnvironmentNone: proto.CapabilitySupported, - })}, mcode.Configuration(), func(context.Context, proto.PromptRequestPayload, chan<- proto.Envelope) (agent.Session, error) { - return nil, errors.New("fixture execution requires an Executor") - }) + })}, mcode.Configuration()) registry.RegisterExecutor("mcode", h.prepare) sink := &sender{encoder: json.NewEncoder(os.Stdout)} router, err := dispatch.New(dispatch.Config{Registry: registry, Sender: sink, Log: slog.New(slog.NewTextHandler(io.Discard, nil))}) diff --git a/apps/web/DESIGN.md b/apps/web/DESIGN.md index 64bb19dd3..86c08ae95 100644 --- a/apps/web/DESIGN.md +++ b/apps/web/DESIGN.md @@ -402,7 +402,7 @@ A failed action whose outcome needs a decision (a sandbox change with no answer, A local-only installation has the same amber notice on Overview, Nodes and System: other machines cannot connect, followed by Core's configuration path and apply command as copyable values. If Core has no configuration snapshot, state that those instructions are unavailable; never fill in a path or command. Add node is disabled with its reason beside the action, and Getting started leaves its first step to do with the address fix visible. A pending or failed installation read cannot complete that step; a failed read shows Unknown and Retry. ### Onboarding -Signing in and the console tour share one frame: a dark stage on the left (always dark, whatever the theme) and the task panel on the right, which follows the theme. The stage is the product's one authored moment: a flickering indigo dot grid under slow light rays (Magic UI's flickering grid and light rays), Core as the OpenAgentCore mark on a tile with a travelling border beam, and two orbits of Agents, Sessions, Skills, Vaults, files, templates and machines around it; the OpenAgentCore mark is itself nodes on a ring. Brand copy sits bottom-left in solid ink; it is a paragraph, not a heading, because the panel's title names the task. Signing in asks for one thing, the deployment's Core key, in a single password field; the default key location and a copyable read command stay visible beneath it, with a reminder to substitute a custom installation directory. The key’s authority stays in a help tip. A refused key, too many attempts or an unavailable console is an error beside the field. Signing in opens the console on the Overview. The optional tour has three chapters — Monitor, Resources, Platform — whose stage shows a real dark screenshot of those pages, tilted towards the panel; it takes the place of the console until its last button, Skip or Escape, and then returns the focus to the control that opened it. Entering the console or the tour, and leaving the tour, happen inside a View Transition: the old page dissolves forward and the new one is revealed in a circle growing from the pressed button. With reduced motion the orbits hold their places, the grid is a still frame and no transition runs. +Signing in and the console tour share one frame: a dark stage on the left (always dark, whatever the theme) and the task panel on the right, which follows the theme. The stage is the product's one authored moment: a flickering indigo dot grid under slow light rays (Magic UI's flickering grid and light rays), Core as the OpenAgentCore mark on a tile with a travelling border beam, and two orbits of Agents, Sessions, Skills, Vaults, files, templates and machines around it; the OpenAgentCore mark is itself nodes on a ring. Brand copy sits bottom-left in solid ink; it is a paragraph, not a heading, because the panel's title names the task. Signing in asks for one thing, the deployment's Core key, in a single password field; a copyable Docker Compose command to read the key stays visible beneath it, with a reminder to substitute a custom installation directory. The key’s authority stays in a help tip. A refused key, too many attempts or an unavailable console is an error beside the field. Signing in opens the console on the Overview. The optional tour has three chapters — Monitor, Resources, Platform — whose stage shows a real dark screenshot of those pages, tilted towards the panel; it takes the place of the console until its last button, Skip or Escape, and then returns the focus to the control that opened it. Entering the console or the tour, and leaving the tour, happen inside a View Transition: the old page dissolves forward and the new one is revealed in a circle growing from the pressed button. With reduced motion the orbits hold their places, the grid is a still frame and no transition runs. ### Getting started The first card on the Overview while any step is to do: a card header ("Getting started", "n of 4 done", a help tip, then a ghost Take the tour button and an icon button that hides it) over four rows split by Faint Rules. Each row has a 22px numbered ring (a check on the tile wash when done), a 13px/600 title over one 12.5px Graphite line, a status dot (Done in green, To do in Pencil, Checking pending, Unknown for a failed read) and one outline action while the step is to do: Set up sandboxes, Add node, Open Nodes or Open sandbox backend; Open System; Create project (which continues to the new project's first key) or Issue key; See how to call (the newest active project, preferring one with an active key), or Projects and keys without an active project. Add node, Create project and Issue key open their page with the dialog already open; Open System brings the Default model provider section to the top of the page body and focuses the default harness's Set or Replace; See how to call opens the project and, once its keys, usage and address are read, brings its How to call heading to the top of the page body, focused. Only the page body scrolls; the page header stays. Every step done turns it into one line, "You're set", with Take the tour and Dismiss; it stays, through the tour, until dismissed, and the checklist does not come back on its own. The choice is kept per installation in the browser, also while the deployment cannot be read; Show Getting started, a quiet row above the sidebar's account controls, opens it again at any time. diff --git a/apps/web/PRODUCT.md b/apps/web/PRODUCT.md index 2aa72c28c..090ca06fd 100644 --- a/apps/web/PRODUCT.md +++ b/apps/web/PRODUCT.md @@ -22,7 +22,7 @@ The console runs beside the administrator's own Core, with execution, files and ## Operating Context -- Paired console (`services/web`): the administrator signs in with the deployment's Core key, the administration credential the installer writes to `secrets/core.key` under the installation directory (by default `~/.oac/core/secrets/core.key`; keeping and rotating it is described in [Core key](../../docs/getting-started/operations.md#core-key)). There are no console accounts or usernames. Sign-in shows the default file location and a copyable `cat ~/.oac/core/secrets/core.key` command for the Core host, with a reminder to substitute a custom installation directory. The browser sends the key only to sign in and keeps only the session cookie; the console server holds the Core key and forwards the Web API (`/core/v1/**`, including sandbox administration under `/core/v1/sandbox/**`). The console never calls `/v1`. +- Paired console (`services/web`): the administrator signs in with the deployment's [Core key](../../docs/getting-started/operations.md#core-key). Sign-in shows a copyable Docker Compose command to read the key on the Core host, with a reminder to substitute a custom installation directory. The browser sends the key only to sign in and keeps only the session cookie; the console server holds the Core key and forwards the Web API (`/core/v1/**`, including sandbox administration under `/core/v1/sandbox/**`). The console never calls `/v1`. - The Core key is not an Agents API identity and cannot call `/v1`. An administrator who wants to call the Agents API issues a project API key like any other caller. - `/console/config` reports the node installer (`node_installer`, `node_installer_sha256`), offered only with a 64-hex digest. Native self-hosted installation does not depend on this endpoint. It also lists the providers it has node files for (`node_artifacts`); without the deployment's provider, Add node says so and issues no command. Signing in grants administration, sandbox administration included. - Chinese and English UI; light and dark themes; reduced motion honored. diff --git a/apps/web/e2e/access.spec.ts b/apps/web/e2e/access.spec.ts index 41149ef97..f8d48a28d 100644 --- a/apps/web/e2e/access.spec.ts +++ b/apps/web/e2e/access.spec.ts @@ -18,11 +18,11 @@ test("signs in with the Core key, keeps it out of the browser, and signs out and await page.goto("/"); await expect(page.getByRole("heading", { name: "Sign in to OpenAgentCore" })).toBeVisible(); - await expect(page.getByText("cat ~/.oac/core/secrets/core.key", { exact: true })).toBeVisible(); + await expect(page.getByText('docker compose -f "$HOME/.oac/core/compose.yaml" exec -T web oac-web core-key', { exact: true })).toBeVisible(); await expect(page.getByText("For a custom installation directory, replace the path in this command.")).toBeVisible(); await page.context().grantPermissions(["clipboard-read", "clipboard-write"]); await page.getByRole("button", { name: "Copy key read command" }).click(); - expect(await page.evaluate(() => navigator.clipboard.readText())).toBe("cat ~/.oac/core/secrets/core.key"); + expect(await page.evaluate(() => navigator.clipboard.readText())).toBe('docker compose -f "$HOME/.oac/core/compose.yaml" exec -T web oac-web core-key'); await signIn(page, "not-the-core-key"); await expect(page.getByRole("alert")).toHaveText("This Core key is not correct. Check it and try again."); await signIn(page, FIXTURE_CORE_KEY); diff --git a/apps/web/src/features/first-run/ConsoleAccess.tsx b/apps/web/src/features/first-run/ConsoleAccess.tsx index d9ef1f408..5e561f330 100644 --- a/apps/web/src/features/first-run/ConsoleAccess.tsx +++ b/apps/web/src/features/first-run/ConsoleAccess.tsx @@ -12,13 +12,6 @@ import { withTransition } from "../onboarding/view-transition"; import { changeConsoleAuth, ConsoleAuthError, readConsoleAuth, type ConsoleAuth } from "./auth"; import "./console-access.css"; -/** - * Where the installer writes the Core key: its file inside the installation - * directory, and that file under the default installation directory. The - * visible sign-in instructions name both; the actual custom path is not public. - */ -const CORE_KEY_LOCATION = { file: "secrets/core.key", defaultPath: "~/.oac/core/secrets/core.key" } as const; - const ConsoleAccountContext = createContext<{ logout: () => Promise } | null>(null); export const useConsoleAccount = () => useContext(ConsoleAccountContext); @@ -146,8 +139,8 @@ function CoreKeyForm({ onAuthenticated }: { readOnly={busy} aria-invalid={error ? true : undefined} aria-describedby={`${id}-location${error ? ` ${id}-error` : ""}`} />
-

{t("The installer saved the key in {{file}} inside the installation directory. On the Core host, read the default location with:", CORE_KEY_LOCATION)}

- +

{t("On the Core host, run this command to read the Core key:")}

+

{t("For a custom installation directory, replace the path in this command.")}

{error ? : null} diff --git a/apps/web/src/i18n/locales/en/core-errors.ts b/apps/web/src/i18n/locales/en/core-errors.ts index 3c78ff8a6..802d3768f 100644 --- a/apps/web/src/i18n/locales/en/core-errors.ts +++ b/apps/web/src/i18n/locales/en/core-errors.ts @@ -1,5 +1,5 @@ export const coreErrors = { - "invalid_admin_key": "The console's Core key was rejected. Update secrets/core.key on the Core host and run oac apply.", + "invalid_admin_key": "The console's Core key was rejected. Rotate it on the Core host, then sign in again.", "console_sign_in_required": "Sign in to the console again.", "console_origin_rejected": "Open the console at its configured address.", "console_request_invalid": "The console request was rejected. Reload the page.", diff --git a/apps/web/src/i18n/locales/zh-CN/core-errors.ts b/apps/web/src/i18n/locales/zh-CN/core-errors.ts index 1d7fe0ade..fb30419dd 100644 --- a/apps/web/src/i18n/locales/zh-CN/core-errors.ts +++ b/apps/web/src/i18n/locales/zh-CN/core-errors.ts @@ -1,5 +1,5 @@ export const coreErrors = { - "invalid_admin_key": "控制台的 Core Key 被拒绝。请更新 Core 主机上的 secrets/core.key,再运行 oac apply。", + "invalid_admin_key": "控制台的 Core Key 被拒绝。请在 Core 主机上轮换密钥,然后重新登录。", "console_sign_in_required": "请重新登录控制台。", "console_origin_rejected": "请通过配置的地址打开控制台。", "console_request_invalid": "控制台请求被拒绝,请重新加载页面。", diff --git a/apps/web/src/lib/console-auth-strings.ts b/apps/web/src/lib/console-auth-strings.ts index c2c8afdf1..c6490daef 100644 --- a/apps/web/src/lib/console-auth-strings.ts +++ b/apps/web/src/lib/console-auth-strings.ts @@ -4,8 +4,8 @@ export const consoleAuthChinese = { "Core key": "Core Key", "The Core key is an administration credential: it cannot call the /v1 Agents API, and the console never keeps it in your browser.": "Core Key 是管理凭据:不能调用 /v1 Agents API,控制台也不会把它保存在你的浏览器里。", - "The installer saved the key in {{file}} inside the installation directory. On the Core host, read the default location with:": - "安装器将 key 保存在安装目录下的 {{file}} 中。在 Core 主机上运行以下命令可读取默认位置:", + "On the Core host, run this command to read the Core key:": + "在 Core 主机上运行以下命令,读取 Core Key:", "Copy key read command": "复制 key 读取命令", "For a custom installation directory, replace the path in this command.": "如果使用了自定义安装目录,请替换命令中的路径。", "Sign in": "登录", diff --git a/contracts/agents-api/core-errors.md b/contracts/agents-api/core-errors.md index b8eddd89d..00be4d1c5 100644 --- a/contracts/agents-api/core-errors.md +++ b/contracts/agents-api/core-errors.md @@ -96,7 +96,7 @@ The [Session and Turn diagnostics reads](./session-diagnostics.md) return these | `execution_interrupted` | Core execution interrupted | | `delivery_unconfirmed` | `delivery_unknown`, `input_outcome_unknown`, `cancel_unconfirmed`, `cancel_outcome_unavailable`, `function_result_unconfirmed` | | `input_rejected` | `invalid_input`, `input_not_applied`, `message_input_unsupported`, and the exact steering outcomes `input_invalid_input`, `input_run_inactive`, `input_input_conflict`, `input_input_limit`, `input_unsupported`, `input_rejected`, `input_not_ready`, `input_busy` | -| `executor_protocol_error` | `invalid_executor_result`, `interaction_not_supported`, `execution_state_unavailable`, `execution_state_changed`, `function_call_invalid`, `function_result_invalid` | +| `executor_protocol_error` | `invalid_executor_result`, `execution_state_unavailable`, `execution_state_changed`, `function_call_invalid`, `function_result_invalid` | | `core_storage_failed` | `event_persistence_failed`, `artifact_capture_failed` | | `internal_error` | Unknown or malformed outcome; no raw value is returned | | `environment_connection_timeout` | Initial input connection deadline expired | diff --git a/contracts/agents-api/core.openapi.yaml b/contracts/agents-api/core.openapi.yaml index 9a74d1381..c3412ffd4 100644 --- a/contracts/agents-api/core.openapi.yaml +++ b/contracts/agents-api/core.openapi.yaml @@ -1458,6 +1458,10 @@ definitions: service_tier: enum: - auto + - default + - flex + - priority + - fast type: string text: $ref: '#/definitions/v1.TextConfig' @@ -1466,13 +1470,13 @@ definitions: type: object type: array x_agents_core: - allOf: - - $ref: '#/definitions/v1.AgentsCore' - x-nullable: true + $ref: '#/definitions/v1.AgentsCore' required: - id + - instructions - model - multi_agent + - name - reasoning - service_tier - text @@ -1481,8 +1485,6 @@ definitions: v1.AgentDeleted: properties: deleted: - enum: - - true type: boolean id: type: string @@ -1546,8 +1548,8 @@ definitions: x-nullable: true type: enum: - - static_bearer - mcp_oauth + - static_bearer type: string required: - mcp_server_url @@ -1588,7 +1590,9 @@ definitions: type: string required: - data + - first_id - has_more + - last_id - object type: object v1.EnvironmentInstallation: @@ -1684,6 +1688,7 @@ definitions: - created_at - files - id + - name - network - object - packages @@ -1726,7 +1731,9 @@ definitions: type: string required: - data + - first_id - has_more + - last_id - object type: object v1.ExecutionHarnessConfigSelection: @@ -1794,7 +1801,9 @@ definitions: v1.Item: properties: action: - $ref: '#/definitions/v1.WebSearchAction' + allOf: + - $ref: '#/definitions/v1.WebSearchAction' + x-nullable: true agent_id: type: string arguments: {} @@ -1808,18 +1817,25 @@ definitions: type: array cwd: type: string + x-nullable: true duration_ms: type: integer - error: {} + x-nullable: true + error: + x-nullable: true exit_code: type: integer + x-nullable: true id: type: string + x-nullable: true model: type: string + x-nullable: true name: type: string - output: {} + output: + x-nullable: true phase: enum: - commentary @@ -1828,6 +1844,7 @@ definitions: x-nullable: true reasoning_effort: type: string + x-nullable: true recipient_agent_id: type: string recipient_agent_ids: @@ -1847,9 +1864,10 @@ definitions: enum: - in_progress - completed - - failed - incomplete + - failed type: string + x-nullable: true summary: items: $ref: '#/definitions/v1.SummaryText' @@ -1859,13 +1877,13 @@ definitions: type: enum: - message - - command_execution - - mcp_call + - reasoning - function_call - function_call_output - - web_search_call - - reasoning - agent_message + - mcp_call + - web_search_call + - command_execution - create_subagent_call - send_subagent_input_call - resume_subagent_call @@ -1889,8 +1907,8 @@ definitions: type: enum: - input_text - - output_text - input_image + - output_text - encrypted_content type: string required: @@ -1916,7 +1934,9 @@ definitions: type: string required: - data + - first_id - has_more + - last_id - object type: object v1.ModelConfigurationInput: @@ -1997,6 +2017,7 @@ definitions: x-nullable: true required: - enabled + - max_concurrent_subagents type: object v1.OAuthCredentialRefresh: properties: @@ -2014,6 +2035,8 @@ definitions: $ref: '#/definitions/v1.OAuthEndpointAuth' required: - client_id + - resource + - scope - token_endpoint - token_endpoint_auth type: object @@ -2038,9 +2061,21 @@ definitions: v1.Reasoning: properties: effort: + enum: + - none + - minimal + - low + - medium + - high + - xhigh + - max type: string x-nullable: true summary: + enum: + - concise + - detailed + - auto type: string x-nullable: true type: object @@ -2552,7 +2587,7 @@ definitions: - fast type: string text: - $ref: '#/definitions/v1.SavedAgentText' + $ref: '#/definitions/v1.TextConfig' tools: items: type: object @@ -2560,15 +2595,15 @@ definitions: updated_at: type: integer x_agents_core: - allOf: - - $ref: '#/definitions/v1.SavedAgentCore' - x-nullable: true + $ref: '#/definitions/v1.SavedAgentCore' required: - created_at - id + - instructions - metadata - model - multi_agent + - name - object - reasoning - service_tier @@ -2609,35 +2644,11 @@ definitions: type: string required: - data + - first_id - has_more + - last_id - object type: object - v1.SavedAgentText: - properties: - format: - $ref: '#/definitions/v1.SavedAgentTextFormat' - verbosity: - enum: - - low - - medium - - high - type: string - required: - - format - - verbosity - type: object - v1.SavedAgentTextFormat: - properties: - schema: - type: object - type: - enum: - - text - - json_schema - type: string - required: - - type - type: object v1.Session: properties: agent: @@ -2686,12 +2697,14 @@ definitions: - agent - created_at - environment + - error - id - last_active_at - metadata - object - required_actions - status + - usage - vault_ids type: object v1.SessionArtifact: @@ -2759,7 +2772,9 @@ definitions: type: string required: - data + - first_id - has_more + - last_id - object type: object v1.SessionCore: @@ -2811,8 +2826,8 @@ definitions: type: enum: - none - - self_hosted - openai_hosted + - self_hosted type: string workspace_directory: type: string @@ -2868,7 +2883,9 @@ definitions: type: string required: - data + - first_id - has_more + - last_id - object type: object v1.Skill: @@ -2933,7 +2950,9 @@ definitions: type: string required: - data + - first_id - has_more + - last_id - object type: object v1.SkillVersion: @@ -3001,19 +3020,19 @@ definitions: type: string required: - data + - first_id - has_more + - last_id - object type: object v1.SourceFile: properties: bytes: - minimum: 0 type: integer created_at: type: integer expires_at: type: integer - x-nullable: true filename: type: string id: @@ -3024,15 +3043,23 @@ definitions: type: string purpose: enum: + - assistants + - assistants_output + - batch + - batch_output + - fine-tune + - fine-tune-results + - vision - user_data type: string status: enum: + - uploaded - processed + - error type: string status_details: type: string - x-nullable: true required: - bytes - created_at @@ -3065,19 +3092,17 @@ definitions: type: array first_id: type: string - x-nullable: true has_more: type: boolean last_id: type: string - x-nullable: true object: - enum: - - list type: string required: - data + - first_id - has_more + - last_id - object type: object v1.SummaryText: @@ -3179,11 +3204,16 @@ definitions: x-nullable: true required: - agent_id + - completed_at - created_at + - error - id - object - session_id + - started_at - status + - subagent_id + - usage type: object v1.TurnError: properties: @@ -3232,7 +3262,9 @@ definitions: type: string required: - data + - first_id - has_more + - last_id - object type: object v1.Vault: @@ -3256,6 +3288,7 @@ definitions: - created_at - id - metadata + - name - object type: object v1.VaultDeleted: @@ -3293,19 +3326,24 @@ definitions: type: string required: - data + - first_id - has_more + - last_id - object type: object v1.WebSearchAction: properties: pattern: type: string + x-nullable: true queries: items: type: string type: array + x-nullable: true query: type: string + x-nullable: true type: enum: - search @@ -3315,6 +3353,7 @@ definitions: type: string url: type: string + x-nullable: true required: - type type: object diff --git a/contracts/agents-api/go-bindings.json b/contracts/agents-api/go-bindings.json new file mode 100644 index 000000000..5b5d9253e --- /dev/null +++ b/contracts/agents-api/go-bindings.json @@ -0,0 +1,536 @@ +{ + "APIError": { + "sources": ["#/components/schemas/Error"], + "order": ["message", "type", "code", "param"] + }, + "Agent": { + "sources": ["#/components/schemas/SessionAgentResource"], + "fields": { + "x_agents_core": {"type": "*AgentsCore"}, + "reasoning": {"type": "Reasoning"} + }, + "order": ["x_agents_core", "id", "instructions", "model", "multi_agent", "name", "reasoning", "service_tier", "text", "tools"] + }, + "AgentContent": { + "sources": ["#/components/schemas/AgentContentResource"] + }, + "AgentDeleted": { + "sources": ["#/components/schemas/DeletedAgentResource"] + }, + "AgentMessageItem": { + "sources": ["#/components/schemas/AgentMessageItemResource"] + }, + "CreateAgentRequest": { + "sources": ["#/components/schemas/CreateAgentParams"], + "fields": { + "x_agents_core": {"type": "*SavedAgentCoreInput"}, + "model": {"type": "*string"}, + "metadata": {"type": "map[string]*string"}, + "reasoning": {"type": "*Reasoning"}, + "text": {"type": "*TextConfigInput"} + }, + "order": ["x_agents_core", "model", "name", "instructions", "metadata", "multi_agent", "reasoning", "service_tier", "text", "tools"] + }, + "CreateSessionRequest": { + "sources": ["#/components/schemas/CreateAgentSessionParams"], + "fields": { + "x_agents_core": {"type": "*SessionExecutionInput"}, + "environment": {"type": "*Environment"}, + "input": {"type": "any"}, + "stream": {"type": "bool"} + }, + "order": ["x_agents_core", "agent", "agent_id", "environment", "input", "metadata", "stream", "vault_ids"] + }, + "CreateSubagentCallItem": { + "sources": ["#/components/schemas/CreateSubagentCallItemResource"], + "order": ["id", "turn_id", "type", "status", "agent_id", "content", "model", "reasoning_effort"] + }, + "Credential": { + "sources": ["#/components/schemas/VaultCredentialResource"], + "order": ["id", "vault_id", "name", "object", "auth", "created_at", "updated_at"] + }, + "CredentialAuth": { + "sources": ["#/components/schemas/VaultCredentialAuthResource"], + "fields": { + "expires_at": {"omit": false}, + "refresh": {"type": "*OAuthCredentialRefresh", "omit": false} + } + }, + "CredentialAuthInput": { + "sources": ["#/components/schemas/CreateVaultCredentialAuthParam"], + "fields": { + "mcp_server_url": {"type": "*string"}, + "refresh": {"type": "*OAuthCredentialRefreshInput"} + }, + "order": ["type", "mcp_server_url", "token", "access_token", "expires_at", "refresh"] + }, + "CredentialAuthReplacement": { + "sources": ["#/components/schemas/RotateVaultCredentialAuthParam"], + "fields": { + "expires_at": {"type": "json.RawMessage"}, + "refresh": {"type": "*OAuthCredentialRefreshReplacement"} + }, + "order": ["type", "token", "access_token", "expires_at", "refresh"] + }, + "CredentialDeleted": { + "sources": ["#/components/schemas/DeletedVaultCredentialResource"], + "order": ["id", "deleted", "object"] + }, + "CredentialList": { + "sources": ["#/components/schemas/VaultCredentialListResource"], + "order": ["object", "data", "has_more", "first_id", "last_id"] + }, + "Environment": { + "sources": ["#/components/schemas/EnvironmentParam"], + "fields": { + "packages": {"type": "*EnvironmentPackages"}, + "files": {"type": "[]json.RawMessage"}, + "environment_template_id": {"type": "string"}, + "workspace_directory": {"type": "string"}, + "network": {"type": "*EnvironmentNetworkInput"} + }, + "order": ["plugins", "skills", "env", "setup_commands", "packages", "files", "environment_template_id", "type", "workspace_directory", "capability_directories", "network"] + }, + "EnvironmentConnectionAction": { + "sources": ["#/components/schemas/SessionRequiredActionResourceEnvironmentConnection"], + "order": ["environment_id", "type"] + }, + "EnvironmentFile": { + "sources": ["#/components/schemas/EnvironmentFileResource"], + "order": ["environment_id", "object", "path", "size_bytes"] + }, + "EnvironmentFileCreateRequest": { + "sources": ["#/components/schemas/HostedEnvironmentFileParam"], + "fields": { + "path": {"type": "*string"} + }, + "order": ["type", "data", "file_id", "path"] + }, + "EnvironmentFileList": { + "sources": ["#/components/schemas/EnvironmentFileListResource"] + }, + "EnvironmentInfo": { + "sources": ["#/components/schemas/PublicEnvironmentResource"], + "order": ["id", "object", "type", "status", "files", "plugins", "skills"] + }, + "EnvironmentNetwork": { + "sources": ["#/components/schemas/NetworkPolicyResource"] + }, + "EnvironmentNetworkInput": { + "sources": ["#/components/schemas/NetworkPolicyParam"] + }, + "EnvironmentPackages": { + "exclude": ["system"], + "sources": ["#/components/schemas/EnvironmentPackagesParam"], + "fields": { + "npm": {"omit": false}, + "python": {"omit": false} + }, + "order": ["npm", "python"] + }, + "EnvironmentPackagesResponse": { + "sources": ["#/components/schemas/EnvironmentPackagesResource"], + "order": ["npm", "python", "system"] + }, + "EnvironmentTemplate": { + "sources": ["#/components/schemas/EnvironmentTemplateResource"], + "order": ["id", "object", "name", "created_at", "updated_at", "capability_directories", "network", "packages", "files", "plugins", "skills"] + }, + "EnvironmentTemplateDeleted": { + "sources": ["#/components/schemas/DeletedEnvironmentTemplateResource"] + }, + "EnvironmentTemplateList": { + "sources": ["#/components/schemas/EnvironmentTemplateListResource"], + "order": ["object", "data", "has_more", "first_id", "last_id"] + }, + "ErrorResponse": { + "sources": ["#/components/schemas/ErrorResponse-2"], + "fields": { + "error": {"type": "APIError"} + } + }, + "FunctionCallAction": { + "sources": ["#/components/schemas/SessionRequiredActionResourceFunctionCall"], + "fields": { + "arguments": {"type": "any"} + }, + "order": ["arguments", "call_id", "name", "turn_id", "type"] + }, + "FunctionToolInput": { + "sources": ["#/components/schemas/AgentToolConfigParamFunction"], + "fields": { + "name": {"type": "*string"}, + "description": {"type": "*string"}, + "parameters": {"type": "json.RawMessage"}, + "defer_loading": {"type": "json.RawMessage"} + } + }, + "InlineAgent": { + "sources": ["#/components/schemas/SessionAgentConfigParam"], + "fields": { + "x_agents_core": {"type": "*AgentsCore"}, + "reasoning": {"type": "*Reasoning"}, + "text": {"type": "*TextConfigInput"} + }, + "order": ["x_agents_core", "model", "instructions", "multi_agent", "reasoning", "service_tier", "text", "tools"] + }, + "InputContent": { + "sources": ["#/components/schemas/InputContentParam"] + }, + "InputMessage": { + "sources": ["#/components/schemas/InputMessageParam"], + "fields": { + "type": {"type": "string"} + } + }, + "InputTokenDetails": { + "sources": ["#/components/schemas/InputTokensDetailsResource"] + }, + "Item": { + "sources": ["#/components/schemas/SessionTurnItemResource"], + "fields": { + "id": {"type": "string"}, + "status": {"type": "string", "omit": false}, + "role": {"type": "string"}, + "phase": {"type": "string"}, + "content": {"type": "[]ItemContent"}, + "command": {"type": "string"}, + "name": {"type": "string"}, + "call_id": {"type": "string"}, + "server_label": {"type": "string"}, + "arguments": {"type": "any"}, + "output": {"type": "any"}, + "error": {"type": "any"}, + "action": {"type": "*WebSearchAction"}, + "agent_id": {"type": "string"}, + "sender_agent_id": {"type": "string"}, + "recipient_agent_id": {"type": "string"} + }, + "order": ["id", "turn_id", "type", "status", "role", "phase", "content", "command", "cwd", "duration_ms", "exit_code", "name", "call_id", "server_label", "arguments", "output", "error", "action", "agent_id", "sender_agent_id", "recipient_agent_id", "recipient_agent_ids", "model", "reasoning_effort", "summary"] + }, + "ItemContent": { + "sources": ["#/components/schemas/MessageContentResource", "#/components/schemas/EncryptedContentResource"], + "fields": { + "image_url": {"type": "string"} + } + }, + "ItemList": { + "sources": ["#/components/schemas/SessionItemListResource"], + "order": ["object", "first_id", "last_id", "data", "has_more"] + }, + "MCPHTTPTransport": { + "sources": ["#/components/schemas/PersistedMcpTransportConfigParamHttp"], + "fields": { + "headers": {"type": "*map[string]string"} + } + }, + "MCPTool": { + "sources": ["#/components/schemas/AgentToolResourceMcp"], + "fields": { + "transport": {"type": "MCPHTTPTransport"}, + "allowed_tools": {"type": "*[]string"} + }, + "order": ["type", "server_label", "transport", "allowed_tools", "connection_origin", "credential_id", "request_metadata", "required"] + }, + "MCPToolInput": { + "sources": ["#/components/schemas/AgentToolConfigParamMcp"], + "fields": { + "server_label": {"type": "*string"}, + "allowed_tools": {"type": "json.RawMessage", "omit": false}, + "connection_origin": {"omit": false}, + "credential_id": {"omit": false}, + "request_metadata": {"type": "json.RawMessage", "omit": false}, + "required": {"type": "json.RawMessage", "omit": false} + }, + "order": ["type", "server_label", "transport", "allowed_tools", "connection_origin", "credential_id", "request_metadata", "required"] + }, + "MultiAgentConfig": { + "sources": ["#/components/schemas/MultiAgentConfigResource"], + "fields": { + "max_concurrent_subagents": {"type": "*int"} + } + }, + "OAuthCredentialRefresh": { + "sources": ["#/components/schemas/McpOauthRefreshResource"], + "order": ["client_id", "token_endpoint", "token_endpoint_auth", "resource", "scope"] + }, + "OAuthCredentialRefreshInput": { + "sources": ["#/components/schemas/CreateMcpOauthRefreshParam"], + "fields": { + "client_id": {"type": "*string"}, + "refresh_token": {"type": "*string"}, + "token_endpoint": {"type": "*string"}, + "token_endpoint_auth": {"type": "*OAuthEndpointAuthInput"} + }, + "order": ["client_id", "refresh_token", "token_endpoint", "token_endpoint_auth", "resource", "scope"] + }, + "OAuthCredentialRefreshReplacement": { + "sources": ["#/components/schemas/RotateMcpOauthRefreshParam"], + "fields": { + "scope": {"type": "json.RawMessage"}, + "token_endpoint_auth": {"type": "*OAuthEndpointAuthReplacement"} + } + }, + "OAuthEndpointAuth": { + "sources": ["#/components/schemas/McpOauthTokenEndpointAuthResource"] + }, + "OAuthEndpointAuthInput": { + "sources": ["#/components/schemas/CreateMcpOauthTokenEndpointAuthParam"] + }, + "OAuthEndpointAuthReplacement": { + "sources": ["#/components/schemas/RotateMcpOauthTokenEndpointAuthParam"] + }, + "OutputTokenDetails": { + "sources": ["#/components/schemas/OutputTokensDetailsResource"] + }, + "Reasoning": { + "sources": ["#/components/schemas/ReasoningParam"] + }, + "ReasoningItem": { + "sources": ["#/components/schemas/ReasoningItemResource"], + "order": ["id", "turn_id", "type", "status", "summary"] + }, + "RequiredAction": { + "sources": ["#/components/schemas/SessionRequiredActionResource"], + "fields": { + "arguments": {"type": "any"}, + "call_id": {"type": "string"}, + "name": {"type": "string"}, + "turn_id": {"type": "string"}, + "environment_id": {"type": "string"} + }, + "order": ["type", "arguments", "call_id", "name", "turn_id", "environment_id"] + }, + "SavedAgent": { + "sources": ["#/components/schemas/AgentResource"], + "embed": {"SavedAgentConfiguration": ["x_agents_core", "model", "name", "instructions", "multi_agent", "reasoning", "service_tier", "text", "tools"]}, + "order": ["id", "object", "metadata", "created_at", "updated_at"] + }, + "SavedAgentConfiguration": { + "sources": ["#/components/schemas/AgentResource"], + "fields": { + "x_agents_core": {"type": "*SavedAgentCore"}, + "reasoning": {"type": "Reasoning"} + }, + "exclude": ["id", "object", "created_at", "updated_at", "metadata"], + "order": ["x_agents_core", "model", "name", "instructions", "multi_agent", "reasoning", "service_tier", "text", "tools"] + }, + "SavedAgentList": { + "sources": ["#/components/schemas/AgentListResource"], + "order": ["object", "data", "has_more", "first_id", "last_id"] + }, + "TextConfigInput": { + "sources": ["#/components/schemas/TextParam"] + }, + "SendSubagentInputCallItem": { + "sources": ["#/components/schemas/SendSubagentInputCallItemResource"], + "order": ["id", "turn_id", "type", "status", "sender_agent_id", "recipient_agent_id", "content"] + }, + "Session": { + "sources": ["#/components/schemas/SessionResource"], + "fields": { + "x_agents_core": {"type": "*SessionCore"}, + "usage": {"type": "*TokenUsage"} + }, + "order": ["x_agents_core", "id", "agent", "created_at", "environment", "error", "last_active_at", "metadata", "object", "required_actions", "status", "usage", "vault_ids"] + }, + "SessionArtifact": { + "sources": ["#/components/schemas/SessionArtifactResource"], + "order": ["id", "created_at", "environment_id", "object", "path", "session_id", "size_bytes", "turn_id"] + }, + "SessionArtifactDeleted": { + "sources": ["#/components/schemas/DeletedSessionArtifactResource"] + }, + "SessionArtifactList": { + "sources": ["#/components/schemas/SessionArtifactListResource"], + "order": ["object", "first_id", "last_id", "data", "has_more"] + }, + "SessionDeleted": { + "sources": ["#/components/schemas/DeletedSessionResource"], + "order": ["id", "deleted", "object"] + }, + "SessionEnvironment": { + "sources": ["#/components/schemas/EnvironmentResource"], + "fields": { + "id": {"type": "string"}, + "capability_directories": {"type": "*[]string"}, + "remote_url": {"type": "string"}, + "workspace_directory": {"type": "string"}, + "files": {"type": "*[]json.RawMessage"}, + "plugins": {"type": "*[]json.RawMessage"}, + "skills": {"type": "*[]json.RawMessage"} + }, + "order": ["type", "id", "capability_directories", "remote_url", "workspace_directory", "network", "packages", "files", "plugins", "skills"] + }, + "SessionEnvironmentState": { + "sources": ["#/components/schemas/SessionEnvironmentStateResource"], + "fields": { + "error": {"type": "*StreamError"} + } + }, + "SessionEvent": { + "sources": ["#/components/schemas/SessionEvent"], + "fields": { + "subagent": {"type": "*Subagent"}, + "session_id": {"type": "string"}, + "turn_id": {"type": "string"}, + "session": {"type": "*Session"}, + "turn": {"type": "*Turn"}, + "item": {"type": "*Item"}, + "item_id": {"type": "string"}, + "output_index": {"type": "*int32"}, + "content_index": {"type": "*int"}, + "part": {"type": "*ItemContent"}, + "usage": {"type": "*TokenUsage"} + }, + "order": ["subagent", "type", "event_id", "session_id", "turn_id", "session", "turn", "item", "item_id", "output_index", "content_index", "part", "delta", "text", "error", "environment", "usage"] + }, + "SessionInput": { + "sources": ["#/components/schemas/SessionInputParam"], + "fields": { + "call_id": {"type": "string"}, + "turn_id": {"type": "string"}, + "error": {"type": "json.RawMessage"}, + "output": {"type": "any"} + }, + "order": ["type", "input", "call_id", "turn_id", "success", "error", "output"] + }, + "SessionList": { + "sources": ["#/components/schemas/SessionListResource"], + "order": ["object", "first_id", "last_id", "data", "has_more"] + }, + "Skill": { + "sources": ["#/components/schemas/SkillResource"], + "order": ["id", "object", "created_at", "name", "description", "default_version", "latest_version"] + }, + "SkillDeleted": { + "sources": ["#/components/schemas/DeletedSkillResource"], + "order": ["id", "object", "deleted"] + }, + "SkillList": { + "sources": ["#/components/schemas/SkillListResource"] + }, + "SkillUpdateRequest": { + "sources": ["#/components/schemas/SetDefaultSkillVersionBody"] + }, + "SkillVersion": { + "sources": ["#/components/schemas/SkillVersionResource"], + "order": ["id", "object", "created_at", "skill_id", "version", "name", "description"] + }, + "SkillVersionDeleted": { + "sources": ["#/components/schemas/DeletedSkillVersionResource"], + "order": ["id", "object", "version", "deleted"] + }, + "SkillVersionList": { + "sources": ["#/components/schemas/SkillVersionListResource"] + }, + "SourceFile": { + "sources": ["#/components/schemas/OpenAIFile"], + "fields": { + "expires_at": {"omit": false}, + "status_details": {"omit": false} + }, + "order": ["id", "object", "bytes", "created_at", "filename", "purpose", "status", "expires_at", "status_details"] + }, + "SourceFileDeleted": { + "sources": ["#/components/schemas/DeleteFileResponse"] + }, + "SourceFileList": { + "sources": ["#/components/schemas/ListFilesResponse"], + "fields": { + "first_id": {"type": "*string"}, + "last_id": {"type": "*string"} + }, + "order": ["object", "data", "has_more", "first_id", "last_id"] + }, + "StreamError": { + "sources": ["#/components/schemas/SessionErrorResource"], + "fields": { + "code": {"type": "string"}, + "param": {"omit": true} + }, + "order": ["code", "type", "message", "param"] + }, + "Subagent": { + "sources": ["#/components/schemas/SubagentResource"], + "order": ["id", "object", "session_id", "parent_agent_id", "opened_at", "closed_at", "name", "instructions", "status"] + }, + "SubagentControlCallItem": { + "sources": ["#/components/schemas/ResumeSubagentCallItemResource", "#/components/schemas/InterruptSubagentCallItemResource", "#/components/schemas/CloseSubagentCallItemResource"], + "order": ["id", "turn_id", "type", "status", "sender_agent_id", "recipient_agent_id"] + }, + "SubagentList": { + "sources": ["#/paths/~1agents~1sessions~1{session_id}~1subagents/get/responses/200/content/application~1json/schema"], + "order": ["object", "first_id", "last_id", "data", "has_more"] + }, + "SummaryText": { + "sources": ["#/components/schemas/SummaryTextResource"] + }, + "TextConfig": { + "sources": ["#/components/schemas/TextResource"] + }, + "TextFormat": { + "sources": ["#/components/schemas/TextFormatResource"], + "fields": { + "schema": {"type": "json.RawMessage"} + } + }, + "TokenUsage": { + "sources": ["#/components/schemas/TokenUsageResource"] + }, + "Turn": { + "sources": ["#/components/schemas/TurnResource"], + "fields": { + "error": {"type": "*TurnError"}, + "usage": {"type": "*TokenUsage"} + }, + "order": ["id", "agent_id", "subagent_id", "session_id", "object", "status", "created_at", "started_at", "completed_at", "error", "usage"] + }, + "TurnError": { + "sources": ["#/components/schemas/SessionTurnErrorResource"] + }, + "TurnList": { + "sources": ["#/components/schemas/SessionTurnListResource"], + "order": ["object", "first_id", "last_id", "data", "has_more"] + }, + "UpdateAgentRequest": { + "sources": ["#/components/schemas/UpdateAgentParams"], + "fields": { + "x_agents_core": {"type": "*SavedAgentCoreInput"}, + "metadata": {"type": "map[string]*string"}, + "reasoning": {"type": "*Reasoning"}, + "text": {"type": "*TextConfigInput"} + }, + "order": ["x_agents_core", "model", "name", "instructions", "metadata", "multi_agent", "reasoning", "service_tier", "text", "tools"] + }, + "Vault": { + "sources": ["#/components/schemas/VaultResource"], + "order": ["id", "object", "created_at", "name", "metadata"] + }, + "VaultDeleted": { + "sources": ["#/components/schemas/DeletedVaultResource"], + "order": ["id", "deleted", "object"] + }, + "VaultList": { + "sources": ["#/components/schemas/VaultListResource"], + "order": ["object", "data", "has_more", "first_id", "last_id"] + }, + "WaitForSubagentsCallItem": { + "sources": ["#/components/schemas/WaitForSubagentsCallItemResource"], + "order": ["id", "turn_id", "type", "status", "sender_agent_id", "recipient_agent_ids"] + }, + "WebSearchAction": { + "marshal_union": true, + "sources": ["#/components/schemas/WebSearchActionResource"] + }, + "reasoningResponse": { + "sources": ["#/components/schemas/ReasoningResource"] + }, + "sessionError": { + "sources": ["#/components/schemas/SessionErrorResource"], + "fields": { + "code": {"type": "string"} + }, + "order": ["code", "type", "message", "param"] + } +} diff --git a/contracts/agents-api/harness-onboarding.md b/contracts/agents-api/harness-onboarding.md index c5cd4a2c3..661e4dd47 100644 --- a/contracts/agents-api/harness-onboarding.md +++ b/contracts/agents-api/harness-onboarding.md @@ -69,7 +69,6 @@ For example, the Codex adapter keeps its app-server and thread, the Claude adapt | `DurableSteerer` | Real implementation on every Turn | Distinguish a complete write from the native application receipt; keep retry identity | | `Steerer` | Explicit implementation or Unsupported | Additional non-durable active-Turn input | | `FunctionResultSubmitter` | Explicit implementation or Unsupported | Match native call and result identity and acknowledge application | -| `PermissionResponder`, `UserChoiceResponder` | Explicit implementation or Unsupported | Respond to exact emitted identities; unknown or expired interactions stay distinct from Unsupported | | `WorkspaceDirectoryLister`, `WorkspaceWriter` | Explicit on Turn and Executor owners | Use the authorized workspace, confirm access, commit or close, or return the operation's Unsupported error | | Neutral messages, images, MCP, structured output and Subagent observations | Explicit capability decisions | Keep each operation's protocol semantics; reject unsupported input before submission | @@ -83,7 +82,7 @@ func (s *Session) SubmitFunctionResult(context.Context, proto.FunctionResultPayl } ``` -The reason is a fixed safe string, never submitted content, a credential or raw native diagnostics. Unsupported guarantees no native side effect and is not a successful empty operation. Installation unavailability, unknown interaction IDs, native failures and uncertain outcomes keep their own errors and ownership. A nil `Turn` still means that no input was submitted and the output stays with the caller; never use it as an Unsupported marker. +The reason is a fixed safe string, never submitted content, a credential or raw native diagnostics. Unsupported guarantees no native side effect and is not a successful empty operation. Installation unavailability, unknown call IDs, native failures and uncertain outcomes keep their own errors and ownership. A nil `Turn` still means that no input was submitted and the output stays with the caller; never use it as an Unsupported marker. The wire request carries no working directory. The Runtime checks `local_environment.workspace_directory` against its binding and gives the Harness its bound workspace directory in `LocalEnvironment.WorkspaceRoot`; run the native Harness there. @@ -105,26 +104,26 @@ A Session owns one reusable Executor in its connected Runtime; a Turn owns one i **Binding.** The Runtime binds its Executor record to the Session, Environment, connection and immutable execution configuration. Resume identity and prior-Turn recovery flags are continuity assertions, not configuration changes. A supplied native identity must match the retained owner, and when existing history is required, recovery never starts a new root. A configuration conflict is an error, not a hot switch. A lost connection retires its owners and handles; old timers, output and cancellation cannot affect their replacements. -**Per-Turn state.** Each Turn gets a fresh wrapper, output channel and receipt state. Steering, function, permission and user-choice interfaces belong to that Turn. Native callbacks capture the originating Turn before asynchronous work, so a late event is never attributed to whichever Turn is active. Native processes, query or transport connections, fixed capability configuration and native session identity belong to the Executor. Do not reset completed `sync.Once` values or reuse an old Turn object. +**Per-Turn state.** Each Turn gets a fresh wrapper, output channel and receipt state. Steering and function interfaces belong to that Turn. Native callbacks capture the originating Turn before asynchronous work, so a late event is never attributed to whichever Turn is active. Native processes, query or transport connections, fixed capability configuration and native session identity belong to the Executor. Do not reset completed `sync.Once` values or reuse an old Turn object. **Start.** A nil Turn from `StartTurn` guarantees that no native input was submitted and the output channel was not retained; the Runtime then closes the channel. Once input may have been submitted, return a non-nil Turn even with an error: that Turn owns exactly-once output closure and stays tracked until settlement. Unknown input is never replayed. A definite `executor_unavailable` Start rejection allows one common recovery attempt, only after the previous Executor has been closed and no input was submitted; the Runtime rechecks the same physical peer and the current authorization. -**Cancellation and settlement.** `Turn.Cancel` targets only that Turn and does not close a healthy Executor. `AwaitSettlement` applies after both natural completion and cancellation. Success means output can no longer be written and the Turn's native events, input, functions, interactions and child work have settled. Native completion or cancellation confirmation is independent of resource retirement: closing a transport cannot supply a missing native terminal or operation receipt. +**Cancellation and settlement.** `Turn.Cancel` targets only that Turn and does not close a healthy Executor. `AwaitSettlement` applies after both natural completion and cancellation. Success means output can no longer be written and the Turn's native events, input, functions and child work have settled. Native completion or cancellation confirmation is independent of resource retirement: closing a transport cannot supply a missing native terminal or operation receipt. - `Reusable=true` also confirms that the native owner can accept the next Turn. `Reusable=false` requires a reason and a later confirmed Executor close. - An error means settlement is unconfirmed and frees neither ownership nor capacity. Caller deadlines stop the wait, not the tracked cleanup. Retry the same cleanup target serially; a failed cleanup blocks replacement and keeps its resource slot. - `Executor.Close` confirms resource retirement independently of the Turn outcome: an immutable Turn error must not prevent closing the native transport once its work and output have stopped. - Include owned background work in settlement and keep the exact native cleanup target after a failure. Native termination belongs to the adapter; a bulk cleanup acknowledgement alone does not establish quiescence. -- Every `Session`, including a direct-call factory result, declares `CancellationOutcome`. `Turn` inherits it. The snapshot keeps observed native identity, Usage and output and remains readable after cancellation. Missing evidence stays unset; an empty `DonePayload` means nothing has been observed, not that cancellation succeeded or is unsupported. Reading the snapshot does not wait for settlement. -- Direct-call `Session.Cancel` requests cancellation; output closure signals teardown. Turn settlement still requires `AwaitSettlement` and any required `Executor.Close`; neither a successful cancellation request nor its snapshot replaces those waits. +- Every `Session` declares `CancellationOutcome`. `Turn` inherits it. The snapshot keeps observed native identity, Usage and output and remains readable after cancellation. Missing evidence stays unset; an empty `DonePayload` means nothing has been observed, not that cancellation succeeded or is unsupported. Reading the snapshot does not wait for settlement. +- `Session.Cancel` requests cancellation; output closure signals teardown. Turn settlement still requires `AwaitSettlement` and any required `Executor.Close`; neither a successful cancellation request nor its snapshot replaces those waits. -**What the Runtime does around a Turn.** One output consumer starts before native Start, drains the bounded 64-frame channel and keeps the terminal observation until Start publication, Turn settlement and admitted operation receipts finish. Natural completion never calls Cancel. Input, function and interaction admission close before settlement; operations already admitted hold their barrier through native receipts and outbound acknowledgement. The Runtime sends cancellation to the Turn before waiting on that barrier, because a written input may need a native interrupt to produce its receipt. It joins native settlement, any required confirmed Executor close, output drain and all admitted operations before an applied acknowledgement or reuse, and only then forwards Done or an applied cancellation receipt. A failed Close can report failure while keeping the same Run and outstanding operations for retry; a closed caller wait cannot manufacture an applied input receipt. The Runtime commits native continuity and releases the old Run's admission before publishing Done, since the receiver may start another Turn at once; a late terminal-send failure belongs to the old Run and cannot invalidate a successor that already owns the Executor. Connection shutdown owns transport-loss cleanup. The settlement wait is ten seconds and the receipt send budget five seconds; a timeout is not proof of quiescence. +**What the Runtime does around a Turn.** One output consumer starts before native Start, drains the bounded 64-frame channel and keeps the terminal observation until Start publication, Turn settlement and admitted operation receipts finish. Natural completion never calls Cancel. Input and function admission close before settlement; operations already admitted hold their barrier through native receipts and outbound acknowledgement. The Runtime sends cancellation to the Turn before waiting on that barrier, because a written input may need a native interrupt to produce its receipt. It joins native settlement, any required confirmed Executor close, output drain and all admitted operations before an applied acknowledgement or reuse, and only then forwards Done or an applied cancellation receipt. A failed Close can report failure while keeping the same Run and outstanding operations for retry; a closed caller wait cannot manufacture an applied input receipt. The Runtime commits native continuity and releases the old Run's admission before publishing Done, since the receiver may start another Turn at once; a late terminal-send failure belongs to the old Run and cannot invalidate a successor that already owns the Executor. Connection shutdown owns transport-loss cleanup. The settlement wait is ten seconds and the receipt send budget five seconds; a timeout is not proof of quiescence. ## Events, inputs and optional capabilities Use [`internal/agentdaemon/proto`](https://github.com/MiniMax-AI/OpenAgentCore/tree/main/internal/agentdaemon/proto) for neutral requests, events and receipts. Each Turn emits only its own events with its Run ID, in order, and one terminal outcome. Native IDs and usage are observed, never invented; a missing measurement is unknown, not zero. -Initial input and steering use ordered `proto.MessageInput`. Keep user-message and content order. Text-only adapters reject images through `TextOnly()` instead of dropping them; image adapters translate each part natively and acknowledge an active batch only after all its messages are applied. A successful transport write is distinct from confirmed native application. User-choice answers use the emitted question ID and an array of values; the shared `PromptForUserChoiceDecisionPayload.AnswersFor` validates identity before consuming a pending interaction, so never map answers by header or position. Resume only the exact history bound to the Session; missing, ambiguous or foreign history fails before new model input. Device identity is not native session ownership. +Initial input and steering use ordered `proto.MessageInput`. Keep user-message and content order. Text-only adapters reject images through `TextOnly()` instead of dropping them; image adapters translate each part natively and acknowledge an active batch only after all its messages are applied. A successful transport write is distinct from confirmed native application. Resume only the exact history bound to the Session; missing, ambiguous or foreign history fails before new model input. Device identity is not native session ownership. ### Required and extension operations @@ -148,23 +147,21 @@ A Harness that supports the Subagent reads implements the [neutral observation c ## Register the adapter -Registration is static and requires a build. Export one `agent.Declaration` from `apps/daemon/internal/agent//declaration.go`, then add it to `harnessDeclarations` in [`cli/agent_discovery.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/apps/daemon/internal/cli/agent_discovery.go). The declaration contains the kind and complete capability descriptor, the shared model `Configuration` and a `Discover` function. Discovery receives the profile and diagnostic writers, owns native configuration and availability checks, and returns the installed `agent.Runtime` with its descriptor and session and Executor factories. Return nil when the adapter is not configured; return an unavailable descriptor with a session factory when configured prerequisites fail. Keep version gates and factory-selection conditions inside the adapter. +Registration is static and requires a build. Export one `agent.Declaration` from `apps/daemon/internal/agent//declaration.go`, then add it to `harnessDeclarations` in [`cli/agent_discovery.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/apps/daemon/internal/cli/agent_discovery.go). The declaration contains the kind and complete capability descriptor, the shared model `Configuration` and a `Discover` function. Discovery receives the profile and diagnostic writers, owns native configuration and availability checks, and returns the installed `agent.Runtime` with its descriptor, Executor factory and view declaration. Return nil when the adapter is not configured; return an unavailable descriptor without an Executor factory or view when configured prerequisites fail. Keep version gates and factory-selection conditions inside the adapter. -[`cli/agent_registration.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/apps/daemon/internal/cli/agent_registration.go) iterates the discovered runtimes and calls `Registry.Register` from `agent/harness.go`. It verifies that discovery retained the declared kind and installs factories in this order: +[`cli/agent_registration.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/apps/daemon/internal/cli/agent_registration.go) iterates the discovered runtimes and calls `Registry.Register` from `agent/harness.go`. It verifies that discovery retained the declared kind and registers the Runtime in this order: | Order | Method | Registers | | --- | --- | --- | -| 1 | `RegisterKind(proto.SupportedAgentKind, harnessconfig.Configuration, agent.Factory)` | Kind, availability, version, `AgentKindCapabilities`, the model configuration declaration and the direct-call factory. It resets the other registrations, so call it first. | +| 1 | `RegisterKind(proto.SupportedAgentKind, harnessconfig.Configuration)` | Kind, availability, version, `AgentKindCapabilities` and the model configuration declaration. It resets the other registrations, so call it first. | | 2 | `RegisterExecutor(kind, agent.ExecutorFactory)` | The Executor and Turn lifecycle used for execution; derives the `Preparation` capability | | 3 | `RegisterView(kind, agent.View)` | Optional: the agent-host view declaration from `Runtime.View`. It panics with `ErrInvalidView` when `View.Validate` fails. Its Executor factory validates the model configuration like `RegisterExecutor` and enforces the [gateway rule](#endpoints-and-proxy). | -The direct-call `agent.Factory` delegates to the same Executor implementation. - `Runtime.View` declares how the Harness runs in an agent-host Session view, described in [Run in an agent-host view](#run-in-an-agent-host-view). Every adapter sets it explicitly; `View: nil` means the agent host rejects the kind, and `Registry.ResolveView` returns an error wrapping `ErrUnsupportedOperation`. `TestPublicHarnessContractDeclarations` requires the field in each declaration. Every `proto.AgentKindCapabilities` field must be explicitly `proto.CapabilitySupported` or `proto.CapabilityUnsupported`, even for an unavailable Harness. `proto.CapabilityUnspecified` is invalid: zero values and omitted fields never mean Unsupported. An installation probe may set an individual field with `proto.CapabilityFromBool`; it must not populate unmentioned or future fields. Availability stays separate in `SupportedAgentKind.Available`. Registration validates the complete declaration before changing the registry, and the wire carries an explicit boolean for every field, so omitted and null fields are invalid. A new field requires a decision in every production declaration. Runtime consumers use `IsSupported()` and reject unsupported requests before native operations; an interface assertion verifies implementation, never support. Every declaration must match the behavior verified for that installation; the [Core–Runtime protocol](../../docs/runtime-protocol.md#capability-declarations) owns how declarations travel and are frozen. -The admission mapping is explicit. `Steering` controls non-durable `Steerer` input. `DurableInputReceipts` controls `DurableSteerer` input and also requires the Turn settlement contract; neither implies the other, and Core's public text profile requires both. `Permissions` qualifies permission and user-choice responses together and requires both native response paths. Workspace declarations describe the authorized resource owner, including the common Runtime workspace implementation. Runtime registration does not grant Core qualification; the service profile does. +The admission mapping is explicit. `Steering` controls non-durable `Steerer` input. `DurableInputReceipts` controls `DurableSteerer` input and also requires the Turn settlement contract; neither implies the other, and Core's public text profile requires both. Workspace declarations describe the authorized resource owner, including the common Runtime workspace implementation. `WorkspaceReadPreparation` admits `execution_prepare` with `workspace_read_only`. The Runtime readies that preparation itself and serves its reads from the bound local workspace directory without calling the Executor factory, so an adapter declares it only when this kind's Environment workspace is that local directory. Registration does not derive it. Runtime registration does not grant Core qualification; the service profile does. The runnable test-only example [`testdata/onboarding/main.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/apps/daemon/testdata/onboarding/main.go) registers a text-only synthetic Harness under the `mcode` kind, because Core admits only [catalog](./harness-catalog.md) Harnesses. It shows a Session-owned Executor, fresh Turns, durable steering, cancellation and history binding, and is never shipped. @@ -201,7 +198,7 @@ Run the `engine` and `execution` tests for omission, policy, combination and err ## Native model configuration -[`internal/harnessconfig/harness.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/harnessconfig/harness.go) owns the shared configuration declaration and pure preparation contract. Each adapter supplies one `Configuration`, in `internal/harnessconfig/`, to Core's composition and to the Runtime's `RegisterKind`. The direct factory and Executor paths both validate through that declaration before native side effects, and Registry wrappers keep the declaration with the factory. The wire object is `proto.HarnessConfig`. [Model execution](./model-execution.md#native-model-parameters) lists each Harness's accepted fields. +[`internal/harnessconfig/harness.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/harnessconfig/harness.go) owns the shared configuration declaration and pure preparation contract. Each adapter supplies one `Configuration`, in `internal/harnessconfig/`, to Core's composition and to the Runtime's `RegisterKind`. The Executor and view paths both validate through that declaration before native side effects, and Registry wrappers keep the declaration with the factory. The wire object is `proto.HarnessConfig`. [Model execution](./model-execution.md#native-model-parameters) lists each Harness's accepted fields. Every request names a nonempty `model` and a `model_provider`; preparation rejects a request without either, including an explicit null or empty value, before any native side effect. An empty declaration therefore accepts no request. Unknown protocol formats and duplicate protocol declarations fail at registration. @@ -212,7 +209,7 @@ The declaration's ordered `protocols` list is the only source of accepted protoc Before starting, record the operation set, expected results, exclusions and stopping conditions. A qualification ends when its declared operations pass; it does not expand to match another Harness's feature list. 1. **Contract tests.** Call `agent/contracttest.TextLifecycle` from a test named `TestSharedTextLifecycle` with the adapter's prepared Executor and a deterministic native fixture; `claudesdk/executor_test.go` is the reference. It checks independent Turn streams, native owner and history continuity, durable write and application receipts, stale cancellation and healthy continuation after cancellation. `make check-runtime-contract` runs it together with the shared wire, gateway, transport and dispatcher tests, the declaration completeness check and each adapter's `TestUnsupportedExtensionsHaveNoNativeEffects`. Adapter tests also cover two ordinary Turns sharing one native process or connection and history, cancellation followed by another Turn, stale cancellation and late events, native exit, cleanup failure, input write and application receipts, unknown outcomes and fresh per-Turn usage, function, input and child-observation state. State whether a fixture is controlled or a real provider. -2. **Shared integration.** `TestThirdHarnessPublicOnboarding` runs the synthetic Harness through public Session and input admission, Worker device selection, the real WebSocket gateway, the daemon Registry and Router, neutral events and durable terminal projection. It uses a custom immutable `engine.Catalog` in the same `execution.Policy` given to the API handler and the dispatcher, and checks applied input receipts, saved native identity, continuation, cancellation, unsupported optional requests and missing mandatory Runtime support. The fixture has no workspace, MCP, public functions, permissions or user-choice handlers, and its registration stays local to the test. It proves the integration path, not native execution. +2. **Shared integration.** `TestThirdHarnessPublicOnboarding` runs the synthetic Harness through public Session and input admission, Worker device selection, the real WebSocket gateway, the daemon Registry and Router, neutral events and durable terminal projection. It uses a custom immutable `engine.Catalog` in the same `execution.Policy` given to the API handler and the dispatcher, and checks applied input receipts, saved native identity, continuation, cancellation, unsupported optional requests and missing mandatory Runtime support. The fixture has no workspace, MCP or public functions, and its registration stays local to the test. It proves the integration path, not native execution. 3. **Real acceptance.** Use the pinned official Python SDK and raw HTTP against Core, a real provider API, the native Harness and a dedicated database. Verify initial execution, a warm follow-up, cancellation and restart with continuation; record native owner identity and same-condition cold and warm timing. For workspace placements also verify Files and Artifacts, workspace identity, that no credentials appear in public responses and that foreign history is rejected. `services/core/tests/official_hosted_functions_native.py` holds the shared function assertions: success and error, native file output and public Artifact bytes, same-history continuation after restart, foreign result rejection and pending-call cancellation. Synthetic or failed runs never count. The opt-in tests below run the pinned-SDK fixtures in `services/core/tests` against a real daemon and model; each runs when `OAC_TEST_OFFICIAL_SDK_PYTHON`, `OAC_TEST_NATIVE_DAEMON_BIN`, `OAC_TEST_NATIVE_PROOF_DIR` and its private options file are set. The options file is a JSON object with exactly `model` and `model_provider` (the fields of `x_agents_core.model_provider`); the test sets it as the deployment default model provider, which the fixtures' `environment: none` Sessions freeze at creation. 4. **Regression.** Existing Harnesses keep working. Run targeted tests, then `make check`; run `make openapi` after API changes and `make sqlc-generate` after query changes. 5. **Review.** Follow the [blind review workflow](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/CONTRIBUTING.md#review). @@ -252,7 +249,7 @@ The daemon's `clirunner` starts every native child in its own Unix process group Owned output pipes stay readable after the leader exits. Consumers drain stdout and stderr before calling `Wait`, which joins the cached process result and closes the readers. `Done` reports leader reaping and group cleanup signals; it is not a native execution receipt or proof of persisted history. SDK adapters settle each Turn and drain its observations before publishing completion, and Executor close also closes the query and awaits the native child. Process groups are lifecycle supervision, not isolation or containment of descendants that leave the group. -Adapters run native tools unattended with the launching user's permissions: Codex with approval policy `never` and full access, Claude through the adapter's tool callback in native `default` permission mode with the SDK sandbox disabled, and MiniMax with bypassed permissions and its sandbox disabled. Do not add permission profiles, bubblewrap wrappers or native sandbox settings; there is one execution path for every Environment origin. Resource paths are operator configuration, not a permission boundary. +Adapters run native tools unattended with the launching user's permissions, and a Harness never asks a human. Codex runs with approval policy `never`, under which it settles MCP elicitation itself without reaching the client, and with full access; the adapter also disables its blocking `request_user_input` tool. Claude runs through the adapter's tool callback, which allows or denies without asking, in native `default` permission mode with the SDK sandbox disabled. MiniMax runs with bypassed permissions and its sandbox disabled; the adapter disables `askUser`, advertises no elicitation and answers `session/request_permission` with the ACP `cancelled` outcome, which MiniMax treats as a denial. No native permission or question request reaches Core: a human in the loop goes through a function tool, the Session reads [`requires_action`](./sessions-events.md#session-status) and the application submits the function result. Do not add permission profiles, bubblewrap wrappers or native sandbox settings; there is one execution path for every Environment origin. Resource paths are operator configuration, not a permission boundary. Network admission follows [Restricted network](./environments.md#restricted-network). diff --git a/contracts/agents-api/index.md b/contracts/agents-api/index.md index fa4a93fef..88c1f7cfc 100644 --- a/contracts/agents-api/index.md +++ b/contracts/agents-api/index.md @@ -9,10 +9,18 @@ Core targets the complete OpenAI Agents API as pinned below ([public API rule](h | File | Contents | | --- | --- | | [upstream.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream.json) | The pin: [openai-python](https://github.com/openai/openai-python/tree/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/resources/beta/agents) 3.13.0 at commit `d7c41ef`, resources under `beta/agents`, Beta header `agents=v1` | -| [upstream-routes.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream-routes.json), [upstream-fields.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream-fields.json) | The 58 method and path pairs and their official fields: 42 operations under `beta/agents`, 5 Files and 11 Skills operations. `scripts/extract-agents-api-upstream.py` extracts them from the pinned SDK; run it with that SDK installed | -| [openapi.yaml](./openapi.yaml) | Core's public schema, generated by `make openapi` from the route annotations in `services/core/internal/api/` and the wire types in [`v1/`](https://github.com/MiniMax-AI/OpenAgentCore/tree/main/contracts/agents-api/v1) | +| [upstream/openapi.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream/openapi.json) | Unmodified official OpenAPI 3.1 at commit `046a2a0f325bf11f97966f2729219f27281ba71e`, published on 2026-09-10. Its 58 Agents, Vaults, Files and Skills operations match the pinned SDK route set. `upstream.json` records the SHA-256 checksum | +| [upstream-routes.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream-routes.json) | The normalized method/path inventory generated from the official schema | +| [openapi.yaml](./openapi.yaml) | The official public contract with Core's `x_agents_core` extension on Agent and Session request/response objects | +| [go-bindings.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/go-bindings.json) | Go names, field representations, encoding order and stored projections; it does not define official field membership, enums or constraints | -Contract tests hold Core to the pin: the router and `openapi.yaml` serve exactly the pinned routes (`services/core/internal/api/routing_test.go`, `v1/upstream_contract_test.go`), every query parameter and field is official, and Core-only fields sit only inside `x_agents_core` on Agents and Sessions. Swagger 2.0 cannot express string-or-array unions, so `openapi.yaml` leaves Session `input` and function-result `output` unconstrained; the pinned types and Core's validation define them. Operations and fields newer than the pin wait for a protocol upgrade. +Run `make openapi` to regenerate the public Go types, route inventory and all three OpenAPI documents. `scripts/generate-public-api.py` reads the checked-in, checksum-verified official source without network access. It selects Agents, Vaults, Files and Skills and follows their schema references, preserving union types, nullability, required fields and constraints. Core's extension types in `v1/` remain authored in Go and are added to the public schema during generation. The internal `/core/v1` and `/api/v1` documents come from handler annotations. `make check-openapi` checks freshness and the generator; it also runs through `make check-go`. + +The public contract is the official API plus Core extensions. Standard fields are generated into `v1/official.gen.go`; `go-bindings.json` lists types consumed by Core and overrides only the Go representation or field order that existing storage or custom JSON encoding requires. Unspecified fields follow the official schema; shared shapes use one Go type. Selected discriminated unions also generate JSON serializers to retain required nullable fields for each variant. Other union serializers, request admission and state transitions remain implementation code. Contract tests verify that the public schema preserves the official definitions, extensions remain in `x_agents_core`, and all documents match registered routes. Official-client and raw HTTP tests verify behavior. Schema generation does not qualify an unimplemented feature; the gaps below still apply. Upstream upgrades update the OpenAPI and SDK pins together after comparison and compatibility tests. + +The official source and existing service have these recorded differences: Agents authentication errors can return a null `code`; empty Files pages return null `first_id` and `last_id`; File resources can return null `expires_at` and `status_details`. The source declares those fields non-null. The official-client response validator allows null only for these named fields and otherwise validates OpenAPI 3.1 response schemas. Files and Skills operations omit error responses in the source, so those error bodies use the upstream shared `ErrorResponse` schema. [Wire semantics](./wire-semantics.md) and raw HTTP tests qualify service behavior; the published schema retains the official definitions. + +Go input projections exclude `packages.system` to preserve its explicit rejection, recorded below. Generation does not enable an unsupported operation or change stored setup validation. Evidence for a status comes from the pinned official SDK and raw HTTP against the running service, as [CONTRIBUTING](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/CONTRIBUTING.md#compatibility-evidence) requires. diff --git a/contracts/agents-api/openapi.yaml b/contracts/agents-api/openapi.yaml index 12e691d56..1885da9ed 100644 --- a/contracts/agents-api/openapi.yaml +++ b/contracts/agents-api/openapi.yaml @@ -1,5423 +1,19444 @@ -basePath: /v1 -definitions: - v1.APIError: - properties: - code: - type: string - x-nullable: true - message: - type: string - param: - type: string - x-nullable: true - type: - type: string - required: - - message - - type - type: object - v1.Agent: - properties: - id: - type: string - instructions: - type: string - x-nullable: true - model: - type: string - multi_agent: - $ref: '#/definitions/v1.MultiAgentConfig' - name: - type: string - x-nullable: true - reasoning: - $ref: '#/definitions/v1.Reasoning' - service_tier: - enum: - - auto - type: string - text: - $ref: '#/definitions/v1.TextConfig' - tools: - items: - type: object - type: array - x_agents_core: - allOf: - - $ref: '#/definitions/v1.AgentsCore' - x-nullable: true - required: - - id - - model - - multi_agent - - reasoning - - service_tier - - text - - tools - type: object - v1.AgentContent: - properties: - encrypted_content: - type: string - text: - type: string - type: - enum: - - output_text - - encrypted_content - type: string - required: - - type - type: object - v1.AgentDeleted: - properties: - deleted: - enum: - - true - type: boolean - id: - type: string - object: - enum: - - agent.deleted - type: string - required: - - deleted - - id - - object - type: object - v1.AgentsCore: - properties: - harness: - enum: - - claude_sdk - - codex - - mcode - type: string - harness_config: - type: object - type: object - v1.CreateAgentRequest: - properties: - instructions: - type: string - x-nullable: true - metadata: - additionalProperties: - type: string - type: object - x-nullable: true - model: - type: string - multi_agent: - type: object - x-nullable: true - name: - maxLength: 128 - type: string - x-nullable: true - reasoning: - allOf: - - $ref: '#/definitions/v1.Reasoning' - x-nullable: true - service_tier: - enum: - - auto - - default - - flex - - priority - - fast - type: string - x-nullable: true - text: - allOf: - - $ref: '#/definitions/v1.SavedAgentTextInput' - x-nullable: true - tools: - items: - type: object - type: array - x-nullable: true - x_agents_core: - allOf: - - $ref: '#/definitions/v1.SavedAgentCoreInput' - x-nullable: true - required: - - model - type: object - v1.CreateCredentialRequest: - properties: - auth: - $ref: '#/definitions/v1.CredentialAuthInput' - name: - type: string - required: - - auth - - name - type: object - v1.CreateEventsRequest: - properties: - events: - items: - $ref: '#/definitions/v1.SessionInput' - type: array - required: - - events - type: object - v1.CreateSessionRequest: - properties: - agent: - $ref: '#/definitions/v1.InlineAgent' - agent_id: - type: string - environment: - $ref: '#/definitions/v1.Environment' - input: - description: |- - Input accepts a string or an ordered array of user InputMessage objects. - Required for none and streamed creation outside self_hosted; otherwise optional. - x-nullable: true - metadata: - additionalProperties: - type: string - type: object - x-nullable: true - stream: - default: false - type: boolean - vault_ids: - items: - type: string - type: array - x_agents_core: - $ref: '#/definitions/v1.SessionExecutionInput' - required: - - environment - type: object - v1.CreateVaultRequest: - properties: - metadata: - additionalProperties: - type: string - type: object - x-nullable: true - name: - type: string - type: object - v1.Credential: - properties: - auth: - $ref: '#/definitions/v1.CredentialAuth' - created_at: - type: integer - id: - type: string - name: - type: string - object: - enum: - - vault.credential - type: string - updated_at: - type: integer - vault_id: - type: string - required: - - auth - - created_at - - id - - name - - object - - updated_at - - vault_id - type: object - v1.CredentialAuth: - properties: - expires_at: - type: string - x-nullable: true - mcp_server_url: - type: string - refresh: - allOf: - - $ref: '#/definitions/v1.OAuthCredentialRefresh' - x-nullable: true - type: - enum: - - static_bearer - - mcp_oauth - type: string - required: - - mcp_server_url - - type - type: object - v1.CredentialAuthInput: - properties: - access_token: - minLength: 1 - type: string - expires_at: - type: string - x-nullable: true - mcp_server_url: - type: string - refresh: - allOf: - - $ref: '#/definitions/v1.OAuthCredentialRefreshInput' - x-nullable: true - token: - minLength: 1 - type: string - type: - enum: - - static_bearer - - mcp_oauth - type: string - required: - - mcp_server_url - - type - type: object - v1.CredentialAuthReplacement: - properties: - access_token: - minLength: 1 - type: string - x-nullable: true - expires_at: - type: string - x-nullable: true - refresh: - allOf: - - $ref: '#/definitions/v1.OAuthCredentialRefreshReplacement' - x-nullable: true - token: - minLength: 1 - type: string - type: - enum: - - static_bearer - - mcp_oauth - type: string - required: - - type - type: object - v1.CredentialDeleted: - properties: - deleted: - type: boolean - id: - type: string - object: - enum: - - vault.credential.deleted - type: string - required: - - deleted - - id - - object - type: object - v1.CredentialList: - properties: - data: - items: - $ref: '#/definitions/v1.Credential' - type: array - first_id: - type: string - x-nullable: true - has_more: - type: boolean - last_id: - type: string - x-nullable: true - object: - enum: - - list - type: string - required: - - data - - has_more - - object - type: object - v1.Environment: - properties: - capability_directories: - items: - type: string - type: array - x-nullable: true - env: - additionalProperties: - type: string - type: object - x-nullable: true - environment_template_id: - type: string - files: - items: - type: object - type: array - x-nullable: true - network: - allOf: - - $ref: '#/definitions/v1.EnvironmentNetworkInput' - x-nullable: true - packages: - allOf: - - $ref: '#/definitions/v1.EnvironmentPackages' - x-nullable: true - plugins: - items: - type: object - type: array - x-nullable: true - setup_commands: - items: - type: object - type: array - x-nullable: true - skills: - items: - type: object - type: array - x-nullable: true - type: - enum: - - none - - self_hosted - - openai_hosted - type: string - workspace_directory: - type: string - required: - - type - type: object - v1.EnvironmentFile: - properties: - environment_id: - type: string - object: - enum: - - agent.environment.file - type: string - path: - type: string - size_bytes: - minimum: 0 - type: integer - required: - - environment_id - - object - - path - - size_bytes - type: object - v1.EnvironmentFileCreateRequest: - properties: - data: - type: string - file_id: - type: string - path: - type: string - type: - enum: - - inline - - file_id - type: string - required: - - path - - type - type: object - v1.EnvironmentFileList: - properties: - data: - items: - $ref: '#/definitions/v1.EnvironmentFile' - type: array - has_more: - type: boolean - next: - type: string - x-nullable: true - object: - enum: - - page - type: string - required: - - data - - has_more - - object - type: object - v1.EnvironmentInfo: - properties: - files: - items: - type: object - type: array - id: - type: string - object: - enum: - - agent.environment - type: string - plugins: - items: - type: object - type: array - skills: - items: - type: object - type: array - status: - enum: - - pending - - connected - - disconnected - - expired - - failed - type: string - type: - enum: - - openai_hosted - - self_hosted - type: string - required: - - files - - id - - object - - plugins - - skills - - status - - type - type: object - v1.EnvironmentInstallation: - properties: - commands: - additionalProperties: - type: string - type: object - expires_at: - type: integer - message: - type: string - status: - enum: - - available - - unavailable - type: string - version: - type: string - type: object - v1.EnvironmentNetwork: - properties: - access: - enum: - - enabled - - disabled - - restricted - type: string - allowed_domains: - items: - type: string - type: array - required: - - access - - allowed_domains - type: object - v1.EnvironmentNetworkInput: - properties: - access: - enum: - - enabled - - disabled - - restricted - type: string - allowed_domains: - items: - type: string - type: array - x-nullable: true - required: - - access - type: object - v1.EnvironmentPackages: - properties: - npm: - items: - type: string - type: array - python: - items: - type: string - type: array - required: - - npm - - python - type: object - v1.EnvironmentPackagesInput: - properties: - npm: - items: - type: string - type: array - x-nullable: true - python: - items: - type: string - type: array - x-nullable: true - type: object - v1.EnvironmentPackagesResponse: - properties: - npm: - items: - type: string - type: array - python: - items: - type: string - type: array - system: - items: - type: string - type: array - required: - - npm - - python - - system - type: object - v1.EnvironmentTemplate: - properties: - capability_directories: - items: - type: string - type: array - created_at: - type: integer - files: - items: - type: object - type: array - id: - type: string - name: - type: string - x-nullable: true - network: - $ref: '#/definitions/v1.EnvironmentNetwork' - object: - enum: - - agent.environment.template - type: string - packages: - $ref: '#/definitions/v1.EnvironmentPackagesResponse' - plugins: - items: - type: object - type: array - skills: - items: - type: object - type: array - updated_at: - type: integer - required: - - capability_directories - - created_at - - files - - id - - network - - object - - packages - - plugins - - skills - - updated_at - type: object - v1.EnvironmentTemplateDeleted: - properties: - deleted: - type: boolean - id: - type: string - object: - enum: - - agent.environment.template.deleted - type: string - required: - - deleted - - id - - object - type: object - v1.EnvironmentTemplateList: - properties: - data: - items: - $ref: '#/definitions/v1.EnvironmentTemplate' - type: array - first_id: - type: string - x-nullable: true - has_more: - type: boolean - last_id: - type: string - x-nullable: true - object: - enum: - - list - type: string - required: - - data - - has_more - - object - type: object - v1.EnvironmentTemplateRequest: - properties: - capability_directories: - items: - type: string - type: array - x-nullable: true - env: - additionalProperties: - type: string - type: object - x-nullable: true - files: - items: - type: object - type: array - x-nullable: true - name: - type: string - x-nullable: true - network: - allOf: - - $ref: '#/definitions/v1.EnvironmentNetworkInput' - x-nullable: true - packages: - allOf: - - $ref: '#/definitions/v1.EnvironmentPackagesInput' - x-nullable: true - plugins: - items: - type: object - type: array - x-nullable: true - setup_commands: - items: - type: object - type: array - x-nullable: true - skills: - items: - type: object - type: array - x-nullable: true - type: object - v1.ErrorResponse: - properties: - error: - $ref: '#/definitions/v1.APIError' - required: - - error - type: object - v1.InlineAgent: - properties: - instructions: - type: string - x-nullable: true - model: - type: string - multi_agent: - type: object - x-nullable: true - reasoning: - allOf: - - $ref: '#/definitions/v1.Reasoning' - x-nullable: true - service_tier: - enum: - - auto - - default - - flex - - priority - - fast - type: string - x-nullable: true - text: - allOf: - - $ref: '#/definitions/v1.SavedAgentTextInput' - x-nullable: true - tools: - items: - type: object - type: array - x-nullable: true - x_agents_core: - allOf: - - $ref: '#/definitions/v1.AgentsCore' - x-nullable: true - type: object - v1.InputContent: - properties: - image_url: - type: string - text: - type: string - type: - enum: - - input_text - - input_image - type: string - required: - - type - type: object - v1.InputMessage: - properties: - content: - items: - $ref: '#/definitions/v1.InputContent' - type: array - role: - enum: - - user - type: string - type: - enum: - - message - type: string - required: - - content - - role - type: object - v1.InputTokenDetails: - properties: - cached_tokens: - type: integer - required: - - cached_tokens - type: object - v1.Item: - properties: - action: - $ref: '#/definitions/v1.WebSearchAction' - agent_id: - type: string - arguments: {} - call_id: - type: string - command: - type: string - content: - items: - $ref: '#/definitions/v1.ItemContent' - type: array - cwd: - type: string - duration_ms: - type: integer - error: {} - exit_code: - type: integer - id: - type: string - model: - type: string - name: - type: string - output: {} - phase: - enum: - - commentary - - final_answer - type: string - x-nullable: true - reasoning_effort: - type: string - recipient_agent_id: - type: string - recipient_agent_ids: - items: - type: string - type: array - role: - enum: - - user - - assistant - type: string - sender_agent_id: - type: string - server_label: - type: string - status: - enum: - - in_progress - - completed - - failed - - incomplete - type: string - summary: - items: - $ref: '#/definitions/v1.SummaryText' - type: array - turn_id: - type: string - type: - enum: - - message - - command_execution - - mcp_call - - function_call - - function_call_output - - web_search_call - - reasoning - - agent_message - - create_subagent_call - - send_subagent_input_call - - resume_subagent_call - - wait_for_subagents_call - - interrupt_subagent_call - - close_subagent_call - type: string - required: - - id - - turn_id - - type - type: object - v1.ItemContent: - properties: - encrypted_content: - type: string - image_url: - type: string - text: - type: string - type: - enum: - - input_text - - output_text - - input_image - - encrypted_content - type: string - required: - - type - type: object - v1.ItemList: - properties: - data: - items: - $ref: '#/definitions/v1.Item' - type: array - first_id: - type: string - x-nullable: true - has_more: - type: boolean - last_id: - type: string - x-nullable: true - object: - enum: - - list - type: string - required: - - data - - has_more - - object - type: object - v1.ModelProviderInput: - properties: - api_key: - type: string - base_url: - type: string - context_window: - type: integer - max_output_tokens: - type: integer - protocol: - enum: - - anthropic - - responses - - chat_completions - type: string - required: - - api_key - - base_url - - protocol - type: object - v1.ModelProviderView: - properties: - api_key_configured: - type: boolean - base_url: - type: string - context_window: - type: integer - max_output_tokens: - type: integer - protocol: - enum: - - anthropic - - responses - - chat_completions - type: string - required: - - api_key_configured - - base_url - - protocol - type: object - v1.MultiAgentConfig: - properties: - enabled: - type: boolean - max_concurrent_subagents: - type: integer - x-nullable: true - required: - - enabled - type: object - v1.OAuthCredentialRefresh: - properties: - client_id: - type: string - resource: - type: string - x-nullable: true - scope: - type: string - x-nullable: true - token_endpoint: - type: string - token_endpoint_auth: - $ref: '#/definitions/v1.OAuthEndpointAuth' - required: - - client_id - - token_endpoint - - token_endpoint_auth - type: object - v1.OAuthCredentialRefreshInput: - properties: - client_id: - type: string - refresh_token: - type: string - resource: - type: string - x-nullable: true - scope: - type: string - x-nullable: true - token_endpoint: - type: string - token_endpoint_auth: - $ref: '#/definitions/v1.OAuthEndpointAuthInput' - required: - - client_id - - refresh_token - - token_endpoint - - token_endpoint_auth - type: object - v1.OAuthCredentialRefreshReplacement: - properties: - refresh_token: - type: string - x-nullable: true - scope: - type: string - x-nullable: true - token_endpoint_auth: - allOf: - - $ref: '#/definitions/v1.OAuthEndpointAuthReplacement' - x-nullable: true - type: object - v1.OAuthEndpointAuth: - properties: - type: - enum: - - none - - client_secret_basic - - client_secret_post - type: string - required: - - type - type: object - v1.OAuthEndpointAuthInput: - properties: - client_secret: - type: string - type: - enum: - - none - - client_secret_basic - - client_secret_post - type: string - required: - - type - type: object - v1.OAuthEndpointAuthReplacement: - properties: - client_secret: - type: string - x-nullable: true - type: - enum: - - client_secret_basic - - client_secret_post - type: string - required: - - type - type: object - v1.OutputTokenDetails: - properties: - reasoning_tokens: - type: integer - required: - - reasoning_tokens - type: object - v1.Reasoning: - properties: - effort: - type: string - x-nullable: true - summary: - type: string - x-nullable: true - type: object - v1.RequiredAction: - properties: - arguments: {} - call_id: - type: string - environment_id: - type: string - name: - type: string - turn_id: - type: string - type: - enum: - - function_call - - environment_connection - type: string - required: - - type - type: object - v1.SavedAgent: - properties: - created_at: - type: integer - id: - type: string - instructions: - type: string - x-nullable: true - metadata: - additionalProperties: - type: string - type: object - model: - type: string - multi_agent: - $ref: '#/definitions/v1.MultiAgentConfig' - name: - type: string - x-nullable: true - object: - enum: - - agent - type: string - reasoning: - $ref: '#/definitions/v1.Reasoning' - service_tier: - enum: - - auto - - default - - flex - - priority - - fast - type: string - text: - $ref: '#/definitions/v1.SavedAgentText' - tools: - items: - type: object - type: array - updated_at: - type: integer - x_agents_core: - allOf: - - $ref: '#/definitions/v1.SavedAgentCore' - x-nullable: true - required: - - created_at - - id - - metadata - - model - - multi_agent - - object - - reasoning - - service_tier - - text - - tools - - updated_at - type: object - v1.SavedAgentCore: - properties: - harness: - enum: - - claude_sdk - - codex - - mcode - type: string - harness_config: - type: object - model_provider: - $ref: '#/definitions/v1.ModelProviderView' - type: object - v1.SavedAgentCoreInput: - properties: - harness: - enum: - - claude_sdk - - codex - - mcode - type: string - harness_config: - type: object - model_provider: - allOf: - - $ref: '#/definitions/v1.ModelProviderInput' - x-nullable: true - type: object - v1.SavedAgentList: - properties: - data: - items: - $ref: '#/definitions/v1.SavedAgent' - type: array - first_id: - type: string - x-nullable: true - has_more: - type: boolean - last_id: - type: string - x-nullable: true - object: - enum: - - list - type: string - required: - - data - - has_more - - object - type: object - v1.SavedAgentText: - properties: - format: - $ref: '#/definitions/v1.SavedAgentTextFormat' - verbosity: - enum: - - low - - medium - - high - type: string - required: - - format - - verbosity - type: object - v1.SavedAgentTextFormat: - properties: - schema: - type: object - type: - enum: - - text - - json_schema - type: string - required: - - type - type: object - v1.SavedAgentTextInput: - properties: - format: - type: object - x-nullable: true - verbosity: - enum: - - low - - medium - - high - type: string - x-nullable: true - type: object - v1.Session: - properties: - agent: - $ref: '#/definitions/v1.Agent' - created_at: - type: integer - environment: - $ref: '#/definitions/v1.SessionEnvironment' - error: - type: string - x-nullable: true - id: - type: string - last_active_at: - type: integer - metadata: - additionalProperties: - type: string - type: object - object: - enum: - - agent.session - type: string - required_actions: - items: - $ref: '#/definitions/v1.RequiredAction' - type: array - status: - enum: - - idle - - in_progress - - requires_action - - failed - type: string - usage: - allOf: - - $ref: '#/definitions/v1.TokenUsage' - x-nullable: true - vault_ids: - items: - type: string - type: array - x_agents_core: - $ref: '#/definitions/v1.SessionCore' - required: - - agent - - created_at - - environment - - id - - last_active_at - - metadata - - object - - required_actions - - status - - vault_ids - type: object - v1.SessionArtifact: - properties: - created_at: - type: integer - environment_id: - type: string - id: - type: string - object: - enum: - - agent.session.artifact - type: string - path: - type: string - session_id: - type: string - size_bytes: - type: integer - turn_id: - type: string - required: - - created_at - - environment_id - - id - - object - - path - - session_id - - size_bytes - - turn_id - type: object - v1.SessionArtifactDeleted: - properties: - deleted: - type: boolean - id: - type: string - object: - enum: - - agent.session.artifact.deleted - type: string - required: - - deleted - - id - - object - type: object - v1.SessionArtifactList: - properties: - data: - items: - $ref: '#/definitions/v1.SessionArtifact' - type: array - first_id: - type: string - x-nullable: true - has_more: - type: boolean - last_id: - type: string - x-nullable: true - object: - enum: - - list - type: string - required: - - data - - has_more - - object - type: object - v1.SessionCore: - properties: - installation: - $ref: '#/definitions/v1.EnvironmentInstallation' - type: object - v1.SessionDeleted: - properties: - deleted: - type: boolean - id: - type: string - object: - enum: - - agent.session.deleted - type: string - required: - - deleted - - id - - object - type: object - v1.SessionEnvironment: - properties: - capability_directories: - items: - type: string - type: array - files: - items: - type: object - type: array - id: - type: string - network: - $ref: '#/definitions/v1.EnvironmentNetwork' - packages: - $ref: '#/definitions/v1.EnvironmentPackagesResponse' - plugins: - items: - type: object - type: array - remote_url: - type: string - skills: - items: - type: object - type: array - type: - enum: - - none - - self_hosted - - openai_hosted - type: string - workspace_directory: - type: string - required: - - type - type: object - v1.SessionEnvironmentState: - properties: - error: - allOf: - - $ref: '#/definitions/v1.StreamError' - x-nullable: true - id: - type: string - status: - enum: - - pending - - ready - - connected - - disconnected - - failed - type: string - type: - type: string - required: - - id - - status - - type - type: object - v1.SessionEvent: - properties: - content_index: - type: integer - delta: - type: string - environment: - $ref: '#/definitions/v1.SessionEnvironmentState' - error: - $ref: '#/definitions/v1.StreamError' - event_id: - type: string - item: - $ref: '#/definitions/v1.Item' - item_id: - type: string - output_index: - type: integer - x-nullable: true - part: - $ref: '#/definitions/v1.ItemContent' - session: - $ref: '#/definitions/v1.Session' - session_id: - type: string - subagent: - $ref: '#/definitions/v1.Subagent' - text: - type: string - turn: - $ref: '#/definitions/v1.Turn' - turn_id: - type: string - type: - type: string - usage: - allOf: - - $ref: '#/definitions/v1.TokenUsage' - description: |- - Usage is present only on terminal Turn events, where it mirrors the Turn - snapshot and is null when unknown. Other events omit it. - x-nullable: true - required: - - event_id - - type - type: object - v1.SessionExecutionInput: - properties: - environment: - description: Environment supplies placement-independent preparation through - the Core extension. - type: object - harness_config: - type: object - model_provider: - $ref: '#/definitions/v1.ModelProviderInput' - type: object - v1.SessionInput: - properties: - call_id: - type: string - error: - type: string - x-nullable: true - input: - items: - $ref: '#/definitions/v1.InputMessage' - type: array - output: - x-nullable: true - success: - type: boolean - turn_id: - type: string - type: - enum: - - agent.session.input.message - - agent.session.input.cancel - - agent.session.input.tool_result - type: string - required: - - type - type: object - v1.SessionList: - properties: - data: - items: - $ref: '#/definitions/v1.Session' - type: array - first_id: - type: string - x-nullable: true - has_more: - type: boolean - last_id: - type: string - x-nullable: true - object: - enum: - - list - type: string - required: - - data - - has_more - - object - type: object - v1.Skill: - properties: - created_at: - type: integer - default_version: - type: string - description: - type: string - id: - type: string - latest_version: - type: string - name: - type: string - object: - enum: - - skill - type: string - required: - - created_at - - default_version - - description - - id - - latest_version - - name - - object - type: object - v1.SkillDeleted: - properties: - deleted: - type: boolean - id: - type: string - object: - enum: - - skill.deleted - type: string - required: - - deleted - - id - - object - type: object - v1.SkillList: - properties: - data: - items: - $ref: '#/definitions/v1.Skill' - type: array - first_id: - type: string - x-nullable: true - has_more: - type: boolean - last_id: - type: string - x-nullable: true - object: - enum: - - list - type: string - required: - - data - - has_more - - object - type: object - v1.SkillUpdateRequest: - properties: - default_version: - type: string - required: - - default_version - type: object - v1.SkillVersion: - properties: - created_at: - type: integer - description: - type: string - id: - type: string - name: - type: string - object: - enum: - - skill.version - type: string - skill_id: - type: string - version: - type: string - required: - - created_at - - description - - id - - name - - object - - skill_id - - version - type: object - v1.SkillVersionDeleted: - properties: - deleted: - type: boolean - id: - type: string - object: - enum: - - skill.version.deleted - type: string - version: - type: string - required: - - deleted - - id - - object - - version - type: object - v1.SkillVersionList: - properties: - data: - items: - $ref: '#/definitions/v1.SkillVersion' - type: array - first_id: - type: string - x-nullable: true - has_more: - type: boolean - last_id: - type: string - x-nullable: true - object: - enum: - - list - type: string - required: - - data - - has_more - - object - type: object - v1.SourceFile: - properties: - bytes: - minimum: 0 - type: integer - created_at: - type: integer - expires_at: - type: integer - x-nullable: true - filename: - type: string - id: - type: string - object: - enum: - - file - type: string - purpose: - enum: - - user_data - type: string - status: - enum: - - processed - type: string - status_details: - type: string - x-nullable: true - required: - - bytes - - created_at - - filename - - id - - object - - purpose - - status - type: object - v1.SourceFileDeleted: - properties: - deleted: - type: boolean - id: - type: string - object: - enum: - - file - type: string - required: - - deleted - - id - - object - type: object - v1.SourceFileList: - properties: - data: - items: - $ref: '#/definitions/v1.SourceFile' - type: array - first_id: - type: string - x-nullable: true - has_more: - type: boolean - last_id: - type: string - x-nullable: true - object: - enum: - - list - type: string - required: - - data - - has_more - - object - type: object - v1.StreamError: - properties: - code: - type: string - message: - type: string - param: - description: |- - Param is the pinned SessionError field. An error SessionEvent always - carries it, null when unset; Environment state errors and Core's own - stream_interrupted frame omit it. - type: string - x-nullable: true - type: - type: string - type: object - v1.Subagent: - properties: - closed_at: - type: integer - x-nullable: true - id: - type: string - instructions: - items: - $ref: '#/definitions/v1.AgentContent' - type: array - x-nullable: true - name: - type: string - x-nullable: true - object: - enum: - - agent.session.subagent - type: string - opened_at: - type: integer - parent_agent_id: - type: string - session_id: - type: string - status: - enum: - - active - - closed - type: string - required: - - id - - object - - opened_at - - parent_agent_id - - session_id - - status - type: object - v1.SubagentList: - properties: - data: - items: - $ref: '#/definitions/v1.Subagent' - type: array - first_id: - type: string - x-nullable: true - has_more: - type: boolean - last_id: - type: string - x-nullable: true - object: - enum: - - list - type: string - required: - - data - - has_more - - object - type: object - v1.SummaryText: - properties: - text: - type: string - type: - enum: - - summary_text - type: string - required: - - text - - type - type: object - v1.TextConfig: - properties: - format: - $ref: '#/definitions/v1.TextFormat' - verbosity: - enum: - - low - - medium - - high - type: string - required: - - format - - verbosity - type: object - v1.TextFormat: - properties: - schema: - type: object - type: - enum: - - text - - json_schema - type: string - required: - - type - type: object - v1.TokenUsage: - properties: - input_tokens: - type: integer - input_tokens_details: - $ref: '#/definitions/v1.InputTokenDetails' - output_tokens: - type: integer - output_tokens_details: - $ref: '#/definitions/v1.OutputTokenDetails' - total_tokens: - type: integer - required: - - input_tokens - - input_tokens_details - - output_tokens - - output_tokens_details - - total_tokens - type: object - v1.Turn: - properties: - agent_id: - type: string - completed_at: - type: integer - x-nullable: true - created_at: - type: integer - error: - allOf: - - $ref: '#/definitions/v1.TurnError' - x-nullable: true - id: - type: string - object: - enum: - - agent.session.turn - type: string - session_id: - type: string - started_at: - type: integer - x-nullable: true - status: - enum: - - queued - - in_progress - - waiting - - completed - - failed - - cancelled - type: string - subagent_id: - type: string - x-nullable: true - usage: - allOf: - - $ref: '#/definitions/v1.TokenUsage' - x-nullable: true - required: - - agent_id - - created_at - - id - - object - - session_id - - status - type: object - v1.TurnError: - properties: - code: - enum: - - context_length_exceeded - - session_budget_exceeded - - usage_limit_exceeded - - rate_limit_exceeded - - server_overloaded - - cyber_policy - - connection_failed - - server_error - - authentication_error - - invalid_request - - resource_not_found - - sandbox_error - - executor_version_incompatible - - active_turn_not_steerable - - request_timeout - - internal_error - type: string - message: - type: string - required: - - code - - message - type: object - v1.TurnList: - properties: - data: - items: - $ref: '#/definitions/v1.Turn' - type: array - first_id: - type: string - x-nullable: true - has_more: - type: boolean - last_id: - type: string - x-nullable: true - object: - enum: - - list - type: string - required: - - data - - has_more - - object - type: object - v1.UpdateAgentRequest: - properties: - instructions: - type: string - x-nullable: true - metadata: - additionalProperties: - type: string - type: object - x-nullable: true - model: - type: string - multi_agent: - type: object - x-nullable: true - name: - maxLength: 128 - type: string - x-nullable: true - reasoning: - allOf: - - $ref: '#/definitions/v1.Reasoning' - x-nullable: true - service_tier: - enum: - - auto - - default - - flex - - priority - - fast - type: string - x-nullable: true - text: - allOf: - - $ref: '#/definitions/v1.SavedAgentTextInput' - x-nullable: true - tools: - items: - type: object - type: array - x-nullable: true - x_agents_core: - allOf: - - $ref: '#/definitions/v1.SavedAgentCoreInput' - x-nullable: true - type: object - v1.UpdateCredentialRequest: - properties: - auth: - $ref: '#/definitions/v1.CredentialAuthReplacement' - required: - - auth - type: object - v1.UpdateSessionRequest: - properties: - metadata: - additionalProperties: - type: string - type: object - x-nullable: true - required: - - metadata - type: object - v1.Vault: - properties: - created_at: - type: integer - id: - type: string - metadata: - additionalProperties: - type: string - type: object - name: - type: string - x-nullable: true - object: - enum: - - vault - type: string - required: - - created_at - - id - - metadata - - object - type: object - v1.VaultDeleted: - properties: - deleted: - type: boolean - id: - type: string - object: - enum: - - vault.deleted - type: string - required: - - deleted - - id - - object - type: object - v1.VaultList: - properties: - data: - items: - $ref: '#/definitions/v1.Vault' - type: array - first_id: - type: string - x-nullable: true - has_more: - type: boolean - last_id: - type: string - x-nullable: true - object: - enum: - - list - type: string - required: - - data - - has_more - - object - type: object - v1.WebSearchAction: - properties: - pattern: - type: string - queries: - items: - type: string - type: array - query: - type: string - type: - enum: - - search - - open_page - - find_in_page - - other - type: string - url: - type: string - required: - - type - type: object -info: - contact: {} - description: Supported single-Agent execution resources from the pinned openai-python - beta/agents contract. Bearer keys bind an execution principal to one project; - optional OpenAI-Organization and OpenAI-Project headers must match that binding. - license: - name: Apache 2.0 - url: https://www.apache.org/licenses/LICENSE-2.0.html - title: OpenAgentCore Agents API - version: "1" -paths: - /agents: - get: - description: Lists only the authenticated tenant's saved Agents, independently - of Sessions. Limit 0 is treated as 1 and larger limits as 100, as observed - on the hosted service. The local default is 20; exact upstream default/cap - and empty cursor fields remain unverified. An unknown, malformed or foreign - after cursor returns not found. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Last Agent ID from the previous page - in: query - name: after - type: string - - default: 20 - description: Page size; 0 is treated as 1 and values above 100 as 100 - in: query - minimum: 0 - name: limit - type: integer - - default: desc - description: Creation order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SavedAgentList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List reusable Agents - tags: - - Agents - post: - consumes: - - application/json - description: 'Persists configuration independently of execution. Names over - 128 characters and metadata outside 16 string pairs with 64-character keys - and 512-character values return invalid_request_error with the official param; - U+0000 in stored strings is rejected as a local storage limit. As on every - Agents API JSON route, a non-JSON Content-Type, invalid UTF-8, malformed JSON, - a repeated key at any depth or a non-object root returns invalid_request_error - with a null param and the official message before other checks; an empty or - null body is {}. Missing, unknown, wrongly typed or unsupported enum members - of the pinned configuration shapes (tools, text, reasoning, service_tier, - multi_agent) return invalid_request_error with the JSON path as param; duplicate - function names, repeated web_search or tool_search and non-object schema root - types return it with a null param. Supports model/name/instructions/metadata, - explicit reasoning and service tiers, multi_agent, text/json_schema, function/tool_search/programmatic_tool_calling/web_search - and HTTP MCP with nullable credential_id, service origin (omitted or null - on HTTP transport is saved as service) and boolean required defaulting to - false. Saving credential_id grants no access: Session admission checks attached - Vault ownership and destination. MCP allowed_tools preserves null versus empty; - saved HTTP transport includes empty headers. Model-derived reasoning defaults, - other MCP variants and public retry conformance remain incomplete. web_search - saves every pinned mode: omitted or null mode is saved as live and omitted - or null context_size as medium; allowed_domains preserves null versus empty - and a present location, including {}, includes all four keys with null for - omitted ones, as observed officially (req_db41d2f6261b4abfb69465eafe719ab5, - req_165d53b88445490b9146d8272c54134d). Session execution accepts only explicit - disabled web_search and disabled programmatic_tool_calling through qualified - Runtime controls; saved enabled forms reject at Session admission. Session - execution admits only its supported configuration subset.' - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Reusable Agent configuration - in: body - name: body - required: true - schema: - $ref: '#/definitions/v1.CreateAgentRequest' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/v1.SavedAgent' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Create a reusable Agent - tags: - - Agents - /agents/{agent_id}: - delete: - description: Deletes only the authenticated tenant's saved configuration. Existing - Session snapshots, history and recorded creation retry identities remain independent. - Missing and repeated deletion locally return404; exact hosted error and in-flight - creation/deletion semantics remain unverified. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Agent ID - in: path - name: agent_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.AgentDeleted' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Delete a reusable Agent - tags: - - Agents - get: - description: Reads the saved resource owned by the authenticated tenant, independently - of execution Sessions. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Agent ID - in: path - name: agent_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SavedAgent' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Retrieve a reusable Agent - tags: - - Agents - post: - consumes: - - application/json - description: Preserves omitted fields and replaces supplied fields using shared - saved-configuration validation. Null name/instructions clear; null or empty - metadata clears all pairs. Name, metadata and configuration validation errors - return invalid_request_error with the official param, using the Agent create - rules before the Agent lookup. Existing Session snapshots are unchanged. Empty - updates advance updated_at without changing saved fields. Nested replacement/null - defaults, model-derived reasoning and exact hosted error behavior remain incompletely - verified. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Agent ID - in: path - name: agent_id - required: true - type: string - - description: Supplied reusable Agent fields - in: body - name: body - required: true - schema: - $ref: '#/definitions/v1.UpdateAgentRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SavedAgent' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Update a reusable Agent - tags: - - Agents - /agents/environments/{environment_id}: - get: - description: Returns durable connection status and safe installed metadata for - supported self_hosted and basic openai_hosted profiles. Initial files expose - frozen safe metadata without content; Plugin/Skill entries expose only safe - configured installation metadata. Capability-directory discoveries are not - added to those arrays. Unsupported installation configurations remain implementation - gaps. This read does not prepare execution, start compute or require an enabled - execution worker. Session deletion removes the associated Environment from - public reads; project-shared read authorization is unchanged. Connection status - does not prove native readiness or process quiescence. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Environment ID - in: path - name: environment_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.EnvironmentInfo' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Retrieve an execution Environment - tags: - - Environments - /agents/environments/{environment_id}/files: - get: - description: Lists direct regular files in one authorized self_hosted or qualified - local workspace directory. Local paths use the public /workspace root and - must be in cleaned form. This partial implementation defaults to the workspace - root and limit 20; recursive scope and these defaults are not verified upstream - semantics. A missing path, a regular file or a symbolic link returns an empty - page; links are never followed. Daemons without a local workspace binding - use the Claude SDK adapter reader, which keeps 404 for a missing path and - 503 for a regular file or symbolic link. Well-formed unknown query keys are - ignored; malformed query encoding and a repeated supported key are rejected. - Sorts by case-sensitive path components, descending by default. Keep the same - path, order and limit when using page. Each page rereads the complete bounded - directory; changed file paths/sizes invalidate continuation locally with 400. - There is no snapshot guarantee. An openai_hosted Environment that has not - connected yet returns 400. Truncated or uncertain native results fail with - 503 without returning a partial page. This read never starts a Turn or admits - model input. Actual transport disconnect/reconnect events remain observable. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Environment ID - in: path - name: environment_id - required: true - type: string - - description: Absolute directory in cleaned form inside /workspace - in: query - name: path - type: string - - description: Maximum file count; local default 20 - in: query - maximum: 100 - minimum: 1 - name: limit - type: integer - - default: desc - description: Case-sensitive path-component order; omit for descending, explicit - empty values are invalid - enum: - - asc - - desc - in: query - name: order - type: string - - description: Opaque continuation token; keep path, order and limit unchanged - in: query - name: page - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.EnvironmentFileList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List live Environment files - tags: - - Environments - post: - consumes: - - application/json - description: Uploads standard Base64 bytes to a file beneath /workspace in a - qualified local Environment and returns 201. Accepts inline bytes or a project-owned - source file_id through the same write path. Unknown body fields are rejected - with their name as param. Basic public hosted creation requires explicit managed - Runtime configuration; an openai_hosted Environment that has not connected - yet returns 400. Inline data is limited to 5 MiB decoded and a file_id copy - to 50 MiB. Missing parent directories are created with mode 0700 and the file - with mode 0600. An existing destination is never replaced; a directory, an - existing file or a path through a symlink or non-directory returns 400. Idle - writes exclude execution. Missing receipts return unavailable and retain a - durable mutation gate without automatic replay. Error/timing parity with upstream - remains unverified. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Environment ID - in: path - name: environment_id - required: true - type: string - - description: Inline bytes or source file ID and absolute workspace path - in: body - name: request - required: true - schema: - $ref: '#/definitions/v1.EnvironmentFileCreateRequest' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/v1.EnvironmentFile' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "409": - description: Conflict - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Create an Environment file from inline bytes or a source file - tags: - - Environments - /agents/environments/templates: - get: - description: Lists tenant-owned safe template metadata in creation order with - ID tie-breaking. Defaults to limit 20 and descending order; limit 0 is treated - as 1 and larger limits as 100. Foreign, missing and malformed cursors return - the same not found error. Concurrent-page and exact hosted error behavior - remain unverified. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Previous Template ID - in: query - name: after - type: string - - default: 20 - description: Page size; 0 is treated as 1 and values above 100 as 100 - in: query - minimum: 0 - name: limit - type: integer - - default: desc - description: Creation order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.EnvironmentTemplateList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List Environment Templates - tags: - - Environment Templates - post: - consumes: - - application/json - description: Saves tenant-owned hosted configuration. Supports nullable name, - enabled/disabled or exact-domain restricted network, initial inline/file_id - files, confidential env, ordered setup_commands, npm/Python packages inline/referenced - Skill ZIPs, Plugin ZIPs and workspace-contained capability directories. Omitted/null - network defaults to enabled. Restricted network requires 1–100 exact ASCII - hostnames; other host forms and populated unsupported installations are rejected - before persistence without echoing input. Network policy rejections return - invalid_request_error with a null param. System dependencies must be preinstalled - in the sandbox image or template, or on the host machine; packages.system - is rejected. No compute is allocated. Exact hosted error/retry semantics remain - unverified. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Reusable configuration - in: body - name: body - required: true - schema: - $ref: '#/definitions/v1.EnvironmentTemplateRequest' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/v1.EnvironmentTemplate' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Create an Environment Template - tags: - - Environment Templates - /agents/environments/templates/{environment_template_id}: - delete: - description: Deletes the tenant-owned reusable configuration without changing - or deleting existing Sessions and their frozen configuration. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Template ID - in: path - name: environment_template_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.EnvironmentTemplateDeleted' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Delete an Environment Template - tags: - - Environment Templates - get: - description: Returns safe tenant-owned configuration metadata without allocating - compute. Missing and foreign resources return the same not-found response. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Template ID - in: path - name: environment_template_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.EnvironmentTemplate' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Retrieve an Environment Template - tags: - - Environment Templates - post: - consumes: - - application/json - description: Supplied fields replace atomically; omitted fields remain unchanged. - Null name clears and null network resets to the pinned enabled default. Existing - Session snapshots and creation retries remain unchanged. Initial files replace - as a list; null/empty clears. File data is encrypted separately and excluded - from response metadata. Skills replace as a list; null/empty clears. Skill - archives are encrypted separately and omitted from responses. Plugins and - capability directories replace as lists; null/empty clears. Plugin archives - are encrypted and omitted from responses. Capability directories are snapshotted - after setup. Environment MCP execution requires a qualified native transport - and runtime network policy. Empty updates advance updated_at without changing - saved fields or confidential contents. Network policy rejections return invalid_request_error - with a null param. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Template ID - in: path - name: environment_template_id - required: true - type: string - - description: Configuration replacements - in: body - name: body - required: true - schema: - $ref: '#/definitions/v1.EnvironmentTemplateRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.EnvironmentTemplate' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Update an Environment Template - tags: - - Environment Templates - /agents/sessions: - get: - description: Cursor and results are scoped to the authenticated execution tenant; - an unknown, malformed or foreign after cursor returns not found. Optional - agent_id matches the immutable root Agent ID, including inline Agents and - historical Sessions whose saved source was updated or deleted. Omission lists - all Agents. Returns the same Environment and pending-input activity projection - as Session retrieval, including self_hosted Sessions. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Root Agent ID whose Sessions to return - in: query - name: agent_id - type: string - - description: Last Session ID from the previous page - in: query - name: after - type: string - - default: 20 - description: Page size; 0 is treated as 1 and values above 100 as 100 - in: query - minimum: 0 - name: limit - type: integer - - default: desc - description: Creation order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SessionList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List execution Sessions - tags: - - Sessions - post: - consumes: - - application/json - description: 'The optional Core model_provider bundle resolves from the Session - override, saved Agent defaults, then the deployment default of the resolved - harness, for every environment type. A Session that resolves no bundle returns - 400 model_provider_required with param x_agents_core.model_provider before - any write. Core encrypts and freezes the resolved bundle; later Agent or deployment - default edits and same-key retries cannot change it. Keys are never returned. - Supports inline configuration or a tenant-owned saved agent_id with per-Session - field replacements. Execution supports model/instructions, text verbosity, - non-deferred function tools, adapter-qualified multi_agent with persisted - Subagent reads, implicit reasoning, service tier auto and environment type - none, subject to the configured engine. Codex additionally supports HTTP MCP - with service origin (omitted or null on HTTP transport is saved as service), - native allowed_tools and boolean required defaulting to false. Session vault_ids - attach only project-owned Vaults; credential_id selects an attached static - bearer or OAuth credential for the exact HTTPS URL, while null/omission selects - a unique match or remains anonymous. Session reads, lists and event snapshots - show that implicitly selected credential ID in a null or omitted credential_id, - also after the credential is deleted; anonymous selections stay null and the - stored caller intent is unchanged. After the input requirement and before - any write, a credential_id without vault_ids, one outside the attached Vaults - (one message for missing, foreign and unattached IDs) or one for another server_url - returns 400 invalid_request_error, and several implicit matches return 409 - conflict_error. Missing decryption configuration fails dispatch without anonymous - fallback. Required initialization uses native startup before the first native - Turn, including cold resume, and requires a separately advertised capability; - exact hosted creation timing and error parity remain unverified. Explicit - environment-origin HTTP MCP is supported on managed and self-hosted workspaces - through the same Runtime bindings; native OAuth login remains unsupported. - The self_hosted profile uses a qualified native harness, a clean absolute - workspace_directory and optional absolute local capability_directories prepared - by Runtime, with optional non-deferred function tools and HTTP MCP using explicit - environment origin, optionally authenticated by the attached Vault rules. - Service-origin HTTP remains restricted to service-side environment:none. Remote - MCP and remote Bearer authentication each require separately advertised combination - support; old peers cannot receive unsupported work. Omitted/null capability_directories - use the empty-list default; self_hosted requires configured execution plus - executor registry. Claude SDK currently requires medium verbosity and object-root - function schemas. It supports anonymous or attached static-bearer service-origin - HTTP MCP on none with boolean required and separately advertised MCP/bearer/required - runtime support. Required servers must be connected before the first native - input is released; pending or failed startup rejects execution. The shared - Vault selection and immutable binding rules apply; unsupported native labels/tool - names reject before persistence. An attached Vault with no matching credential - may remain anonymous; missing keys or failed credential lookup/decryption - never fall back to anonymous execution. Omitted stream defaults to false; - stream and agent_id cannot be null. Metadata may be null; non-string values - and limit violations return invalid_request_error with a metadata or metadata. - param. The inline agent uses the Agent create configuration validation with - agent.-prefixed params, reported before the input requirement and saved-Agent - lookup; saved configurations with conflicting tools or schema roots reject - admission with the same errors, and execution limits keep unsupported_or_invalid_configuration. - Hosted network policy rejections return invalid_request_error with a null - param. Initial input accepts a string or ordered user-message array. Codex - and Claude SDK on none and qualified managed or self_hosted workspace profiles - also accept inline PNG/JPEG image content; other image combinations and remote - URLs are unsupported. None initial input atomically starts a Turn; self_hosted - initial input is reserved while returning its Environment connection target, - with execution deferred to native readiness and Session failure on initial - timeout. Initial input is required for none and for streamed creation outside - self_hosted. Omitted/null input remains valid for non-streaming hosted and - self_hosted creation. With stream=true, returns live Session events starting - with the committed creation snapshot and closes right after the first agent.session.idle - recorded when a Turn ends or an input reservation stops being pending, or - any agent.session.failed, without sending later events. A creation that admitted - nothing closes after the snapshot; a settlement that records no event closes - after events up to the cursor read with a settled Session projection. Required - actions keep it open; disconnect does not cancel execution. The GET events - stream remains live-only. New Sessions retain their authenticated creator; - all creation retries require the same typed subject, including across key - rotation. Every creation records caller intent before saved Agents, templates, - Vault credentials or deployment defaults resolve, so a same-intent retry returns - the committed Session independently of later changes to those resources. Provider - keys enter retry hashes only as fingerprints keyed by the credential key. - Unknown historical creators reject retries. These conflict policies are local - and not verified hosted parity. A same-key stream=true retry of an existing - creation returns 201 with no events and closes at once; retry with stream=false - or use the GET events stream to recover. Claude SDK on none, Core-managed - Docker openai_hosted and self_hosted supports qualified object-root json_schema - output with medium verbosity, single-Agent execution and ordinary functions. - Hosted execution reuses native workspace tools and Files/Artifacts; Skills, - Plugins, capability directories, HTTP MCP, Subagent and tool_search combinations - remain unqualified, including inherited template contents. Other non-text - initial input remains unsupported. The Claude workspace profile supports non-deferred - function tools with text or successful inline PNG/JPEG results alongside native - workspace tools; explicit environment-origin HTTP MCP uses the common Runtime - path. MiniMax accepts public environment-origin HTTP MCP only with null or - omitted allowed_tools and required=false; even an empty non-null allowlist - rejects. Idle Sessions provision automatically; initial provisioning has no - caller connection action. Network defaults to enabled; disabled and restricted - policies reject before compute allocation because the current Runtime cannot - enforce them. The x_agents_core.environment extension accepts common preparation - fields for either hosted or self-hosted placement: environment_template_id, - files, env, packages, setup_commands, skills, plugins and capability_directories. - Duplicate fields in environment and the extension reject. Confidential env, - npm/Python packages and ordered setup commands use the same Environment-owned - initialization lifecycle; compute allocation does not own preparation. Unknown - side effects are not replayed after disconnect or restart. System dependencies - must be preinstalled in the sandbox image or template, or on the host machine; - packages.system is rejected. Initial inline and tenant-owned file_id files - freeze encrypted bytes before provisioning, then install through the common - Core lifecycle before native execution or live Files access. With a template - reference, omitted/null files, env, packages and setup_commands inherit. Non-null - files and command lists replace; env overlays by key; each package manager - inherits on omission/null and otherwise replaces its list. Empty lists clear - their selected field. Tenant-owned environment_template_id references inherit - omitted/null network and allow only narrowing overrides. Inline hosted network:null - retains the enabled default; updating a Template with network:null resets - its saved policy to enabled. Core freezes effective configuration; template - updates/deletion do not alter Session snapshots or same-intent creation retries. - Inline or tenant-owned skill_reference Skills share initialization. Templates - preserve default/latest/explicit selectors; Session creation freezes concrete - metadata and encrypted content atomically. Skill, Plugin and capability-directory - list omission/null inherit; a non-null list replaces, including empty-list - clearing. Omitted/null Skill version selectors resolve the default version. - Source deletion/default updates cannot change committed Session Skill contents. - Deferred function discovery uses type-only tool_search and per-function defer_loading - in the qualified single-agent Claude function profile on none or a managed/user-owned - workspace, including qualified inline image messages and text results. Explicit - web_search mode disabled and programmatic_tool_calling enabled false use frozen - common Runtime controls. Enabled forms, including those saved on an Agent, - remain unqualified and reject before any write unless the Session replaces - tools. Omitted programmatic configuration preserves native behavior, a documented - difference from the official default-on behavior. Other combinations remain - unqualified; see the operation coverage.' - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Creation retry key, up to 128 bytes - in: header - name: Idempotency-Key - type: string - - description: Session configuration - in: body - name: body - required: true - schema: - $ref: '#/definitions/v1.CreateSessionRequest' - produces: - - application/json - - text/event-stream - responses: - "201": - description: Created - schema: - $ref: '#/definitions/v1.Session' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "409": - description: Conflict - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Create an execution Session - tags: - - Sessions - /agents/sessions/{session_id}: - delete: - description: Removes a durably idle or failed Session and its history from the - public API. A Session whose root Turn is queued, in progress or waiting (including - required actions) or whose input reservation is pending returns 409 conflict_error - and is left unchanged; cancel it and wait until it is idle before deleting. - Subagent child Turns and pending Environment file writes are not checked and - do not block deletion. Repeating the deletion of the caller's own deleted - Session returns the same confirmation; missing and foreign Sessions return - 404. Internal records and native history are retained pending separate physical - cleanup; overlapping stream timing remains unverified. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SessionDeleted' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "409": - description: Conflict - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Delete an execution Session - tags: - - Sessions - get: - description: Returns supported none, self_hosted and basic openai_hosted Session - environments. Self-hosted pending input can require a caller connection before - a Turn exists. Hosted initial provisioning remains idle until a Turn starts; - connection observations are not native execution readiness. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.Session' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Retrieve an execution Session - tags: - - Sessions - post: - consumes: - - application/json - description: The metadata field is required in an update body. Send null or - {} to clear it, or supply an object to replace all pairs. Up to 16 string - pairs, with keys at most 64 characters and values at most 512 characters; - violations and non-string values return invalid_request_error with a metadata - or metadata. param. U+0000 is rejected as a local storage limit. Malformed, - missing and foreign Session IDs share the not-found response. Execution configuration - and activity are unchanged. Returns the same safe Environment and pending-input - activity projection as Session retrieval. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Session metadata - in: body - name: body - required: true - schema: - $ref: '#/definitions/v1.UpdateSessionRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.Session' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Update execution Session metadata - tags: - - Sessions - /agents/sessions/{session_id}/artifacts: - get: - description: Lists published outputs independently of Environment availability. - Sorting uses publication time and ID. A later Turn publishes a path again - only when it is new, its bytes changed, or no Artifact remains for it. A malformed - environment_id matches nothing. An after value that is not an Artifact of - this Session, including a malformed one, returns 400 invalid_request_error - with the message "after is not a valid artifact ID". The local default page - size is 20; exact upstream defaults remain unverified. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Producing Environment ID; an unknown or malformed ID returns - an empty page - in: query - name: environment_id - type: string - - description: Last immutable artifact ID - in: query - name: after - type: string - - default: 20 - description: Page size - in: query - maximum: 100 - minimum: 1 - name: limit - type: integer - - default: desc - description: Publication order; omit for descending, explicit empty values - are invalid - enum: - - asc - - desc - in: query - name: order - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SessionArtifactList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List immutable Session artifacts - tags: - - Artifacts - /agents/sessions/{session_id}/artifacts/{artifact_id}: - delete: - description: Deletes the published copy without modifying its original workspace - file. Already admitted content reads may finish; later reads reject. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Artifact ID - in: path - name: artifact_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SessionArtifactDeleted' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Delete a published artifact - tags: - - Artifacts - get: - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Artifact ID - in: path - name: artifact_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SessionArtifact' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Retrieve immutable artifact metadata - tags: - - Artifacts - /agents/sessions/{session_id}/artifacts/{artifact_id}/content: - get: - description: Streams stored bytes after tenant and Session authorization, including - after Environment expiration. Exact upstream headers and Range behavior remain - unverified. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Artifact ID - in: path - name: artifact_id - required: true - type: string - produces: - - application/octet-stream - responses: - "200": - description: OK - schema: - type: file - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Download immutable artifact bytes - tags: - - Artifacts - /agents/sessions/{session_id}/events: - get: - description: |- - Live-only events, including command output fragments from capable Codex peers as agent.output.command_execution_output.delta with stable Item/output indexes. Native text conversion and output quotas apply; completion snapshots remain authoritative. Reconnect through Session, Turn and Items reads; missed events are not replayed. A lagging stream closes with an error when its bounded buffer is exceeded. When a hosted Environment fails to provision, the stream sends agent.session.environment.failed, an error event (environment_error/sandbox_error with the safe step and exit-status reason, never command output) and agent.session.failed, then ends. Session activity includes immutable pending-input connection actions before Turn creation; self_hosted environments use the same safe output as Session retrieval. - Active streams revalidate the original Project key every second before output; revocation, Project archival or authentication unavailability closes the stream. Authentication checks use a five-second timeout. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - produces: - - text/event-stream - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SessionEvent' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Stream live Session events - tags: - - Events - post: - consumes: - - application/json - description: An empty events array is a resource-authorized no-op; it creates - no execution retry identity, Turn, Item or input receipt. For environment - none, atomically accepts text messages, cancellation and function results. - Messages steer active work or start a queued Turn. Qualified Codex and Claude - SDK workspace profiles accept text and inline PNG/JPEG messages, independently - of managed or self_hosted ownership. Under the Session lock, matching retries - retain their original target; new active messages append to the current Turn, - while idle messages reserve work and wait up to the original five-minute connection/admission - deadline. Return 202 only after durable admission, without claiming native - application; active messages create no Turn or reservation. Cancellation-only - prepared-environment batches use existing durable cancellation admission and - return 202 without waiting for native exit; a new cancellation conflicts while - a pre-Turn reservation is pending. Homogeneous tool_result-only prepared-environment - batches reuse existing scoped result admission and application receipts without - creating a Turn or bypassing a pending reservation. Mixed prepared-environment - batches remain unsupported. HTTP expiry/cancellation use local 409 environment_input_expired/environment_input_cancelled - errors. New input on a Session whose hosted Environment failed to provision - returns the observed 409 conflict_error "the hosted environment failed to - provision"; input already waiting when it fails and expired Environments keep - the local 409 environment_unavailable. Input the Session cannot accept in - its current state, such as a result after cancellation or a batch while earlier - input is pending, and a result that differs from the call's saved result return - 409 with type and code conflict_error; reusing an Idempotency-Key with a different - batch returns the local 409 idempotency_conflict. Inside an owned Session, - a result for an unknown call or for a call of another Turn returns 400 invalid_request_error - and changes nothing; missing and foreign Sessions return 404. Losing execution - ownership returns 503. The response write deadline accommodates the admission - window for either prepared Environment, independently of new-hosted-admission - and executor URL settings. Disconnecting the waiting HTTP request does not - cancel retained work or restart its deadline. Retry keys identify the whole - ordered batch. Function output accepts text or ordered text/image parts subject - to engine support; Claude SDK accepts text results and, on none and qualified - workspace profiles, successful inline PNG/JPEG results, preserving ordered - content; error images and remote references reject before admission. Native - image resizing may change bytes. Runtime image-result support is checked only - for image-bearing delivery. Codex and Claude SDK on none and qualified managed - or self_hosted workspace profiles accept ordered inline PNG/JPEG image messages. - Other engines remain text-only; remote image URLs are unsupported. Image references - are retained unchanged without service-side downloads. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Retry key, up to 128 bytes - in: header - name: Idempotency-Key - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Ordered input events - in: body - name: body - required: true - schema: - $ref: '#/definitions/v1.CreateEventsRequest' - responses: - "202": - description: Accepted - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "409": - description: Conflict - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Submit Session input events - tags: - - Sessions - /agents/sessions/{session_id}/items: - get: - description: Returns supported message and tool Items in first-observation order. - Native engine fields are projected explicitly; unfinished Items on terminal - Turns are incomplete. Cursors are Items of the same tenant and Session. Any - other after value, including a malformed one, returns 400 invalid_request_error - with the message "Invalid session item ID in `after`". - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Last Item ID from the previous page - in: query - name: after - type: string - - default: 20 - description: Page size; 0 is treated as 1 and values above 100 as 100 - in: query - minimum: 0 - name: limit - type: integer - - default: desc - description: Creation order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.ItemList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List persisted execution Items - tags: - - Items - /agents/sessions/{session_id}/subagents: - get: - description: Includes nested and closed Subagents. Cursors are Subagents of - the same tenant and Session. Any other after value, including a malformed - one, returns 400 invalid_request_error with the message "Invalid resource - ID in `after`". A limit outside 1–100 is rejected. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Last Subagent ID from the previous page - in: query - name: after - type: string - - default: 20 - description: Page size - in: query - maximum: 100 - minimum: 1 - name: limit - type: integer - - default: desc - description: Resource order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SubagentList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List Session Subagents - tags: - - Subagents - /agents/sessions/{session_id}/subagents/{subagent_id}: - get: - description: Returns this Session's persisted Subagent. Active includes idle - between Turns. Resuming preserves opened_at and clears closed_at. Unknown - or inaccessible parent scopes return not found. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Subagent ID - in: path - name: subagent_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.Subagent' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Retrieve a Session Subagent - tags: - - Subagents - /agents/sessions/{session_id}/subagents/{subagent_id}/items: - get: - description: Returns only this Subagent's own Items across all its Turns, not - its descendants' Items. Cursors are Items of the same tenant, Session and - Subagent. Any other after value, including a malformed one, returns 400 invalid_request_error - with the message "Invalid session item ID in `after`". - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Subagent ID - in: path - name: subagent_id - required: true - type: string - - description: Last Item ID from the previous page - in: query - name: after - type: string - - default: 20 - description: Page size; 0 is treated as 1 and values above 100 as 100 - in: query - minimum: 0 - name: limit - type: integer - - default: desc - description: Resource order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.ItemList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List a Subagent's Items - tags: - - Subagents - /agents/sessions/{session_id}/subagents/{subagent_id}/turns: - get: - description: Includes this Subagent's Turns after resume, with the Session's - Agent ID as agent_id. Cursors are Turns of the same tenant, Session and Subagent. - Any other after value, including a malformed one, returns 400 invalid_request_error - with the message "Invalid resource ID in `after`". Missing recorded usage - remains null. A limit outside 1–100 is rejected. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Subagent ID - in: path - name: subagent_id - required: true - type: string - - description: Last Turn ID from the previous page - in: query - name: after - type: string - - default: 20 - description: Page size - in: query - maximum: 100 - minimum: 1 - name: limit - type: integer - - default: desc - description: Creation order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.TurnList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List a Subagent's Turns - tags: - - Subagents - /agents/sessions/{session_id}/subagents/{subagent_id}/turns/{turn_id}: - get: - description: Returns a Turn owned by this Subagent. Its agent_id is the Session's - Agent ID and its subagent_id identifies the Subagent. Session Turn routes - do not return child Turns. Unknown or inaccessible parent scopes return not - found. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Subagent ID - in: path - name: subagent_id - required: true - type: string - - description: Turn ID - in: path - name: turn_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.Turn' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Retrieve a Subagent Turn - tags: - - Subagents - /agents/sessions/{session_id}/subagents/{subagent_id}/turns/{turn_id}/items: - get: - description: Returns Items owned by this exact Subagent Turn. Cursors are Items - of the same tenant, Session, Subagent and Turn. Any other after value, including - a malformed one, returns 400 invalid_request_error with the message "Invalid - session item ID in `after`". - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Subagent ID - in: path - name: subagent_id - required: true - type: string - - description: Turn ID - in: path - name: turn_id - required: true - type: string - - description: Last Item ID from the previous page - in: query - name: after - type: string - - default: 20 - description: Page size; 0 is treated as 1 and values above 100 as 100 - in: query - minimum: 0 - name: limit - type: integer - - default: desc - description: Resource order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.ItemList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List a Subagent Turn's Items - tags: - - Subagents - /agents/sessions/{session_id}/turns: - get: - description: Returns the Session's root Turns in creation order; Subagent Turns - are listed through the Subagent Turn routes. The cursor belongs to the same - Session and tenant; any other after value, including a malformed one or a - Subagent Turn ID, returns not found. Usage contains the latest recorded complete - token breakdown; missing measurements remain null. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Last Turn ID from the previous page - in: query - name: after - type: string - - default: 20 - description: Page size - in: query - maximum: 100 - minimum: 1 - name: limit - type: integer - - default: desc - description: Creation order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.TurnList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List execution Turns - tags: - - Turns - /agents/sessions/{session_id}/turns/{turn_id}: - get: - description: Returns a root Turn of this Session. A Subagent Turn ID returns - the same not found error as a missing Turn; read it through the Subagent Turn - routes. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Session ID - in: path - name: session_id - required: true - type: string - - description: Turn ID - in: path - name: turn_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.Turn' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Retrieve an execution Turn - tags: - - Turns - /files: - get: - description: Lists project-owned Files without reading their bodies. The limit - defaults to 10000 and must be 1–10000. Equal creation times use ID ordering. - Purpose validation precedes cursor lookup; current storage contains only user_data. - An explicit empty purpose is treated as omitted. Repeated purpose values remain - rejected. Hosted positive filtering, default order and concurrent-page behavior - remain unverified. No Beta header is required. - parameters: - - description: Last File ID from the previous page - in: query - name: after - type: string - - default: 10000 - description: Maximum page size, 1–10000 - in: query - maximum: 10000 - minimum: 1 - name: limit - type: integer - - default: desc - description: Creation order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - - description: Only return Files with this purpose - in: query - name: purpose - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SourceFileList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List source files - tags: - - Files - post: - consumes: - - multipart/form-data - description: Accepts one multipart file and purpose=user_data in either order, - with a private 512 MiB content limit and 64 KiB envelope allowance. Commits - only after the entire request validates. The source is project-owned, independent - of Sessions and workspace copies. No Beta header is required. Other purposes, - expires_after, listing, resumable Uploads, quotas/rate-limit and complete - hosted error/status parity remain unsupported or unverified. - parameters: - - description: Source bytes - in: formData - name: file - required: true - type: file - - description: user_data - enum: - - user_data - in: formData - name: purpose - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SourceFile' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Upload a source file - tags: - - Files - /files/{file_id}: - delete: - description: Atomically deletes project-owned metadata and stored bytes. Already-admitted - reads or copies may finish. Workspace copies remain independent. Historical - WAL/backups are not erased. No Beta header is required; exact hosted concurrent - deletion/error semantics remain unverified. - parameters: - - description: Source file ID - in: path - name: file_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SourceFileDeleted' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Delete a source file - tags: - - Files - get: - description: Returns immutable project-owned user_data file metadata. No Beta - header is required. Other purposes, expiration and full hosted status/error - semantics remain unimplemented or unverified. - parameters: - - description: Source file ID - in: path - name: file_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SourceFile' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Retrieve source file metadata - tags: - - Files - /files/{file_id}/content: - get: - description: Resolves project-owned File metadata before enforcing download - policy. Public download of user_data Files returns 400; missing and foreign - Files return the same 404. Internal initial-file and workspace copies remain - available. No Beta header is required. - parameters: - - description: Source file ID - in: path - name: file_id - required: true - type: string - produces: - - application/json - responses: - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Download source file bytes - tags: - - Files - /skills: - get: - description: Lists tenant-owned metadata in timestamp order. Default page size - 20, maximum 100. Limit 0 returns an empty page whose has_more reports whether - any Skill follows the cursor; exact hosted defaults remain unverified. - parameters: - - description: Skill resource cursor - in: query - name: after - type: string - - default: 20 - description: Page size; 0 returns an empty page - in: query - maximum: 100 - minimum: 0 - name: limit - type: integer - - description: Creation order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SkillList' - security: - - BearerAuth: [] - summary: List Skills - tags: - - Skills - post: - consumes: - - multipart/form-data - description: Accepts one ZIP in files or a directory in files[]. Applies the - qualified portable Skill bundle profile. No Beta header is required; full - hosted upload limits and activation extensions are not qualified. - parameters: - - description: Skill ZIP or directory files - in: formData - name: files - required: true - type: file - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.Skill' - security: - - BearerAuth: [] - summary: Upload a Skill - tags: - - Skills - /skills/{skill_id}: - delete: - description: Deletes tenant-owned source bundles. Existing Session installation - snapshots remain independent. - parameters: - - description: Skill ID - in: path - name: skill_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SkillDeleted' - security: - - BearerAuth: [] - summary: Delete a Skill and its versions - tags: - - Skills - get: - description: Returns tenant-owned metadata without decrypting contents or starting - Runtime. No Beta header is required. - parameters: - - description: Skill ID - in: path - name: skill_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.Skill' - security: - - BearerAuth: [] - summary: Retrieve Skill metadata - tags: - - Skills - post: - consumes: - - application/json - description: Changes only the tenant-owned default pointer; immutable versions - and existing Session snapshots remain unchanged. - parameters: - - description: Skill ID - in: path - name: skill_id - required: true - type: string - - description: Default version - in: body - name: body - required: true - schema: - $ref: '#/definitions/v1.SkillUpdateRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.Skill' - security: - - BearerAuth: [] - summary: Update the default Skill version - tags: - - Skills - /skills/{skill_id}/content: - get: - description: Downloads an authorized ZIP using the default pointer when no concrete - version is supplied. Exact upstream unversioned selection, content headers - and range semantics remain unverified. - parameters: - - description: Skill ID - in: path - name: skill_id - required: true - type: string - produces: - - application/octet-stream - responses: - "200": - description: OK - schema: - type: file - security: - - BearerAuth: [] - summary: Download Skill content - tags: - - Skills - /skills/{skill_id}/versions: - get: - description: Orders by version number; after identifies a version resource, - not a version number. An after value that does not begin with skillver, or - a version of another Skill, returns 400 invalid_value with param after; a - missing version returns not found. No contents are decrypted. Limit 0 returns - an empty page whose has_more reports whether any version follows the cursor. - parameters: - - description: Skill ID - in: path - name: skill_id - required: true - type: string - - description: Version resource cursor - in: query - name: after - type: string - - default: 20 - description: Page size; 0 returns an empty page - in: query - maximum: 100 - minimum: 0 - name: limit - type: integer - - description: Version order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SkillVersionList' - security: - - BearerAuth: [] - summary: List Skill versions - tags: - - Skills - post: - consumes: - - multipart/form-data - parameters: - - description: Skill ID - in: path - name: skill_id - required: true - type: string - - description: Skill ZIP or directory files - in: formData - name: files - required: true - type: file - - description: Set as default - in: formData - name: default - type: boolean - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SkillVersion' - security: - - BearerAuth: [] - summary: Upload an immutable Skill version - tags: - - Skills - /skills/{skill_id}/versions/{version}: - delete: - description: Deleting the only remaining version also deletes the Skill; existing - Session installation snapshots remain independent. The default version cannot - be deleted while other versions remain. Version numbers are never reused. - parameters: - - description: Skill ID - in: path - name: skill_id - required: true - type: string - - description: Concrete version number - in: path - name: version - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SkillVersionDeleted' - security: - - BearerAuth: [] - summary: Delete a Skill version - tags: - - Skills - get: - parameters: - - description: Skill ID - in: path - name: skill_id - required: true - type: string - - description: Concrete version number - in: path - name: version - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.SkillVersion' - security: - - BearerAuth: [] - summary: Retrieve Skill version metadata - tags: - - Skills - /skills/{skill_id}/versions/{version}/content: - get: - parameters: - - description: Skill ID - in: path - name: skill_id - required: true - type: string - - description: Concrete version number - in: path - name: version - required: true - type: string - produces: - - application/octet-stream - responses: - "200": - description: OK - schema: - type: file - security: - - BearerAuth: [] - summary: Download immutable Skill version content - tags: - - Skills - /vaults: - get: - description: Lists project-owned Vaults independently of execution. An unknown, - malformed or foreign after cursor returns not found. Includes active and archived - records by default. Status accepts a scalar, the SDK's status[] array or both, - filtering by their union; a repeated scalar is rejected. Limits default to - 20 and clamp to 1–100. Equal creation times use ID ordering; exact hosted - errors and concurrent-page behavior remain unverified. Archive/delete lifecycle - is not implemented. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Last Vault ID from the previous page - in: query - name: after - type: string - - default: 20 - description: Requested page size, clamped to 1–100 - in: query - name: limit - type: integer - - default: desc - description: Creation order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - - description: Scalar status filter - enum: - - active - - archived - in: query - name: status - type: string - - collectionFormat: multi - description: Array status filter; combined with status as a union - in: query - items: - enum: - - active - - archived - type: string - name: status[] - type: array - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.VaultList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List Vaults - tags: - - Vaults - post: - consumes: - - application/json - description: Creates a project-owned Vault independently of execution. Omitted - name stays null; a supplied string is trimmed and must contain 1–256 UTF-8 - bytes. Explicit null name is invalid. Omitted/null metadata becomes an empty - object; non-string values return invalid_request_error with a metadata. - param. Metadata has a local 64 KiB encoded storage bound. U+0000 in stored - strings is rejected as a local storage limit. Credentials, Session binding - and hosted error/retry parity remain incomplete. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Vault name and metadata - in: body - name: body - required: true - schema: - $ref: '#/definitions/v1.CreateVaultRequest' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/v1.Vault' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Create a Vault - tags: - - Vaults - /vaults/{vault_id}: - delete: - description: Atomically removes the authenticated project's Vault and all its - stored Credentials without an encryption key, decryption or external requests. - Existing Session snapshots, history and recorded retries retain their frozen - identities; subsequent credential lookups fail without reselection or anonymous - fallback. Already-resolved tokens and running Sessions are not revoked or - cancelled. Missing/repeated deletion locally returns 404. Exact hosted archive, - post-delete visibility and concurrent/error semantics remain unverified; physical - erasure from native history, WAL or backups is not established. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Vault ID - in: path - name: vault_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.VaultDeleted' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Delete a Vault and all its Credentials - tags: - - Vaults - get: - description: Reads a Vault owned by the authenticated project without resolving - credentials, Sessions or execution devices. Missing and foreign IDs share - the same not-found response; exact hosted error semantics remain unverified. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Vault ID - in: path - name: vault_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.Vault' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Retrieve a Vault - tags: - - Vaults - /vaults/{vault_id}/credentials: - get: - description: Lists only metadata from the authenticated project's requested - Vault, without decryption or execution. An unknown, malformed or foreign after - cursor, including another Vault's Credential, returns not found. Includes - active and archived Credentials by default, independently of Vault status. - Status accepts a scalar, the SDK status[] array or both, filtering by their - union; a repeated scalar is rejected. Limits default to 20 and clamp to 1–100. - Equal creation times use ID ordering. Hosted errors, concurrent-page behavior - and archive/delete lifecycle remain unverified or unimplemented. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Vault ID - in: path - name: vault_id - required: true - type: string - - description: Last Credential ID from the previous page - in: query - name: after - type: string - - default: 20 - description: Requested page size, clamped to 1–100 - in: query - name: limit - type: integer - - default: desc - description: Creation order; omit for descending, explicit empty values are - invalid - enum: - - asc - - desc - in: query - name: order - type: string - - description: Scalar status filter - enum: - - active - - archived - in: query - name: status - type: string - - collectionFormat: multi - description: Array status filter; combined with status as a union - in: query - items: - enum: - - active - - archived - type: string - name: status[] - type: array - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.CredentialList' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: List safe Vault Credential metadata - tags: - - Credentials - post: - consumes: - - application/json - description: Stores static_bearer or mcp_oauth secrets as execution-owned authenticated - ciphertext without contacting any endpoint. Static bearer and OAuth access - tokens must be nonempty strings; their bytes are preserved. OAuth accepts - a required access token, nullable RFC3339 expiry and optional refresh configuration - with none, client_secret_basic or client_secret_post authentication. Required - name is trimmed to 1–256 UTF-8 bytes. Credential and token endpoints require - HTTPS without userinfo or fragments. Responses contain safe metadata only, - including explicit nullable OAuth expiry, refresh, resource and scope. Missing - encryption configuration returns local 503. External authorization and provider - revocation remain caller responsibilities; exact hosted error/default semantics - remain unverified. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Vault ID - in: path - name: vault_id - required: true - type: string - - description: Write-only credential authentication union - in: body - name: body - required: true - schema: - $ref: '#/definitions/v1.CreateCredentialRequest' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/v1.Credential' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Create a Vault Credential - tags: - - Credentials - /vaults/{vault_id}/credentials/{credential_id}: - delete: - description: Removes one Credential and its encrypted token within the authenticated - project and owning Vault, without an encryption key or secret decryption. - Subsequent metadata reads, updates and dispatch lookups cannot use it. Existing - Session snapshots and history retain their frozen identities; already-resolved - tokens and running Sessions are not revoked or cancelled. This local policy - removes the row rather than defining archived lifecycle; missing/repeated - deletion returns 404. Exact hosted archive, post-delete visibility and retry/error - semantics remain unverified. Provider revocation and physical erasure from - native history, WAL or backups are separate concerns. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Vault ID - in: path - name: vault_id - required: true - type: string - - description: Credential ID - in: path - name: credential_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.CredentialDeleted' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Delete a Vault Credential - tags: - - Credentials - get: - description: Reads only non-secret metadata scoped to the authenticated project - and owning Vault. No token decryption, network request or execution is performed. - Unknown, foreign, wrong-Vault and malformed IDs use the same local not-found - response; hosted error parity remains unverified. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Vault ID - in: path - name: vault_id - required: true - type: string - - description: Credential ID - in: path - name: credential_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.Credential' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Retrieve safe Vault Credential metadata - tags: - - Credentials - post: - consumes: - - application/json - description: Explicitly empty static bearer or OAuth access tokens and OAuth - patches without a mutable field are rejected before storage. Omitted OAuth - access tokens preserve the existing grant when expiry or refresh fields change. - Updates the existing static_bearer or mcp_oauth authentication method without - network requests. OAuth access_token omission/null retains the token; a new - token clears omitted expiry, explicit null clears expiry, and other omitted - fields remain unchanged. OAuth refresh patches cannot add configuration or - change client, endpoint, resource or authentication method; nullable token/client-secret - values retain stored secrets while explicit null scope clears scope. Whole-null - refresh and token_endpoint_auth retain existing configuration under local - policy. Identity, destination, creation time and Session bindings remain unchanged. - Responses expose safe metadata only. Already-dispatched work is not revoked; - provider revocation, storage-key rotation and exact hosted concurrent-update/error - semantics remain separate. - parameters: - - description: agents=v1 - in: header - name: OpenAI-Beta - required: true - type: string - - description: Vault ID - in: path - name: vault_id - required: true - type: string - - description: Credential ID - in: path - name: credential_id - required: true - type: string - - description: Write-only credential authentication replacement union - in: body - name: body - required: true - schema: - $ref: '#/definitions/v1.UpdateCredentialRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.Credential' - "400": - description: Bad Request - schema: - $ref: '#/definitions/v1.ErrorResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/v1.ErrorResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/v1.ErrorResponse' - "413": - description: Request Entity Too Large - schema: - $ref: '#/definitions/v1.ErrorResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/v1.ErrorResponse' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/v1.ErrorResponse' - security: - - BearerAuth: [] - summary: Replace Vault Credential authentication secrets - tags: - - Credentials -schemes: -- http -- https -securityDefinitions: - BearerAuth: - in: header - name: Authorization - type: apiKey -swagger: "2.0" +{ + "openapi": "3.1.0", + "info": { + "title": "OpenAgentCore public API", + "version": "v1", + "description": "The pinned OpenAI Agents, Files and Skills API with x_agents_core extensions. See the coverage ledger for implementation qualification." + }, + "servers": [ + { + "url": "/v1" + } + ], + "security": [ + { + "ProjectKey": [] + } + ], + "tags": [ + { + "name": "Assistants", + "description": "Build Assistants that can call models and use tools." + }, + { + "name": "Audio", + "description": "Turn audio into text or text into audio." + }, + { + "name": "Chat", + "description": "Given a list of messages comprising a conversation, the model will return a response." + }, + { + "name": "Conversations", + "description": "Manage conversations and conversation items." + }, + { + "name": "Completions", + "description": "Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position." + }, + { + "name": "Embeddings", + "description": "Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms." + }, + { + "name": "Evals", + "description": "Manage and run evals in the OpenAI platform." + }, + { + "name": "Fine-tuning", + "description": "Manage fine-tuning jobs to tailor a model to your specific training data." + }, + { + "name": "Graders", + "description": "Manage and run graders in the OpenAI platform." + }, + { + "name": "Batch", + "description": "Create large batches of API requests to run asynchronously." + }, + { + "name": "Files", + "description": "Files are used to upload documents that can be used with features like Assistants and Fine-tuning." + }, + { + "name": "Uploads", + "description": "Use Uploads to upload large files in multiple parts." + }, + { + "name": "Images", + "description": "Given a prompt and/or an input image, the model will generate a new image." + }, + { + "name": "Models", + "description": "List and describe the various models available in the API." + }, + { + "name": "Moderations", + "description": "Given text and/or image inputs, classifies if those inputs are potentially harmful." + }, + { + "name": "Audit Logs", + "description": "List user actions and configuration changes within this organization." + } + ], + "paths": { + "/files": { + "get": { + "operationId": "listFiles", + "tags": [ + "Files" + ], + "summary": "Returns a list of files.", + "parameters": [ + { + "in": "query", + "name": "purpose", + "required": false, + "schema": { + "type": "string" + }, + "description": "Only return files with the given purpose." + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 10,000, and the default is 10,000.\n", + "required": false, + "schema": { + "type": "integer", + "default": 10000 + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "schema": { + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListFilesResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List files", + "group": "files", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/files \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.files.list()\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const list = await openai.files.list();\n\n for await (const file of list) {\n console.log(file);\n }\n}\n\nmain();" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"file-abc123\",\n \"object\": \"file\",\n \"bytes\": 175,\n \"created_at\": 1613677385,\n \"expires_at\": 1677614202,\n \"filename\": \"salesOverview.pdf\",\n \"purpose\": \"assistants\",\n },\n {\n \"id\": \"file-abc456\",\n \"object\": \"file\",\n \"bytes\": 140,\n \"created_at\": 1613779121,\n \"expires_at\": 1677614202,\n \"filename\": \"puppy.jsonl\",\n \"purpose\": \"fine-tune\",\n }\n ],\n \"first_id\": \"file-abc123\",\n \"last_id\": \"file-abc456\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "operationId": "createFile", + "tags": [ + "Files" + ], + "summary": "Upload a file that can be used across various endpoints. Individual files\ncan be up to 512 MB, and each project can store up to 2.5 TB of files in\ntotal. There is no organization-wide storage limit. Uploads to this\nendpoint are rate-limited to 1,000 requests per minute per authenticated\nuser.\n\n- The Assistants API supports files up to 2 million tokens and of specific\n file types. See the [Assistants Tools guide](https://developers.openai.com/api/docs/guides/tools) for\n details.\n- The Fine-tuning API only supports `.jsonl` files. The input also has\n certain required formats for fine-tuning\n [chat](https://developers.openai.com/api/docs/guides/supervised-fine-tuning#formatting-your-data) or\n [completions](https://developers.openai.com/api/docs/guides/supervised-fine-tuning#formatting-your-data) models.\n- The Batch API only supports `.jsonl` files up to 200 MB in size. The input\n also has a specific required\n [format](https://developers.openai.com/api/docs/guides/batch#1-prepare-your-batch-file).\n- For Retrieval or `file_search` ingestion, upload files here first. If\n you need to attach multiple uploaded files to the same vector store, use\n [`/vector_stores/{vector_store_id}/file_batches`](https://developers.openai.com/api/reference/resources/vector_stores/subresources/file_batches/methods/create)\n instead of attaching them one by one. Vector store attachment has separate\n limits from file upload, including 2,000 attached files per minute per\n organization.\n\nPlease [contact us](https://help.openai.com/) if you need to increase these\nstorage limits.\n", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateFileRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OpenAIFile" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Upload file", + "group": "files", + "description": "Uploads a file for later use across OpenAI APIs. Uploads to this endpoint are rate-limited to 1,000 requests per minute per authenticated user. For Retrieval or `file_search` ingestion, upload files here first. If you need to attach multiple uploaded files to the same vector store, use vector store file batches instead of attaching them one by one.\n", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/files \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -F purpose=\"fine-tune\" \\\n -F file=\"@mydata.jsonl\"\n -F expires_after[anchor]=\"created_at\"\n -F expires_after[seconds]=2592000\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.files.create(\n file=open(\"mydata.jsonl\", \"rb\"),\n purpose=\"fine-tune\",\n expires_after={\n \"anchor\": \"created_at\",\n \"seconds\": 2592000\n }\n)\n", + "javascript": "import fs from \"fs\";\nimport OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const file = await openai.files.create({\n file: fs.createReadStream(\"mydata.jsonl\"),\n purpose: \"fine-tune\",\n expires_after: {\n anchor: \"created_at\",\n seconds: 2592000\n }\n });\n\n console.log(file);\n}\n\nmain();" + }, + "response": "{\n \"id\": \"file-abc123\",\n \"object\": \"file\",\n \"bytes\": 120000,\n \"created_at\": 1677610602,\n \"expires_at\": 1677614202,\n \"filename\": \"mydata.jsonl\",\n \"purpose\": \"fine-tune\",\n}\n" + } + } + } + }, + "/files/{file_id}": { + "delete": { + "operationId": "deleteFile", + "tags": [ + "Files" + ], + "summary": "Delete a file and remove it from all vector stores.", + "parameters": [ + { + "in": "path", + "name": "file_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the file to use for this request." + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteFileResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete file", + "group": "files", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/files/file-abc123 \\\n -X DELETE \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.files.delete(\"file-abc123\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const file = await openai.files.delete(\"file-abc123\");\n\n console.log(file);\n}\n\nmain();" + }, + "response": "{\n \"id\": \"file-abc123\",\n \"object\": \"file\",\n \"deleted\": true\n}\n" + } + } + }, + "get": { + "operationId": "retrieveFile", + "tags": [ + "Files" + ], + "summary": "Returns information about a specific file.", + "parameters": [ + { + "in": "path", + "name": "file_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the file to use for this request." + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OpenAIFile" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve file", + "group": "files", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/files/file-abc123 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.files.retrieve(\"file-abc123\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const file = await openai.files.retrieve(\"file-abc123\");\n\n console.log(file);\n}\n\nmain();" + }, + "response": "{\n \"id\": \"file-abc123\",\n \"object\": \"file\",\n \"bytes\": 120000,\n \"created_at\": 1677610602,\n \"expires_at\": 1677614202,\n \"filename\": \"mydata.jsonl\",\n \"purpose\": \"fine-tune\",\n}\n" + } + } + } + }, + "/files/{file_id}/content": { + "get": { + "operationId": "downloadFile", + "tags": [ + "Files" + ], + "summary": "Returns a response containing the contents of the specified file.", + "parameters": [ + { + "in": "path", + "name": "file_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the file to use for this request." + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve file content", + "group": "files", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/files/file-abc123/content \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" > file.jsonl\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ncontent = client.files.content(\"file-abc123\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const response = await openai.files.content(\"file-abc123\");\n const content = await response.text();\n\n console.log(content);\n}\n\nmain();\n" + } + } + } + } + }, + "/skills": { + "post": { + "tags": [ + "Skills" + ], + "summary": "Create a new skill.", + "operationId": "CreateSkill", + "parameters": [], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateSkillBody" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSkillBody" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SkillResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + }, + "get": { + "tags": [ + "Skills" + ], + "summary": "List all skills for the current project.", + "operationId": "ListSkills", + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "Number of items to retrieve", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order of results by timestamp. Use `asc` for ascending order or `desc` for descending order.", + "required": false, + "schema": { + "$ref": "#/components/schemas/OrderEnum" + } + }, + { + "name": "after", + "in": "query", + "description": "Identifier for the last item from the previous pagination request", + "required": false, + "schema": { + "description": "Identifier for the last item from the previous pagination request", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SkillListResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/skills/{skill_id}": { + "delete": { + "tags": [ + "Skills" + ], + "summary": "Delete a skill by its ID.", + "operationId": "DeleteSkill", + "parameters": [ + { + "name": "skill_id", + "in": "path", + "description": "The identifier of the skill to delete.", + "required": true, + "schema": { + "example": "skill_123", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedSkillResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + }, + "get": { + "tags": [ + "Skills" + ], + "summary": "Get a skill by its ID.", + "operationId": "GetSkill", + "parameters": [ + { + "name": "skill_id", + "in": "path", + "description": "The identifier of the skill to retrieve.", + "required": true, + "schema": { + "example": "skill_123", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SkillResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + }, + "post": { + "tags": [ + "Skills" + ], + "summary": "Update the default version pointer for a skill.", + "operationId": "UpdateSkillDefaultVersion", + "parameters": [ + { + "name": "skill_id", + "in": "path", + "description": "The identifier of the skill.", + "required": true, + "schema": { + "example": "skill_123", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetDefaultSkillVersionBody" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SkillResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/skills/{skill_id}/content": { + "get": { + "tags": [ + "Skills" + ], + "summary": "Download a skill zip bundle by its ID.", + "operationId": "GetSkillContent", + "parameters": [ + { + "name": "skill_id", + "in": "path", + "description": "The identifier of the skill to download.", + "required": true, + "schema": { + "example": "skill_123", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The skill zip bundle.", + "content": { + "application/zip": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/skills/{skill_id}/versions": { + "post": { + "tags": [ + "Skills" + ], + "summary": "Create a new immutable skill version.", + "operationId": "CreateSkillVersion", + "parameters": [ + { + "name": "skill_id", + "in": "path", + "description": "The identifier of the skill to version.", + "required": true, + "schema": { + "example": "skill_123", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateSkillVersionBody" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSkillVersionBody" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SkillVersionResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + }, + "get": { + "tags": [ + "Skills" + ], + "summary": "List skill versions for a skill.", + "operationId": "ListSkillVersions", + "parameters": [ + { + "name": "skill_id", + "in": "path", + "description": "The identifier of the skill.", + "required": true, + "schema": { + "example": "skill_123", + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "Number of versions to retrieve.", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order of results by version number.", + "required": false, + "schema": { + "$ref": "#/components/schemas/OrderEnum" + } + }, + { + "name": "after", + "in": "query", + "description": "The skill version ID to start after.", + "required": false, + "schema": { + "example": "skillver_123", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SkillVersionListResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/skills/{skill_id}/versions/{version}": { + "get": { + "tags": [ + "Skills" + ], + "summary": "Get a specific skill version.", + "operationId": "GetSkillVersion", + "parameters": [ + { + "name": "skill_id", + "in": "path", + "description": "The identifier of the skill.", + "required": true, + "schema": { + "example": "skill_123", + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "description": "The version number to retrieve.", + "required": true, + "schema": { + "description": "The version number to retrieve.", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SkillVersionResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + }, + "delete": { + "tags": [ + "Skills" + ], + "summary": "Delete a skill version.", + "operationId": "DeleteSkillVersion", + "parameters": [ + { + "name": "skill_id", + "in": "path", + "description": "The identifier of the skill.", + "required": true, + "schema": { + "example": "skill_123", + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "description": "The skill version number.", + "required": true, + "schema": { + "description": "The skill version number.", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedSkillVersionResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/skills/{skill_id}/versions/{version}/content": { + "get": { + "tags": [ + "Skills" + ], + "summary": "Download a skill version zip bundle.", + "operationId": "GetSkillVersionContent", + "parameters": [ + { + "name": "skill_id", + "in": "path", + "description": "The identifier of the skill.", + "required": true, + "schema": { + "example": "skill_123", + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "description": "The skill version number.", + "required": true, + "schema": { + "description": "The skill version number.", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The skill zip bundle.", + "content": { + "application/zip": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/agents/environments/{environment_id}": { + "get": { + "operationId": "retrieveAgentEnvironment", + "summary": "Retrieves an execution environment's connection status and safe installed metadata. See [environment lifecycle](https://developers.openai.com/api/docs/guides/agents-api/environments/lifecycle).", + "description": "Retrieves an execution environment's connection status and safe installed metadata.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "environment_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the environment." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested environment.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicEnvironmentResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve an agent environment" + } + } + }, + "/agents/environments/{environment_id}/files": { + "get": { + "operationId": "listAgentEnvironmentFiles", + "summary": "Lists live files on a connected execution environment with optional directory filtering and opaque cursor pagination. See [environment files](https://developers.openai.com/api/docs/guides/agents-api/environments/files).", + "description": "Lists live files on a connected execution environment with optional directory filtering and opaque cursor pagination.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "environment_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the environment." + }, + { + "name": "path", + "in": "query", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Restrict the listing to this absolute workspace directory." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 1, + "maximum": 100 + }, + "description": "The maximum number of files to return, between 1 and 100." + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListOrderParam" + }, + "description": "Sort by case-sensitive path components. Defaults to descending." + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The opaque token from the previous page. Keep the same path, order, and limit." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "A page of live environment files.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnvironmentFileListResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List agent environment files" + } + }, + "post": { + "operationId": "createAgentEnvironmentFile", + "summary": "Copies inline bytes or a Files API file into a connected execution environment. See [environment files](https://developers.openai.com/api/docs/guides/agents-api/environments/files).", + "description": "Copies inline bytes or a Files API file into a connected execution environment.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "environment_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the environment." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HostedEnvironmentFileParam" + } + } + } + }, + "responses": { + "201": { + "description": "The created live environment file.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnvironmentFileResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create an agent environment file" + } + } + }, + "/agents/sessions/{session_id}/subagents": { + "get": { + "operationId": "listAgentSessionSubagents", + "summary": "Lists subagents in a session, including nested and closed subagents. See [subagent workflows](https://developers.openai.com/api/docs/guides/agents-api/multi-agent).", + "description": "Lists subagents in a session, including nested and closed subagents.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "description": "The maximum number of resources to return, between 1 and 100. Defaults to 20." + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListOrderParam", + "default": "desc" + }, + "description": "The order in which resources are returned. Defaults to `desc`." + }, + { + "name": "after", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Return resources after this resource ID in the selected order." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested subagents.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ], + "default": "list", + "x-stainless-const": true, + "description": "The object type, which is always `list`." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SubagentResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The resources returned in this page, in the requested sort order." + }, + "first_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the first resource in `data`, or `null` if the page is empty." + }, + "last_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the last resource in `data`, or `null` if the page is empty. Pass this as `after` with the same order and filters." + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more resources to retrieve after this page." + } + }, + "required": [ + "object", + "data", + "first_id", + "last_id", + "has_more" + ], + "additionalProperties": false, + "description": "A page of Agents API resources, with IDs for retrieving additional pages." + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List session subagents" + } + } + }, + "/agents/sessions/{session_id}/subagents/{subagent_id}": { + "get": { + "operationId": "retrieveAgentSessionSubagent", + "summary": "Retrieves a subagent belonging to this session. See [subagent workflows](https://developers.openai.com/api/docs/guides/agents-api/multi-agent).", + "description": "Retrieves a subagent belonging to this session.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "subagent_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the subagent in this session." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested subagent.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubagentResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve a session subagent" + } + } + }, + "/agents/sessions/{session_id}/subagents/{subagent_id}/items": { + "get": { + "operationId": "listAgentSessionSubagentItems", + "summary": "Lists this subagent's own items across all of its turns. See [subagent workflows](https://developers.openai.com/api/docs/guides/agents-api/multi-agent).", + "description": "Lists this subagent's own items across all of its turns.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "subagent_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the subagent in this session." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "description": "The maximum number of resources to return, between 1 and 100. Defaults to 20." + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListOrderParam", + "default": "desc" + }, + "description": "The order in which resources are returned. Defaults to `desc`." + }, + { + "name": "after", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Return resources after this resource ID in the selected order." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested subagent history.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionItemListResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List subagent items" + } + } + }, + "/agents/sessions/{session_id}/subagents/{subagent_id}/turns": { + "get": { + "operationId": "listAgentSessionSubagentTurns", + "summary": "Lists all turns of this subagent, including turns after a resume. See [subagent workflows](https://developers.openai.com/api/docs/guides/agents-api/multi-agent).", + "description": "Lists all turns of this subagent, including turns after a resume.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "subagent_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the subagent in this session." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "description": "The maximum number of resources to return, between 1 and 100. Defaults to 20." + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListOrderParam", + "default": "desc" + }, + "description": "The order in which resources are returned. Defaults to `desc`." + }, + { + "name": "after", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Return resources after this resource ID in the selected order." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested subagent history.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionTurnListResource", + "description": "A page of turns from an agent session or subagent." + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List subagent turns" + } + } + }, + "/agents/sessions/{session_id}/subagents/{subagent_id}/turns/{turn_id}": { + "get": { + "operationId": "retrieveAgentSessionSubagentTurn", + "summary": "Retrieves a turn belonging to this subagent. See [subagent workflows](https://developers.openai.com/api/docs/guides/agents-api/multi-agent).", + "description": "Retrieves a turn belonging to this subagent.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "subagent_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the subagent in this session." + }, + { + "name": "turn_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of a turn belonging to this subagent." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested subagent history.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TurnResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve a subagent turn" + } + } + }, + "/agents/sessions/{session_id}/subagents/{subagent_id}/turns/{turn_id}/items": { + "get": { + "operationId": "listAgentSessionSubagentTurnItems", + "summary": "Lists items belonging to one turn of this subagent. See [subagent workflows](https://developers.openai.com/api/docs/guides/agents-api/multi-agent).", + "description": "Lists items belonging to one turn of this subagent.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "subagent_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the subagent in this session." + }, + { + "name": "turn_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of a turn belonging to this subagent." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "description": "The maximum number of resources to return, between 1 and 100. Defaults to 20." + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListOrderParam", + "default": "desc" + }, + "description": "The order in which resources are returned. Defaults to `desc`." + }, + { + "name": "after", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Return resources after this resource ID in the selected order." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested subagent history.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionItemListResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List subagent turn items" + } + } + }, + "/agents": { + "get": { + "operationId": "listAgents", + "summary": "Lists reusable agents in the current project. See [agent configuration](https://developers.openai.com/api/docs/guides/agents-api/configuration).", + "description": "Lists reusable agents in the current project.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 1 + }, + "description": "The maximum number of resources to return." + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListOrderParam", + "default": "desc" + }, + "description": "The order in which resources are returned. Defaults to `desc`." + }, + { + "name": "after", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Return resources after this resource ID in the selected order." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "A page of agents.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentListResource", + "description": "A page of reusable agents." + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List agents" + } + }, + "post": { + "operationId": "createAgent", + "summary": "Creates a reusable agent without storing credentials. See [agent configuration](https://developers.openai.com/api/docs/guides/agents-api/configuration).", + "description": "Creates a reusable agent without storing credentials.", + "tags": [ + "Agents" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAgentParams" + } + } + } + }, + "responses": { + "201": { + "description": "The created agent.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create an agent" + }, + "parameters": [ + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ] + } + }, + "/agents/{agent_id}": { + "get": { + "operationId": "retrieveAgent", + "summary": "Retrieves a reusable agent by ID. See [agent configuration](https://developers.openai.com/api/docs/guides/agents-api/configuration).", + "description": "Retrieves a reusable agent by ID.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "agent_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the reusable agent." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested agent.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve an agent" + } + }, + "post": { + "operationId": "updateAgent", + "summary": "Updates a reusable agent. See [agent configuration](https://developers.openai.com/api/docs/guides/agents-api/configuration).", + "description": "Updates a reusable agent.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "agent_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the reusable agent." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAgentParams" + } + } + } + }, + "responses": { + "200": { + "description": "The updated agent.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Update an agent" + } + }, + "delete": { + "operationId": "deleteAgent", + "summary": "Deletes a reusable agent. See [agent configuration](https://developers.openai.com/api/docs/guides/agents-api/configuration).", + "description": "Deletes a reusable agent.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "agent_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the reusable agent." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The deleted agent.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedAgentResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete an agent" + } + } + }, + "/agents/environments/templates": { + "get": { + "operationId": "listAgentEnvironmentTemplates", + "summary": "Lists reusable environment templates without returning confidential values. See [reusing a hosted setup](https://developers.openai.com/api/docs/guides/agents-api/tools#reuse-a-hosted-plugin-setup).", + "description": "Lists reusable environment templates without returning confidential values.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "description": "The maximum number of resources to return, between 1 and 100. Defaults to 20." + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListOrderParam", + "default": "desc" + }, + "description": "The order in which resources are returned. Defaults to `desc`." + }, + { + "name": "after", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Return resources after this resource ID in the selected order." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "A page of environment templates.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnvironmentTemplateListResource", + "description": "A page of reusable environment templates." + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested environment definition was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List agent environment templates" + } + }, + "post": { + "operationId": "createAgentEnvironmentTemplate", + "summary": "Creates reusable environment configuration without returning confidential setup commands or environment values. See [reusing a hosted setup](https://developers.openai.com/api/docs/guides/agents-api/tools#reuse-a-hosted-plugin-setup).", + "description": "Creates reusable environment configuration without returning confidential setup commands or environment values.", + "tags": [ + "Agents" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateEnvironmentTemplateParams" + } + } + } + }, + "responses": { + "201": { + "description": "The created environment template.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnvironmentTemplateResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested environment definition was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create an agent environment template" + }, + "parameters": [ + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ] + } + }, + "/agents/environments/templates/{environment_template_id}": { + "get": { + "operationId": "retrieveAgentEnvironmentTemplate", + "summary": "Retrieves reusable environment configuration without returning confidential values. See [reusing a hosted setup](https://developers.openai.com/api/docs/guides/agents-api/tools#reuse-a-hosted-plugin-setup).", + "description": "Retrieves reusable environment configuration without returning confidential values.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "environment_template_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the reusable environment template." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested environment template.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnvironmentTemplateResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested environment definition was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve an agent environment template" + } + }, + "post": { + "operationId": "updateAgentEnvironmentTemplate", + "summary": "Updates reusable environment configuration without returning confidential values. See [reusing a hosted setup](https://developers.openai.com/api/docs/guides/agents-api/tools#reuse-a-hosted-plugin-setup).", + "description": "Updates reusable environment configuration without returning confidential values.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "environment_template_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the reusable environment template." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateEnvironmentTemplateParams" + } + } + } + }, + "responses": { + "200": { + "description": "The updated environment template.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnvironmentTemplateResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested environment definition was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current environment state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Update an agent environment template" + } + }, + "delete": { + "operationId": "deleteAgentEnvironmentTemplate", + "summary": "Deletes reusable environment configuration and all confidential template inputs. See [reusing a hosted setup](https://developers.openai.com/api/docs/guides/agents-api/tools#reuse-a-hosted-plugin-setup).", + "description": "Deletes reusable environment configuration and all confidential template inputs.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "environment_template_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the reusable environment template." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The deleted environment template.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedEnvironmentTemplateResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested environment definition was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current environment state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete an agent environment template" + } + } + }, + "/agents/sessions": { + "get": { + "operationId": "listAgentSessions", + "summary": "Lists managed agent sessions using ID-based pagination and the requested sort order. See [managing sessions](https://developers.openai.com/api/docs/guides/agents-api/sessions/manage).", + "description": "Lists managed agent sessions using ID-based pagination and the requested sort order.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 1 + }, + "description": "The maximum number of resources to return." + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListOrderParam", + "default": "desc" + }, + "description": "Sort order by the `created_at` timestamp. Use `asc` for ascending order or `desc` for descending order. Defaults to `desc`." + }, + { + "name": "agent_id", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Only return sessions whose root agent has this ID. Omit to return sessions for all agents." + }, + { + "name": "after", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Return resources after this resource ID in the selected order." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "A page of sessions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionListResource", + "description": "A paginated list of sessions." + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List agent sessions" + } + }, + "post": { + "operationId": "createAgentSession", + "summary": "Creates a managed agent session, optionally submits initial input, and returns the session or streams its events when stream is true. See [running sessions](https://developers.openai.com/api/docs/guides/agents-api/sessions).", + "description": "Creates a managed agent session, optionally submits initial input, and returns the session or streams its events when stream is true.", + "tags": [ + "Agents" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAgentSessionParams" + } + } + } + }, + "responses": { + "201": { + "description": "The created session or its event stream.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionResource" + } + }, + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/SessionEvent" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oai-streaming": { + "request_field": "stream", + "response_status_code": 201 + }, + "x-oaiMeta": { + "name": "Create an agent session" + }, + "parameters": [ + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ] + } + }, + "/agents/sessions/{session_id}": { + "get": { + "operationId": "retrieveAgentSession", + "summary": "Retrieves the current state of a managed agent session. See [managing sessions](https://developers.openai.com/api/docs/guides/agents-api/sessions/manage).", + "description": "Retrieves the current state of a managed agent session.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested session.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve an agent session" + } + }, + "post": { + "operationId": "updateAgentSession", + "summary": "Updates session metadata. Omitted fields are unchanged. See [managing sessions](https://developers.openai.com/api/docs/guides/agents-api/sessions/manage).", + "description": "Updates session metadata. Omitted fields are unchanged.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAgentSessionParams" + } + } + } + }, + "responses": { + "200": { + "description": "The updated session.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Update an agent session" + } + }, + "delete": { + "operationId": "deleteAgentSession", + "summary": "Removes a managed agent session from the public API and returns a deletion confirmation. Physical cleanup may continue asynchronously. See [managing sessions](https://developers.openai.com/api/docs/guides/agents-api/sessions/manage).", + "description": "Removes a managed agent session from the public API and returns a deletion confirmation. Physical cleanup may continue asynchronously.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The deleted session.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedSessionResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete an agent session" + } + } + }, + "/agents/sessions/{session_id}/artifacts": { + "get": { + "operationId": "listAgentSessionArtifacts", + "summary": "Lists immutable artifacts published by completed hosted session turns. See [session artifacts](https://developers.openai.com/api/docs/guides/agents-api/environments/files#openai-hosted-artifacts).", + "description": "Lists immutable artifacts published by completed hosted session turns.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListOrderParam" + }, + "description": "Sort by creation time and ID. Defaults to descending." + }, + { + "name": "environment_id", + "in": "query", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Restrict the listing to artifacts produced by this environment." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 1, + "maximum": 100 + }, + "description": "The maximum number of artifacts to return, between 1 and 100." + }, + { + "name": "after", + "in": "query", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Return artifacts after this immutable artifact ID." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "A page of durable session artifacts.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionArtifactListResource", + "description": "A page of durable artifacts published by a session." + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List agent session artifacts" + } + } + }, + "/agents/sessions/{session_id}/artifacts/{artifact_id}": { + "get": { + "operationId": "retrieveAgentSessionArtifact", + "summary": "Retrieves immutable metadata for one durable session artifact. See [session artifacts](https://developers.openai.com/api/docs/guides/agents-api/environments/files#openai-hosted-artifacts).", + "description": "Retrieves immutable metadata for one durable session artifact.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session that owns the artifact." + }, + { + "name": "artifact_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The immutable session artifact ID." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested session artifact.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionArtifactResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve an agent session artifact" + } + }, + "delete": { + "operationId": "deleteAgentSessionArtifact", + "summary": "Deletes an immutable session artifact without deleting its live environment file or original Files API object. See [session artifacts](https://developers.openai.com/api/docs/guides/agents-api/environments/files#openai-hosted-artifacts).", + "description": "Deletes an immutable session artifact without deleting its live environment file or original Files API object.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session that owns the artifact." + }, + { + "name": "artifact_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The immutable session artifact ID." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The deleted session artifact.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedSessionArtifactResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete an agent session artifact" + } + } + }, + "/agents/sessions/{session_id}/artifacts/{artifact_id}/content": { + "get": { + "operationId": "retrieveAgentSessionArtifactContent", + "summary": "Downloads immutable session artifact bytes after the execution environment expires. See [session artifacts](https://developers.openai.com/api/docs/guides/agents-api/environments/files#openai-hosted-artifacts).", + "description": "Downloads immutable session artifact bytes after the execution environment expires.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session that owns the artifact." + }, + { + "name": "artifact_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The immutable session artifact ID." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The immutable artifact contents.", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve agent session artifact content" + } + } + }, + "/agents/sessions/{session_id}/events": { + "get": { + "operationId": "listAgentSessionEvents", + "summary": "Streams live events for an agent session. See [session events](https://developers.openai.com/api/docs/guides/agents-api/sessions/events).", + "description": "Streams live events for an agent session.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "A live stream of session events.", + "content": { + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/SessionEvent" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oai-streaming": { + "request_field": null, + "response_status_code": 200 + }, + "x-oaiMeta": { + "name": "Stream agent session events" + } + }, + "post": { + "operationId": "createAgentSessionEvents", + "summary": "Submits message, cancellation, or tool-result events to a managed agent session. See [session events](https://developers.openai.com/api/docs/guides/agents-api/sessions/events).", + "description": "Submits message, cancellation, or tool-result events to a managed agent session.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": false, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "description": "An optional client-generated key that makes retries of submitted messages idempotent." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSessionEventsParams" + } + } + } + }, + "responses": { + "202": { + "description": "The events were accepted." + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create agent session input events" + } + } + }, + "/agents/sessions/{session_id}/items": { + "get": { + "operationId": "listAgentSessionItems", + "summary": "Lists items produced by the session's root agent, including its interactions with subagents. Each subagent has its own item history. See [inspecting agent output](https://developers.openai.com/api/docs/guides/agents-api/observability).", + "description": "Lists items produced by the session's root agent, including its interactions with subagents. Each subagent has its own item history.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "description": "The maximum number of resources to return, between 1 and 100. Defaults to 20." + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListOrderParam", + "default": "desc" + }, + "description": "The order in which resources are returned. Defaults to `desc`." + }, + { + "name": "after", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Return resources after this resource ID in the selected order." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "A page of session items.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionItemListResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required Responses permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List agent session items" + } + } + }, + "/agents/sessions/{session_id}/turns": { + "get": { + "operationId": "listAgentSessionTurns", + "summary": "Lists turns by creation time and turn ID. The after cursor is exclusive in the selected order. See [session turns](https://developers.openai.com/api/docs/guides/agents-api/sessions/manage#inspect-session-turns).", + "description": "Lists turns by creation time and turn ID. The after cursor is exclusive in the selected order.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "description": "The maximum number of resources to return, between 1 and 100. Defaults to 20." + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListOrderParam", + "default": "desc" + }, + "description": "The order in which resources are returned. Defaults to `desc`." + }, + { + "name": "after", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Return resources after this resource ID in the selected order." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "A page of session turns.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionTurnListResource", + "description": "A page of turns from an agent session or subagent." + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List agent session turns" + } + } + }, + "/agents/sessions/{session_id}/turns/{turn_id}": { + "get": { + "operationId": "retrieveAgentSessionTurn", + "summary": "Retrieves a turn's current status, timestamps, usage, and error. Returns 404 if the turn does not belong to the session. See [session turns](https://developers.openai.com/api/docs/guides/agents-api/sessions/manage#inspect-session-turns).", + "description": "Retrieves a turn's current status, timestamps, usage, and error. Returns 404 if the turn does not belong to the session.", + "tags": [ + "Agents" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the session that owns the turn." + }, + { + "name": "turn_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the turn." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested turn.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TurnResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested session or event was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current session state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve an agent session turn" + } + } + }, + "/vaults": { + "get": { + "operationId": "listVaults", + "summary": "Lists vaults using ID-based pagination. See [vaults](https://developers.openai.com/api/docs/guides/agents-api/tools/vaults).", + "description": "Lists vaults using ID-based pagination.", + "tags": [ + "Vaults" + ], + "parameters": [ + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListOrderParam", + "default": "desc" + }, + "description": "Sort order by the `created_at` timestamp. Use `asc` for ascending order or `desc` for descending order. Defaults to `desc`." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "description": "The maximum number of resources to return. Defaults to 20. Values are clamped between 1 and 100." + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/VaultStatusFilterParam" + }, + "description": "Filter by one status or a list, such as `status=active` or `status[]=active&status[]=archived`. Both statuses are included by default." + }, + { + "name": "after", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Return resources after this resource ID in the selected order." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "A page of vaults.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VaultListResource", + "description": "A page of vaults." + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested vault or credential was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current vault state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List vaults" + } + }, + "post": { + "operationId": "createVault", + "summary": "Creates a vault for the current project. See [vaults](https://developers.openai.com/api/docs/guides/agents-api/tools/vaults).", + "description": "Creates a vault for the current project.", + "tags": [ + "Vaults" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateVaultParams" + } + } + } + }, + "responses": { + "201": { + "description": "The created vault.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VaultResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested vault or credential was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current vault state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create a vault" + }, + "parameters": [ + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ] + } + }, + "/vaults/{vault_id}": { + "get": { + "operationId": "retrieveVault", + "summary": "Retrieves a vault by its ID. See [vaults](https://developers.openai.com/api/docs/guides/agents-api/tools/vaults).", + "description": "Retrieves a vault by its ID.", + "tags": [ + "Vaults" + ], + "parameters": [ + { + "name": "vault_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the vault." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested vault.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VaultResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested vault or credential was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current vault state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve a vault" + } + }, + "delete": { + "operationId": "deleteVault", + "summary": "Deletes a vault and all its credentials. See [vaults](https://developers.openai.com/api/docs/guides/agents-api/tools/vaults).", + "description": "Deletes a vault and all its credentials.", + "tags": [ + "Vaults" + ], + "parameters": [ + { + "name": "vault_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the vault." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The deleted vault.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedVaultResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested vault or credential was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current vault state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete a vault" + } + } + }, + "/vaults/{vault_id}/credentials": { + "get": { + "operationId": "listVaultCredentials", + "summary": "Lists a vault's credentials using ID-based pagination without returning secret values. See [vaults](https://developers.openai.com/api/docs/guides/agents-api/tools/vaults).", + "description": "Lists a vault's credentials using ID-based pagination without returning secret values.", + "tags": [ + "Vaults" + ], + "parameters": [ + { + "name": "vault_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the vault." + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListOrderParam", + "default": "desc" + }, + "description": "Sort order by the `created_at` timestamp. Use `asc` for ascending order or `desc` for descending order. Defaults to `desc`." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "description": "The maximum number of resources to return. Defaults to 20. Values are clamped between 1 and 100." + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/VaultStatusFilterParam" + }, + "description": "Filter by one status or a list, such as `status=active` or `status[]=active&status[]=archived`. Both statuses are included by default." + }, + { + "name": "after", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "Return resources after this resource ID in the selected order." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "A page of vault credentials.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VaultCredentialListResource", + "description": "A page of credentials in a vault." + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested vault or credential was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current vault state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List vault credentials" + } + }, + "post": { + "operationId": "createVaultCredential", + "summary": "Creates a vault credential. Secret values are write-only and are never returned. See [vaults](https://developers.openai.com/api/docs/guides/agents-api/tools/vaults).", + "description": "Creates a vault credential. Secret values are write-only and are never returned.", + "tags": [ + "Vaults" + ], + "parameters": [ + { + "name": "vault_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the vault." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateVaultCredentialParams" + } + } + } + }, + "responses": { + "201": { + "description": "The created vault credential without secret values.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VaultCredentialResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested vault or credential was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current vault state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create a vault credential" + } + } + }, + "/vaults/{vault_id}/credentials/{credential_id}": { + "get": { + "operationId": "retrieveVaultCredential", + "summary": "Retrieves vault credential metadata without returning secret values. See [vaults](https://developers.openai.com/api/docs/guides/agents-api/tools/vaults).", + "description": "Retrieves vault credential metadata without returning secret values.", + "tags": [ + "Vaults" + ], + "parameters": [ + { + "name": "vault_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the vault." + }, + { + "name": "credential_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the vault credential." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The requested vault credential without secret values.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VaultCredentialResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested vault or credential was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current vault state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve a vault credential" + } + }, + "post": { + "operationId": "rotateVaultCredential", + "summary": "Rotates a vault credential's write-only secret and returns only credential metadata. See [vaults](https://developers.openai.com/api/docs/guides/agents-api/tools/vaults).", + "description": "Rotates a vault credential's write-only secret and returns only credential metadata.", + "tags": [ + "Vaults" + ], + "parameters": [ + { + "name": "vault_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the vault." + }, + { + "name": "credential_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the vault credential." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RotateVaultCredentialParams" + } + } + } + }, + "responses": { + "200": { + "description": "The rotated vault credential without secret values.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VaultCredentialResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested vault or credential was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current vault state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Rotate a vault credential" + } + }, + "delete": { + "operationId": "deleteVaultCredential", + "summary": "Deletes a vault credential. See [vaults](https://developers.openai.com/api/docs/guides/agents-api/tools/vaults).", + "description": "Deletes a vault credential.", + "tags": [ + "Vaults" + ], + "parameters": [ + { + "name": "vault_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the vault." + }, + { + "name": "credential_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "description": "The ID of the vault credential." + }, + { + "name": "OpenAI-Beta", + "in": "header", + "required": true, + "schema": { + "type": "string", + "const": "agents=v1" + } + } + ], + "responses": { + "200": { + "description": "The deleted vault credential.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedVaultCredentialResource" + } + } + } + }, + "400": { + "description": "The request was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "401": { + "description": "Authentication or project context was missing.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "403": { + "description": "The API key lacks the required management permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "404": { + "description": "The requested vault or credential was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "409": { + "description": "The request conflicted with the current vault state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "500": { + "description": "An internal error occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + }, + "503": { + "description": "The service is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse-2" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete a vault credential" + } + } + } + }, + "webhooks": { + "batch_cancelled": { + "post": { + "description": "Sent when a batch has been cancelled.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookBatchCancelled" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried.\n" + } + } + } + }, + "batch_completed": { + "post": { + "description": "Sent when a batch has completed processing.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookBatchCompleted" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried.\n" + } + } + } + }, + "batch_expired": { + "post": { + "description": "Sent when a batch has expired before completion.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookBatchExpired" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried.\n" + } + } + } + }, + "batch_failed": { + "post": { + "description": "Sent when a batch has failed.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookBatchFailed" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried.\n" + } + } + } + }, + "eval_run_canceled": { + "post": { + "description": "Sent when an eval run has been canceled.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookEvalRunCanceled" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried. \n" + } + } + } + }, + "eval_run_failed": { + "post": { + "description": "Sent when an eval run has failed.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookEvalRunFailed" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried. \n" + } + } + } + }, + "eval_run_succeeded": { + "post": { + "description": "Sent when an eval run has succeeded.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookEvalRunSucceeded" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried. \n" + } + } + } + }, + "fine_tuning_job_cancelled": { + "post": { + "description": "Sent when a fine-tuning job has been cancelled.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookFineTuningJobCancelled" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried. \n" + } + } + } + }, + "fine_tuning_job_failed": { + "post": { + "description": "Sent when a fine-tuning job has failed.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookFineTuningJobFailed" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried. \n" + } + } + } + }, + "fine_tuning_job_succeeded": { + "post": { + "description": "Sent when a fine-tuning job has succeeded.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookFineTuningJobSucceeded" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried. \n" + } + } + } + }, + "live_call_incoming": { + "post": { + "deprecated": true, + "description": "Deprecated: use `live.transport.incoming`. Retained only for existing subscriptions.\nSent when an incoming API SIP session is available for Live acceptance.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookLiveCallIncoming" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200\nstatus codes will be retried.\n" + } + } + } + }, + "live_transport_incoming": { + "post": { + "description": "Sent when an incoming API SIP session is available for Live acceptance.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookLiveTransportIncoming" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200\nstatus codes will be retried.\n" + } + } + } + }, + "realtime_call_incoming": { + "post": { + "description": "Sent when an incoming API SIP session is available for Realtime acceptance.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookRealtimeCallIncoming" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200\nstatus codes will be retried.\n" + } + } + } + }, + "response_cancelled": { + "post": { + "description": "Sent when a background response has been cancelled.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookResponseCancelled" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried.\n" + } + } + } + }, + "response_completed": { + "post": { + "description": "Sent when a background response has completed successfully.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookResponseCompleted" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried. \n" + } + } + } + }, + "response_failed": { + "post": { + "description": "Sent when a background response has failed.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookResponseFailed" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried.\n" + } + } + } + }, + "response_incomplete": { + "post": { + "description": "Sent when a background response is incomplete.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookResponseIncomplete" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status code to acknowledge receipt of the event. Non-200 \nstatus codes will be retried.\n" + } + } + } + }, + "safety_alert_created": { + "post": { + "description": "Sent when an approved safety alert is available for an API project.\nRetrieve the alert with a project API key granted `api.safety.alerts.read`.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookSafetyAlertCreated" + } + } + } + }, + "responses": { + "200": { + "description": "Return any 2xx status code to acknowledge receipt. A 410 Gone response also stops retries; other non-2xx responses are retried." + } + } + } + }, + "safety_org_alert_created": { + "post": { + "description": "Sent when an approved safety alert is available for an enterprise workspace.\nRetrieve the alert from `https://api.chatgpt.com/v1/safety/alerts/{id}` with\nan administrator API key for the workspace's backing organization granted\n`chatgpt.enterprise.safety_alerts.read`.\n", + "requestBody": { + "description": "The event payload sent by the API.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookSafetyOrgAlertCreated" + } + } + } + }, + "responses": { + "200": { + "description": "Return any 2xx status code to acknowledge receipt. A 410 Gone response also stops retries; other non-2xx responses are retried." + } + } + } + } + }, + "components": { + "schemas": { + "CreateFileRequest": { + "type": "object", + "additionalProperties": false, + "properties": { + "file": { + "description": "The File object (not file name) to be uploaded.\n", + "type": "string", + "format": "binary" + }, + "purpose": { + "description": "The intended purpose of the uploaded file. One of:\n- `assistants`: Used in the Assistants API\n- `batch`: Used in the Batch API\n- `fine-tune`: Used for fine-tuning\n- `vision`: Images used for vision fine-tuning\n- `user_data`: Flexible file type for any purpose\n- `evals`: Used for eval data sets\n", + "type": "string", + "enum": [ + "assistants", + "batch", + "fine-tune", + "vision", + "user_data", + "evals" + ] + }, + "expires_after": { + "$ref": "#/components/schemas/FileExpirationAfter" + } + }, + "required": [ + "file", + "purpose" + ] + }, + "DeleteFileResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "file" + ], + "x-stainless-const": true + }, + "deleted": { + "type": "boolean" + } + }, + "required": [ + "id", + "object", + "deleted" + ] + }, + "Error": { + "type": "object", + "properties": { + "code": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + }, + "param": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "type": { + "type": "string" + }, + "misalignment": { + "$ref": "#/components/schemas/MisalignmentErrorDetailsResource" + } + }, + "required": [ + "type", + "message", + "param", + "code" + ] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/Error" + } + }, + "required": [ + "error" + ] + }, + "FileExpirationAfter": { + "type": "object", + "title": "File expiration policy", + "description": "The expiration policy for a file. By default, files with `purpose=batch` expire after 30 days and all other files are persisted until they are manually deleted.", + "properties": { + "anchor": { + "description": "Anchor timestamp after which the expiration policy applies. Supported anchors: `created_at`.", + "type": "string", + "enum": [ + "created_at" + ], + "x-stainless-const": true + }, + "seconds": { + "description": "The number of seconds after the anchor time that the file will expire. Must be between 3600 (1 hour) and 2592000 (30 days).", + "type": "integer", + "format": "int64", + "minimum": 3600, + "maximum": 2592000 + } + }, + "required": [ + "anchor", + "seconds" + ] + }, + "ListFilesResponse": { + "type": "object", + "properties": { + "object": { + "type": "string", + "example": "list" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OpenAIFile" + } + }, + "first_id": { + "type": "string", + "example": "file-abc123" + }, + "last_id": { + "type": "string", + "example": "file-abc456" + }, + "has_more": { + "type": "boolean", + "example": false + } + }, + "required": [ + "object", + "data", + "first_id", + "last_id", + "has_more" + ] + }, + "OpenAIFile": { + "title": "OpenAIFile", + "description": "The `File` object represents a document that has been uploaded to OpenAI.", + "properties": { + "id": { + "type": "string", + "description": "The file identifier, which can be referenced in the API endpoints." + }, + "bytes": { + "type": "integer", + "description": "The size of the file, in bytes." + }, + "created_at": { + "type": "integer", + "format": "unixtime", + "description": "The Unix timestamp (in seconds) for when the file was created." + }, + "expires_at": { + "type": "integer", + "format": "unixtime", + "description": "The Unix timestamp (in seconds) for when the file will expire." + }, + "filename": { + "type": "string", + "description": "The name of the file." + }, + "object": { + "type": "string", + "description": "The object type, which is always `file`.", + "enum": [ + "file" + ], + "x-stainless-const": true + }, + "purpose": { + "type": "string", + "description": "The intended purpose of the file. Supported values are `assistants`, `assistants_output`, `batch`, `batch_output`, `fine-tune`, `fine-tune-results`, `vision`, and `user_data`.", + "enum": [ + "assistants", + "assistants_output", + "batch", + "batch_output", + "fine-tune", + "fine-tune-results", + "vision", + "user_data" + ] + }, + "status": { + "type": "string", + "deprecated": true, + "description": "Deprecated. The current status of the file, which can be either `uploaded`, `processed`, or `error`.", + "enum": [ + "uploaded", + "processed", + "error" + ] + }, + "status_details": { + "type": "string", + "deprecated": true, + "description": "Deprecated. For details on why a fine-tuning training file failed validation, see the `error` field on `fine_tuning.job`." + } + }, + "required": [ + "id", + "object", + "bytes", + "created_at", + "filename", + "purpose", + "status" + ], + "x-oaiMeta": { + "name": "The file object", + "example": "{\n \"id\": \"file-abc123\",\n \"object\": \"file\",\n \"bytes\": 120000,\n \"created_at\": 1677610602,\n \"expires_at\": 1680202602,\n \"filename\": \"salesOverview.pdf\",\n \"purpose\": \"assistants\",\n}\n" + } + }, + "_MisalignmentErrorType": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "potentially_unintended_data_transfer", + "potentially_unintended_data_access", + "potentially_unintended_destructive_activity", + "other" + ] + } + ] + }, + "_MisalignmentSteer": { + "properties": { + "message": { + "type": "string", + "description": "The public continuation instruction." + } + }, + "type": "object", + "required": [ + "message" + ] + }, + "MisalignmentErrorDetailsResource": { + "properties": { + "error_type": { + "$ref": "#/components/schemas/_MisalignmentErrorType", + "description": "An optional classification; clients must accept additional values." + }, + "detailed_explanation": { + "type": "string", + "description": "The public explanation for this block." + }, + "steer": { + "$ref": "#/components/schemas/_MisalignmentSteer", + "description": "An optional public continuation instruction." + } + }, + "type": "object", + "required": [] + }, + "OrderEnum": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "SkillResource": { + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the skill." + }, + "object": { + "type": "string", + "enum": [ + "skill" + ], + "description": "The object type, which is `skill`.", + "default": "skill", + "x-stainless-const": true + }, + "name": { + "type": "string", + "description": "Name of the skill." + }, + "description": { + "type": "string", + "description": "Description of the skill." + }, + "created_at": { + "type": "integer", + "format": "unixtime", + "description": "Unix timestamp (seconds) for when the skill was created." + }, + "default_version": { + "type": "string", + "description": "Default version for the skill." + }, + "latest_version": { + "type": "string", + "description": "Latest version for the skill." + } + }, + "type": "object", + "required": [ + "id", + "object", + "name", + "description", + "created_at", + "default_version", + "latest_version" + ] + }, + "SkillListResource": { + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ], + "description": "The type of object returned, must be `list`.", + "default": "list", + "x-stainless-const": true + }, + "data": { + "items": { + "$ref": "#/components/schemas/SkillResource" + }, + "type": "array", + "description": "A list of items" + }, + "first_id": { + "anyOf": [ + { + "type": "string", + "description": "The ID of the first item in the list." + }, + { + "type": "null" + } + ] + }, + "last_id": { + "anyOf": [ + { + "type": "string", + "description": "The ID of the last item in the list." + }, + { + "type": "null" + } + ] + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more items available." + } + }, + "type": "object", + "required": [ + "object", + "data", + "first_id", + "last_id", + "has_more" + ] + }, + "CreateSkillBody": { + "properties": { + "files": { + "oneOf": [ + { + "items": { + "type": "string", + "format": "binary" + }, + "type": "array", + "maxItems": 500, + "description": "Skill files to upload (directory upload) or a single zip file." + }, + { + "type": "string", + "format": "binary", + "description": "Skill zip file to upload." + } + ] + } + }, + "type": "object", + "required": [ + "files" + ], + "title": "Create skill request", + "description": "Uploads a skill either as a directory (multipart `files[]`) or as a single zip file." + }, + "SetDefaultSkillVersionBody": { + "properties": { + "default_version": { + "type": "string", + "description": "The skill version number to set as default." + } + }, + "type": "object", + "required": [ + "default_version" + ], + "title": "Update skill request", + "description": "Updates the default version pointer for a skill." + }, + "DeletedSkillResource": { + "properties": { + "object": { + "type": "string", + "enum": [ + "skill.deleted" + ], + "default": "skill.deleted", + "x-stainless-const": true + }, + "deleted": { + "type": "boolean" + }, + "id": { + "type": "string" + } + }, + "type": "object", + "required": [ + "object", + "deleted", + "id" + ] + }, + "SkillVersionResource": { + "properties": { + "object": { + "type": "string", + "enum": [ + "skill.version" + ], + "description": "The object type, which is `skill.version`.", + "default": "skill.version", + "x-stainless-const": true + }, + "id": { + "type": "string", + "description": "Unique identifier for the skill version." + }, + "skill_id": { + "type": "string", + "description": "Identifier of the skill for this version." + }, + "version": { + "type": "string", + "description": "Version number for this skill." + }, + "created_at": { + "type": "integer", + "format": "unixtime", + "description": "Unix timestamp (seconds) for when the version was created." + }, + "name": { + "type": "string", + "description": "Name of the skill version." + }, + "description": { + "type": "string", + "description": "Description of the skill version." + } + }, + "type": "object", + "required": [ + "object", + "id", + "skill_id", + "version", + "created_at", + "name", + "description" + ] + }, + "SkillVersionListResource": { + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ], + "description": "The type of object returned, must be `list`.", + "default": "list", + "x-stainless-const": true + }, + "data": { + "items": { + "$ref": "#/components/schemas/SkillVersionResource" + }, + "type": "array", + "description": "A list of items" + }, + "first_id": { + "anyOf": [ + { + "type": "string", + "description": "The ID of the first item in the list." + }, + { + "type": "null" + } + ] + }, + "last_id": { + "anyOf": [ + { + "type": "string", + "description": "The ID of the last item in the list." + }, + { + "type": "null" + } + ] + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more items available." + } + }, + "type": "object", + "required": [ + "object", + "data", + "first_id", + "last_id", + "has_more" + ] + }, + "CreateSkillVersionBody": { + "properties": { + "files": { + "oneOf": [ + { + "items": { + "type": "string", + "format": "binary" + }, + "type": "array", + "maxItems": 500, + "description": "Skill files to upload (directory upload) or a single zip file." + }, + { + "type": "string", + "format": "binary", + "description": "Skill zip file to upload." + } + ] + }, + "default": { + "type": "boolean", + "description": "Whether to set this version as the default." + } + }, + "type": "object", + "required": [ + "files" + ], + "title": "Create skill version request", + "description": "Uploads a new immutable version of a skill." + }, + "DeletedSkillVersionResource": { + "properties": { + "object": { + "type": "string", + "enum": [ + "skill.version.deleted" + ], + "default": "skill.version.deleted", + "x-stainless-const": true + }, + "deleted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "version": { + "type": "string", + "description": "The deleted skill version." + } + }, + "type": "object", + "required": [ + "object", + "deleted", + "id", + "version" + ] + }, + "EnvironmentTypeResource": { + "type": "string", + "enum": [ + "openai_hosted", + "self_hosted" + ], + "description": "The kind of execution environment." + }, + "EnvironmentStatusResource": { + "type": "string", + "enum": [ + "pending", + "connected", + "disconnected", + "expired", + "failed" + ], + "description": "The public lifecycle status of an execution environment." + }, + "HostedEnvironmentFileResourceFileId": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "file_id" + ], + "default": "file_id", + "x-stainless-const": true, + "description": "The type of the object. Always `file_id`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The session-scoped ID of the file in the execution environment." + }, + "file_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the uploaded file." + }, + "path": { + "type": "string", + "minLength": 0, + "description": "The file's absolute path inside the environment." + }, + "size_bytes": { + "type": "integer", + "format": "int64", + "minimum": 0, + "description": "The decoded file size in bytes." + } + }, + "required": [ + "type", + "id", + "file_id", + "path", + "size_bytes" + ], + "additionalProperties": false, + "description": "A file copied from the OpenAI Files API." + }, + "HostedEnvironmentFileResourceInline": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "inline" + ], + "default": "inline", + "x-stainless-const": true, + "description": "The type of the object. Always `inline`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The session-scoped ID of the file in the execution environment." + }, + "path": { + "type": "string", + "minLength": 0, + "description": "The file's absolute path inside the environment." + }, + "size_bytes": { + "type": "integer", + "format": "int64", + "minimum": 0, + "description": "The decoded file size in bytes." + } + }, + "required": [ + "type", + "id", + "path", + "size_bytes" + ], + "additionalProperties": false, + "description": "A file supplied inline when the session was created." + }, + "HostedEnvironmentFileResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/HostedEnvironmentFileResourceFileId" + }, + { + "$ref": "#/components/schemas/HostedEnvironmentFileResourceInline" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "file_id": "#/components/schemas/HostedEnvironmentFileResourceFileId", + "inline": "#/components/schemas/HostedEnvironmentFileResourceInline" + } + }, + "x-oai-discriminator-values": [ + "file_id", + "inline" + ], + "description": "Metadata for a file materialized in an OpenAI-hosted execution environment." + }, + "HostedSkillResourceSkillReference": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "skill_reference" + ], + "default": "skill_reference", + "x-stainless-const": true, + "description": "The type of the object. Always `skill_reference`." + }, + "skill_id": { + "type": "string", + "minLength": 0, + "description": "The referenced skill ID." + }, + "version": { + "type": "string", + "minLength": 0, + "description": "The concrete skill version installed for this session." + }, + "name": { + "type": "string", + "minLength": 0, + "description": "The installed skill name." + }, + "description": { + "type": "string", + "minLength": 0, + "description": "The installed skill description." + } + }, + "required": [ + "type", + "skill_id", + "version", + "name", + "description" + ], + "additionalProperties": false, + "description": "A skill installed from the Skills API." + }, + "HostedSkillResourceInline": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "inline" + ], + "default": "inline", + "x-stainless-const": true, + "description": "The type of the object. Always `inline`." + }, + "name": { + "type": "string", + "minLength": 0, + "description": "The installed skill name." + }, + "description": { + "type": "string", + "minLength": 0, + "description": "The installed skill description." + } + }, + "required": [ + "type", + "name", + "description" + ], + "additionalProperties": false, + "description": "A skill installed from an inline ZIP archive." + }, + "HostedSkillResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/HostedSkillResourceSkillReference" + }, + { + "$ref": "#/components/schemas/HostedSkillResourceInline" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "skill_reference": "#/components/schemas/HostedSkillResourceSkillReference", + "inline": "#/components/schemas/HostedSkillResourceInline" + } + }, + "x-oai-discriminator-values": [ + "skill_reference", + "inline" + ], + "description": "A skill installed in an OpenAI-hosted environment." + }, + "HostedPluginResourceInline": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "inline" + ], + "default": "inline", + "x-stainless-const": true, + "description": "The type of the object. Always `inline`." + }, + "name": { + "type": "string", + "minLength": 0, + "description": "The installed plugin name." + }, + "description": { + "type": "string", + "minLength": 0, + "description": "The installed plugin description." + } + }, + "required": [ + "type", + "name", + "description" + ], + "additionalProperties": false, + "description": "A plugin installed from an inline ZIP archive." + }, + "HostedPluginResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/HostedPluginResourceInline" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "inline": "#/components/schemas/HostedPluginResourceInline" + } + }, + "x-oai-discriminator-values": [ + "inline" + ], + "description": "A plugin installed in an OpenAI-hosted environment." + }, + "PublicEnvironmentResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the environment." + }, + "object": { + "type": "string", + "enum": [ + "agent.environment" + ], + "default": "agent.environment", + "x-stainless-const": true, + "description": "The object type. Always `agent.environment`." + }, + "type": { + "$ref": "#/components/schemas/EnvironmentTypeResource", + "description": "Whether the environment is hosted by OpenAI or by the application." + }, + "status": { + "$ref": "#/components/schemas/EnvironmentStatusResource", + "description": "The current environment connection status." + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HostedEnvironmentFileResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "Files installed in the environment, without their contents." + }, + "skills": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HostedSkillResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "Skills installed in the environment, without their archive contents." + }, + "plugins": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HostedPluginResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "Plugins installed in the environment, without their archive contents." + } + }, + "required": [ + "id", + "object", + "type", + "status", + "files", + "skills", + "plugins" + ], + "additionalProperties": false, + "description": "Safe metadata for a first-class execution environment." + }, + "ErrorBodyResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "minLength": 0, + "description": "The error type." + }, + "code": { + "type": "string", + "minLength": 0, + "description": "A machine-readable error code." + }, + "message": { + "type": "string", + "minLength": 0, + "description": "A human-readable error message." + }, + "param": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The request parameter that caused the error, or null for a request-wide error." + } + }, + "required": [ + "type", + "code", + "message", + "param" + ], + "additionalProperties": false, + "description": "Details about an API error." + }, + "ErrorResponse-2": { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/ErrorBodyResource", + "description": "The error returned by the API." + } + }, + "required": [ + "error" + ], + "additionalProperties": false, + "description": "An API error response." + }, + "ListOrderParam": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "x-enumDescriptions": [ + "Returns resources in ascending order.", + "Returns resources in descending order." + ], + "description": "The order in which paginated resources are returned." + }, + "EnvironmentFilePageObjectResource": { + "type": "string", + "enum": [ + "page" + ], + "default": "page", + "x-stainless-const": true, + "description": "The object type for a page of files in an execution environment." + }, + "EnvironmentFileResource": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "agent.environment.file" + ], + "default": "agent.environment.file", + "x-stainless-const": true, + "description": "The object type. Always `agent.environment.file`." + }, + "environment_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the environment containing this file." + }, + "path": { + "type": "string", + "minLength": 0, + "description": "The absolute file path inside the environment's workspace." + }, + "size_bytes": { + "type": "integer", + "format": "int64", + "minimum": 0, + "description": "The file size in bytes." + } + }, + "required": [ + "object", + "environment_id", + "path", + "size_bytes" + ], + "additionalProperties": false, + "description": "A live file in an execution environment." + }, + "EnvironmentFileListResource": { + "type": "object", + "properties": { + "object": { + "$ref": "#/components/schemas/EnvironmentFilePageObjectResource", + "description": "The object type. Always `page`." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EnvironmentFileResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "Files available on the current page." + }, + "next": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The opaque cursor to use when requesting the next page, if any." + }, + "has_more": { + "type": "boolean", + "description": "Whether more files follow this page." + } + }, + "required": [ + "object", + "data", + "next", + "has_more" + ], + "additionalProperties": false, + "description": "A paginated list of live execution environment files." + }, + "HostedEnvironmentFileParamFileId": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "file_id" + ], + "default": "file_id", + "x-stainless-const": true, + "description": "The type of the object. Always `file_id`." + }, + "file_id": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "The ID of the uploaded file." + }, + "path": { + "type": "string", + "minLength": 1, + "maxLength": 4096, + "description": "The absolute destination path inside `/workspace`." + } + }, + "required": [ + "type", + "file_id", + "path" + ], + "additionalProperties": false, + "description": "A file previously uploaded through the OpenAI Files API." + }, + "HostedEnvironmentFileParamInline": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "inline" + ], + "default": "inline", + "x-stainless-const": true, + "description": "The type of the object. Always `inline`." + }, + "data": { + "type": "string", + "minLength": 0, + "maxLength": 6990508, + "description": "The standard-base64-encoded file contents." + }, + "path": { + "type": "string", + "minLength": 1, + "maxLength": 4096, + "description": "The absolute destination path inside `/workspace`." + } + }, + "required": [ + "type", + "data", + "path" + ], + "additionalProperties": false, + "description": "A file supplied directly as standard-base64 data." + }, + "HostedEnvironmentFileParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/HostedEnvironmentFileParamFileId" + }, + { + "$ref": "#/components/schemas/HostedEnvironmentFileParamInline" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "file_id": "#/components/schemas/HostedEnvironmentFileParamFileId", + "inline": "#/components/schemas/HostedEnvironmentFileParamInline" + } + }, + "x-oai-discriminator-values": [ + "file_id", + "inline" + ], + "description": "A file materialized in an OpenAI-hosted execution environment." + }, + "SubagentObjectResource": { + "type": "string", + "enum": [ + "agent.session.subagent" + ], + "default": "agent.session.subagent", + "x-stainless-const": true, + "description": "The object type for a subagent." + }, + "OutputTextResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "output_text" + ], + "default": "output_text", + "x-stainless-const": true, + "description": "The content type. Always `output_text`." + }, + "text": { + "type": "string", + "minLength": 0, + "description": "The text produced by the agent." + } + }, + "required": [ + "type", + "text" + ], + "additionalProperties": false, + "description": "A text content part produced by the agent." + }, + "EncryptedContentResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "encrypted_content" + ], + "default": "encrypted_content", + "x-stainless-const": true, + "description": "The content type. Always `encrypted_content`." + }, + "encrypted_content": { + "type": "string", + "minLength": 0, + "description": "The encrypted content payload." + } + }, + "required": [ + "type", + "encrypted_content" + ], + "additionalProperties": false, + "description": "Encrypted content exchanged between agents." + }, + "AgentContentResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/OutputTextResource" + }, + { + "$ref": "#/components/schemas/EncryptedContentResource" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "output_text": "#/components/schemas/OutputTextResource", + "encrypted_content": "#/components/schemas/EncryptedContentResource" + } + }, + "x-oai-discriminator-values": [ + "output_text", + "encrypted_content" + ], + "description": "A plaintext or encrypted content part exchanged between agents." + }, + "SubagentStatusResource": { + "type": "string", + "enum": [ + "active", + "closed" + ], + "x-enumDescriptions": [ + "The subagent remains available, including while idle between turns.", + "The subagent is closed." + ], + "description": "The current status of a subagent." + }, + "SubagentResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the subagent." + }, + "object": { + "$ref": "#/components/schemas/SubagentObjectResource", + "description": "The object type. Always `agent.session.subagent`." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session that owns the subagent." + }, + "name": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The runner-assigned nickname, or null when unavailable." + }, + "instructions": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/AgentContentResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "Initial task content, or null when unavailable. Text may contain placeholders for images or audio when only a preview is available." + }, + "parent_agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the agent that created this subagent." + }, + "status": { + "$ref": "#/components/schemas/SubagentStatusResource", + "description": "The current status of the subagent." + }, + "opened_at": { + "type": "integer", + "format": "int64", + "description": "The Unix timestamp, in seconds, when the subagent was first opened. Resuming does not change it." + }, + "closed_at": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "description": "The Unix timestamp, in seconds, when the subagent was closed. Null while active, including after resume." + } + }, + "required": [ + "id", + "object", + "session_id", + "name", + "instructions", + "parent_agent_id", + "status", + "opened_at", + "closed_at" + ], + "additionalProperties": false, + "description": "A subagent created within a session." + }, + "SessionMessageRoleResource": { + "type": "string", + "enum": [ + "user", + "assistant" + ], + "description": "The author of a session message." + }, + "MessageContentResourceInputText": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "input_text" + ], + "default": "input_text", + "x-stainless-const": true, + "description": "The type of the object. Always `input_text`." + }, + "text": { + "type": "string", + "minLength": 0, + "description": "The text supplied by the user." + } + }, + "required": [ + "type", + "text" + ], + "additionalProperties": false, + "description": "Text supplied by the user." + }, + "MessageContentResourceInputImage": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "input_image" + ], + "default": "input_image", + "x-stainless-const": true, + "description": "The type of the object. Always `input_image`." + }, + "image_url": { + "type": "string", + "minLength": 0, + "description": "The URL of the image supplied by the user, which may be a base64-encoded data URL." + } + }, + "required": [ + "type", + "image_url" + ], + "additionalProperties": false, + "description": "An image supplied by the user." + }, + "MessageContentResourceOutputText": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "output_text" + ], + "default": "output_text", + "x-stainless-const": true, + "description": "The type of the object. Always `output_text`." + }, + "text": { + "type": "string", + "minLength": 0, + "description": "The text produced by the assistant." + } + }, + "required": [ + "type", + "text" + ], + "additionalProperties": false, + "description": "Text produced by the assistant." + }, + "MessageContentResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/MessageContentResourceInputText" + }, + { + "$ref": "#/components/schemas/MessageContentResourceInputImage" + }, + { + "$ref": "#/components/schemas/MessageContentResourceOutputText" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "input_text": "#/components/schemas/MessageContentResourceInputText", + "input_image": "#/components/schemas/MessageContentResourceInputImage", + "output_text": "#/components/schemas/MessageContentResourceOutputText" + } + }, + "x-oai-discriminator-values": [ + "input_text", + "input_image", + "output_text" + ], + "description": "A content part in a session message." + }, + "OutputItemStatusResource": { + "type": "string", + "enum": [ + "in_progress", + "completed", + "incomplete" + ], + "x-enumDescriptions": [ + "The item is in progress.", + "The item is complete.", + "The item stopped before completing." + ], + "description": "The status of an agent output item." + }, + "MessagePhaseResource": { + "type": "string", + "enum": [ + "commentary", + "final_answer" + ], + "x-enumDescriptions": [ + "Commentary produced while the agent works.", + "The agent's final answer." + ], + "description": "The phase of an assistant message." + }, + "MessageItemResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "message" + ], + "default": "message", + "x-stainless-const": true, + "description": "The item type. Always `message`." + }, + "id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of this item, or null for legacy user messages whose ID was not recorded." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "role": { + "$ref": "#/components/schemas/SessionMessageRoleResource", + "description": "The role of the message author." + }, + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageContentResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The content of the message. User messages contain input text or images; assistant messages contain output text." + }, + "status": { + "$ref": "#/components/schemas/OutputItemStatusResource", + "description": "The status of the message. User messages are always `completed`." + }, + "phase": { + "anyOf": [ + { + "$ref": "#/components/schemas/MessagePhaseResource" + }, + { + "type": "null" + } + ], + "description": "The phase of an assistant message. Null for user messages." + } + }, + "required": [ + "type", + "id", + "turn_id", + "role", + "content", + "status", + "phase" + ], + "additionalProperties": false, + "description": "A user or assistant message recorded in a session." + }, + "SummaryTextResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "summary_text" + ], + "default": "summary_text", + "x-stainless-const": true, + "description": "The content type. Always `summary_text`." + }, + "text": { + "type": "string", + "minLength": 0, + "description": "The reasoning summary text." + } + }, + "required": [ + "type", + "text" + ], + "additionalProperties": false, + "description": "A reasoning summary content part." + }, + "ReasoningItemResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "reasoning" + ], + "default": "reasoning", + "x-stainless-const": true, + "description": "The item type. Always `reasoning`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the reasoning item." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "summary": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryTextResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The reasoning summaries produced by the agent." + }, + "status": { + "anyOf": [ + { + "$ref": "#/components/schemas/OutputItemStatusResource" + }, + { + "type": "null" + } + ], + "description": "The status of the reasoning item." + } + }, + "required": [ + "type", + "id", + "turn_id", + "summary", + "status" + ], + "additionalProperties": false, + "description": "A reasoning item produced by the agent." + }, + "FunctionCallStatusResource": { + "type": "string", + "enum": [ + "in_progress", + "completed", + "failed", + "incomplete" + ], + "x-enumDescriptions": [ + "The call is in progress.", + "The call completed successfully.", + "The call failed.", + "The call stopped before completing." + ], + "description": "The status of a tool call." + }, + "FunctionCallItemResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "function_call" + ], + "default": "function_call", + "x-stainless-const": true, + "description": "The item type. Always `function_call`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the function call item." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "call_id": { + "type": "string", + "minLength": 0, + "description": "The ID used to submit the function result." + }, + "name": { + "type": "string", + "minLength": 0, + "description": "The name of the function to call." + }, + "arguments": { + "description": "The arguments to pass to the function." + }, + "status": { + "$ref": "#/components/schemas/FunctionCallStatusResource", + "description": "The status of the function call." + } + }, + "required": [ + "type", + "id", + "turn_id", + "call_id", + "name", + "arguments", + "status" + ], + "additionalProperties": false, + "description": "A function call produced by the agent." + }, + "InputContentResourceInputText": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "input_text" + ], + "default": "input_text", + "x-stainless-const": true, + "description": "The type of the object. Always `input_text`." + }, + "text": { + "type": "string", + "minLength": 0, + "description": "The text supplied to the agent." + } + }, + "required": [ + "type", + "text" + ], + "additionalProperties": false, + "description": "Text input recorded in a session item." + }, + "InputContentResourceInputImage": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "input_image" + ], + "default": "input_image", + "x-stainless-const": true, + "description": "The type of the object. Always `input_image`." + }, + "image_url": { + "type": "string", + "minLength": 0, + "description": "The URL of the image supplied to the agent, which may be a base64-encoded data URL." + } + }, + "required": [ + "type", + "image_url" + ], + "additionalProperties": false, + "description": "Image input recorded in a session item." + }, + "InputContentResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/InputContentResourceInputText" + }, + { + "$ref": "#/components/schemas/InputContentResourceInputImage" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "input_text": "#/components/schemas/InputContentResourceInputText", + "input_image": "#/components/schemas/InputContentResourceInputImage" + } + }, + "x-oai-discriminator-values": [ + "input_text", + "input_image" + ], + "description": "User-provided content recorded in a session item." + }, + "FunctionCallOutputResource": { + "oneOf": [ + { + "type": "string", + "minLength": 0 + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/InputContentResource" + }, + "minItems": 0, + "maxItems": 2000 + } + ], + "description": "The text or model-input content supplied as a function result." + }, + "FunctionCallOutputItemResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the function call output item." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "type": { + "type": "string", + "enum": [ + "function_call_output" + ], + "default": "function_call_output", + "x-stainless-const": true, + "description": "The item type. Always `function_call_output`." + }, + "call_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the function call that produced this output." + }, + "status": { + "$ref": "#/components/schemas/FunctionCallStatusResource", + "description": "The status of the function call." + }, + "output": { + "anyOf": [ + { + "$ref": "#/components/schemas/FunctionCallOutputResource" + }, + { + "type": "null" + } + ], + "description": "The function result, if the call succeeded." + }, + "error": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The error message, if the call failed." + } + }, + "required": [ + "id", + "turn_id", + "type", + "call_id", + "status", + "output", + "error" + ], + "additionalProperties": false, + "description": "The result supplied for a function call." + }, + "AgentMessageItemResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the message." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "type": { + "type": "string", + "enum": [ + "agent_message" + ], + "default": "agent_message", + "x-stainless-const": true, + "description": "The item type. Always `agent_message`." + }, + "sender_agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID or name of the sending agent." + }, + "recipient_agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID or name of the receiving agent." + }, + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgentContentResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The content exchanged between the agents." + } + }, + "required": [ + "id", + "turn_id", + "type", + "sender_agent_id", + "recipient_agent_id", + "content" + ], + "additionalProperties": false, + "description": "A message exchanged between agent threads." + }, + "McpCallItemResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "mcp_call" + ], + "default": "mcp_call", + "x-stainless-const": true, + "description": "The item type. Always `mcp_call`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the MCP call item." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "server_label": { + "type": "string", + "minLength": 0, + "description": "The label of the MCP server." + }, + "name": { + "type": "string", + "minLength": 0, + "description": "The name of the MCP tool." + }, + "arguments": { + "description": "The arguments passed to the MCP tool." + }, + "status": { + "$ref": "#/components/schemas/FunctionCallStatusResource", + "description": "The status of the MCP tool call." + }, + "output": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "description": "The output returned by the MCP tool, if any." + }, + "error": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "description": "The error returned by the MCP tool, if any." + } + }, + "required": [ + "type", + "id", + "turn_id", + "server_label", + "name", + "arguments", + "status", + "output", + "error" + ], + "additionalProperties": false, + "description": "A call to a tool on an MCP server." + }, + "WebSearchActionResourceSearch": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "search" + ], + "default": "search", + "x-stainless-const": true, + "description": "The type of the object. Always `search`." + }, + "query": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The search query, when a single query was used." + }, + "queries": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "The search queries, when multiple queries were used." + } + }, + "required": [ + "type", + "query", + "queries" + ], + "additionalProperties": false, + "description": "A search query or group of search queries." + }, + "WebSearchActionResourceOpenPage": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "open_page" + ], + "default": "open_page", + "x-stainless-const": true, + "description": "The type of the object. Always `open_page`." + }, + "url": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The URL of the page that was opened." + } + }, + "required": [ + "type", + "url" + ], + "additionalProperties": false, + "description": "Opens a web page." + }, + "WebSearchActionResourceFindInPage": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "find_in_page" + ], + "default": "find_in_page", + "x-stainless-const": true, + "description": "The type of the object. Always `find_in_page`." + }, + "url": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The URL of the page that was searched." + }, + "pattern": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The text pattern that was searched for." + } + }, + "required": [ + "type", + "url", + "pattern" + ], + "additionalProperties": false, + "description": "Finds text within a web page." + }, + "WebSearchActionResourceOther": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "other" + ], + "default": "other", + "x-stainless-const": true, + "description": "The type of the object. Always `other`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Another web search action." + }, + "WebSearchActionResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/WebSearchActionResourceSearch" + }, + { + "$ref": "#/components/schemas/WebSearchActionResourceOpenPage" + }, + { + "$ref": "#/components/schemas/WebSearchActionResourceFindInPage" + }, + { + "$ref": "#/components/schemas/WebSearchActionResourceOther" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "search": "#/components/schemas/WebSearchActionResourceSearch", + "open_page": "#/components/schemas/WebSearchActionResourceOpenPage", + "find_in_page": "#/components/schemas/WebSearchActionResourceFindInPage", + "other": "#/components/schemas/WebSearchActionResourceOther" + } + }, + "x-oai-discriminator-values": [ + "search", + "open_page", + "find_in_page", + "other" + ], + "description": "An action performed by the web search tool." + }, + "WebSearchCallItemResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "web_search_call" + ], + "default": "web_search_call", + "x-stainless-const": true, + "description": "The item type. Always `web_search_call`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the web search call." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "status": { + "$ref": "#/components/schemas/OutputItemStatusResource", + "description": "The status of the web search call." + }, + "action": { + "anyOf": [ + { + "$ref": "#/components/schemas/WebSearchActionResource" + }, + { + "type": "null" + } + ], + "description": "The action performed by the web search tool." + } + }, + "required": [ + "type", + "id", + "turn_id", + "status", + "action" + ], + "additionalProperties": false, + "description": "A web search call produced by the agent." + }, + "CommandExecutionItemResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "command_execution" + ], + "default": "command_execution", + "x-stainless-const": true, + "description": "The item type. Always `command_execution`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the command execution item." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "command": { + "type": "string", + "minLength": 0, + "description": "The command that was executed." + }, + "cwd": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The working directory used to execute the command." + }, + "status": { + "$ref": "#/components/schemas/FunctionCallStatusResource", + "description": "The status of the command execution." + }, + "output": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The command output, if available." + }, + "exit_code": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "description": "The process exit code, if the command completed." + }, + "duration_ms": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "description": "The command duration in milliseconds." + } + }, + "required": [ + "type", + "id", + "turn_id", + "command", + "cwd", + "status", + "output", + "exit_code", + "duration_ms" + ], + "additionalProperties": false, + "description": "A command execution produced by the agent." + }, + "InterruptSubagentCallItemResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "interrupt_subagent_call" + ], + "default": "interrupt_subagent_call", + "x-stainless-const": true, + "x-enumDescriptions": [ + "The current public item type." + ], + "description": "The item type. Always `interrupt_subagent_call`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the tool call item." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "status": { + "$ref": "#/components/schemas/FunctionCallStatusResource", + "description": "The status of the tool call." + }, + "sender_agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the agent requesting the interrupt." + }, + "recipient_agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the agent to interrupt." + } + }, + "required": [ + "type", + "id", + "turn_id", + "status", + "sender_agent_id", + "recipient_agent_id" + ], + "additionalProperties": false, + "description": "A request to interrupt a subagent's current turn. The subagent remains available." + }, + "CreateSubagentCallItemResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "create_subagent_call" + ], + "default": "create_subagent_call", + "x-stainless-const": true, + "x-enumDescriptions": [ + "The current public item type." + ], + "description": "The item type. Always `create_subagent_call`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the tool call item." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "status": { + "$ref": "#/components/schemas/FunctionCallStatusResource", + "description": "The status of the tool call." + }, + "agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the agent that requested the subagent." + }, + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgentContentResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The task given to the spawned agent." + }, + "model": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The model requested for the spawned agent." + }, + "reasoning_effort": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The reasoning effort requested for the spawned agent." + } + }, + "required": [ + "type", + "id", + "turn_id", + "status", + "agent_id", + "content", + "model", + "reasoning_effort" + ], + "additionalProperties": false, + "description": "A request to spawn a subagent." + }, + "SendSubagentInputCallItemResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "send_subagent_input_call" + ], + "default": "send_subagent_input_call", + "x-stainless-const": true, + "x-enumDescriptions": [ + "The current public item type." + ], + "description": "The item type. Always `send_subagent_input_call`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the tool call item." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "status": { + "$ref": "#/components/schemas/FunctionCallStatusResource", + "description": "The status of the tool call." + }, + "sender_agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the agent sending the input." + }, + "recipient_agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the agent receiving the input." + }, + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgentContentResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The input sent to the receiving agent." + } + }, + "required": [ + "type", + "id", + "turn_id", + "status", + "sender_agent_id", + "recipient_agent_id", + "content" + ], + "additionalProperties": false, + "description": "A request to send input to another agent." + }, + "ResumeSubagentCallItemResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "resume_subagent_call" + ], + "default": "resume_subagent_call", + "x-stainless-const": true, + "x-enumDescriptions": [ + "The current public item type." + ], + "description": "The item type. Always `resume_subagent_call`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the tool call item." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "status": { + "$ref": "#/components/schemas/FunctionCallStatusResource", + "description": "The status of the tool call." + }, + "sender_agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the agent requesting the resume." + }, + "recipient_agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the agent to resume." + } + }, + "required": [ + "type", + "id", + "turn_id", + "status", + "sender_agent_id", + "recipient_agent_id" + ], + "additionalProperties": false, + "description": "A request to resume a subagent." + }, + "WaitForSubagentsCallItemResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "wait_for_subagents_call" + ], + "default": "wait_for_subagents_call", + "x-stainless-const": true, + "x-enumDescriptions": [ + "The current public item type." + ], + "description": "The item type. Always `wait_for_subagents_call`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the tool call item." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "status": { + "$ref": "#/components/schemas/FunctionCallStatusResource", + "description": "The status of the tool call." + }, + "sender_agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the agent waiting for results." + }, + "recipient_agent_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "The IDs of the agents to wait for." + } + }, + "required": [ + "type", + "id", + "turn_id", + "status", + "sender_agent_id", + "recipient_agent_ids" + ], + "additionalProperties": false, + "description": "A request to wait for one or more subagents." + }, + "CloseSubagentCallItemResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "close_subagent_call" + ], + "default": "close_subagent_call", + "x-stainless-const": true, + "x-enumDescriptions": [ + "The current public item type." + ], + "description": "The item type. Always `close_subagent_call`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the tool call item." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "status": { + "$ref": "#/components/schemas/FunctionCallStatusResource", + "description": "The status of the tool call." + }, + "sender_agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the agent requesting the close." + }, + "recipient_agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the agent to close." + } + }, + "required": [ + "type", + "id", + "turn_id", + "status", + "sender_agent_id", + "recipient_agent_id" + ], + "additionalProperties": false, + "description": "A request to close a subagent." + }, + "SessionTurnItemResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/MessageItemResource" + }, + { + "$ref": "#/components/schemas/ReasoningItemResource" + }, + { + "$ref": "#/components/schemas/FunctionCallItemResource" + }, + { + "$ref": "#/components/schemas/FunctionCallOutputItemResource" + }, + { + "$ref": "#/components/schemas/AgentMessageItemResource" + }, + { + "$ref": "#/components/schemas/McpCallItemResource" + }, + { + "$ref": "#/components/schemas/WebSearchCallItemResource" + }, + { + "$ref": "#/components/schemas/CommandExecutionItemResource" + }, + { + "$ref": "#/components/schemas/CreateSubagentCallItemResource" + }, + { + "$ref": "#/components/schemas/SendSubagentInputCallItemResource" + }, + { + "$ref": "#/components/schemas/ResumeSubagentCallItemResource" + }, + { + "$ref": "#/components/schemas/WaitForSubagentsCallItemResource" + }, + { + "$ref": "#/components/schemas/InterruptSubagentCallItemResource" + }, + { + "$ref": "#/components/schemas/CloseSubagentCallItemResource" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "message": "#/components/schemas/MessageItemResource", + "reasoning": "#/components/schemas/ReasoningItemResource", + "function_call": "#/components/schemas/FunctionCallItemResource", + "function_call_output": "#/components/schemas/FunctionCallOutputItemResource", + "agent_message": "#/components/schemas/AgentMessageItemResource", + "mcp_call": "#/components/schemas/McpCallItemResource", + "web_search_call": "#/components/schemas/WebSearchCallItemResource", + "command_execution": "#/components/schemas/CommandExecutionItemResource", + "interrupt_subagent_call": "#/components/schemas/InterruptSubagentCallItemResource", + "create_subagent_call": "#/components/schemas/CreateSubagentCallItemResource", + "send_subagent_input_call": "#/components/schemas/SendSubagentInputCallItemResource", + "resume_subagent_call": "#/components/schemas/ResumeSubagentCallItemResource", + "wait_for_subagents_call": "#/components/schemas/WaitForSubagentsCallItemResource", + "close_subagent_call": "#/components/schemas/CloseSubagentCallItemResource" + } + }, + "x-oai-discriminator-values": [ + "message", + "reasoning", + "function_call", + "function_call_output", + "agent_message", + "mcp_call", + "web_search_call", + "command_execution", + "create_subagent_call", + "send_subagent_input_call", + "resume_subagent_call", + "wait_for_subagents_call", + "interrupt_subagent_call", + "close_subagent_call" + ], + "description": "An item associated with a session turn." + }, + "SessionItemListResource": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ], + "default": "list", + "x-stainless-const": true, + "description": "The object type, which is always `list`." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionTurnItemResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The resources returned in this page, in the requested sort order." + }, + "first_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the first resource in `data`, or `null` if the page is empty." + }, + "last_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the last resource in `data`, or `null` if the page is empty. Pass this as `after` with the same order and filters." + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more resources to retrieve after this page." + } + }, + "required": [ + "object", + "data", + "first_id", + "last_id", + "has_more" + ], + "additionalProperties": false, + "description": "A page of messages, reasoning, and tool calls from a session's item history." + }, + "TurnObjectResource": { + "type": "string", + "enum": [ + "agent.session.turn" + ], + "default": "agent.session.turn", + "x-stainless-const": true, + "description": "The object type for a turn." + }, + "TurnStatusResource": { + "type": "string", + "enum": [ + "queued", + "in_progress", + "waiting", + "completed", + "failed", + "cancelled" + ], + "x-enumDescriptions": [ + "The turn is waiting to start.", + "The turn is in progress.", + "The turn is waiting for external input.", + "The turn completed successfully.", + "The turn failed.", + "The turn was cancelled." + ], + "description": "The current status of a turn." + }, + "SessionTurnErrorCodeResource": { + "type": "string", + "enum": [ + "context_length_exceeded", + "session_budget_exceeded", + "usage_limit_exceeded", + "rate_limit_exceeded", + "server_overloaded", + "cyber_policy", + "connection_failed", + "server_error", + "authentication_error", + "invalid_request", + "resource_not_found", + "sandbox_error", + "executor_version_incompatible", + "active_turn_not_steerable", + "request_timeout", + "internal_error" + ], + "x-enumDescriptions": [ + "The request exceeds the model's context window.", + "The session has reached its usage budget.", + "The organization has reached a usage, plan, or billing limit.", + "The request exceeds the available rate limit.", + "The model service is temporarily overloaded.", + "The request was rejected by a safety policy.", + "The request could not connect to the model service.", + "The model service encountered an unexpected error.", + "The API credentials are invalid or lack the required access.", + "The request contains invalid input or configuration.", + "The requested model or resource is unavailable.", + "The request could not complete in its execution environment.", + "The executor must be upgraded before it can run this turn.", + "The session cannot accept additional input while a request is running.", + "The request timed out before the model service responded.", + "An unexpected internal error prevented the session request from completing." + ], + "description": "Stable public categories for session request failures." + }, + "SessionTurnErrorResource": { + "type": "object", + "properties": { + "code": { + "$ref": "#/components/schemas/SessionTurnErrorCodeResource", + "description": "A stable, machine-readable failure category." + }, + "message": { + "type": "string", + "minLength": 0, + "description": "A customer-safe explanation of the failure." + } + }, + "required": [ + "code", + "message" + ], + "additionalProperties": false, + "description": "A customer-safe error describing why a session request failed." + }, + "InputTokensDetailsResource": { + "type": "object", + "properties": { + "cached_tokens": { + "type": "integer", + "format": "int64", + "description": "The number of input tokens retrieved from the prompt cache." + } + }, + "required": [ + "cached_tokens" + ], + "additionalProperties": false, + "description": "A breakdown of input token usage for a session or turn." + }, + "OutputTokensDetailsResource": { + "type": "object", + "properties": { + "reasoning_tokens": { + "type": "integer", + "format": "int64", + "description": "The number of output tokens used for reasoning." + } + }, + "required": [ + "reasoning_tokens" + ], + "additionalProperties": false, + "description": "A breakdown of output token usage for a session or turn." + }, + "TokenUsageResource": { + "type": "object", + "properties": { + "input_tokens": { + "type": "integer", + "format": "int64", + "description": "The number of input tokens used by the agent." + }, + "input_tokens_details": { + "$ref": "#/components/schemas/InputTokensDetailsResource", + "description": "A breakdown of the agent's input token usage." + }, + "output_tokens": { + "type": "integer", + "format": "int64", + "description": "The number of output tokens generated by the agent." + }, + "output_tokens_details": { + "$ref": "#/components/schemas/OutputTokensDetailsResource", + "description": "A breakdown of the agent's output token usage." + }, + "total_tokens": { + "type": "integer", + "format": "int64", + "description": "The total number of input and output tokens used by the agent." + } + }, + "required": [ + "input_tokens", + "input_tokens_details", + "output_tokens", + "output_tokens_details", + "total_tokens" + ], + "additionalProperties": false, + "description": "Recorded token usage for a session or turn. Usage is best effort and may change." + }, + "TurnResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn." + }, + "object": { + "$ref": "#/components/schemas/TurnObjectResource", + "description": "The object type. Always `agent.session.turn`." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session that owns the turn." + }, + "agent_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the agent that ran the turn." + }, + "subagent_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the subagent that ran the turn, if applicable." + }, + "status": { + "$ref": "#/components/schemas/TurnStatusResource", + "description": "The current status of the turn." + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "The Unix timestamp, in seconds, used to order the turn by creation time. Subagent turns use their start time, falling back to completion time or the subagent opening time when the preceding timestamps are unavailable." + }, + "started_at": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "description": "The Unix timestamp, in seconds, when the turn started." + }, + "completed_at": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "description": "The Unix timestamp, in seconds, when the turn reached a terminal state." + }, + "error": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionTurnErrorResource" + }, + { + "type": "null" + } + ], + "description": "A customer-safe error. Non-null only for a failed turn." + }, + "usage": { + "anyOf": [ + { + "$ref": "#/components/schemas/TokenUsageResource" + }, + { + "type": "null" + } + ], + "description": "Best-effort token usage for the turn, or null if unknown. Recorded usage may change." + } + }, + "required": [ + "id", + "object", + "session_id", + "agent_id", + "subagent_id", + "status", + "created_at", + "started_at", + "completed_at", + "error", + "usage" + ], + "additionalProperties": false, + "description": "The canonical public representation of a session turn." + }, + "SessionTurnListResource": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ], + "default": "list", + "x-stainless-const": true, + "description": "The object type, which is always `list`." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TurnResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The resources returned in this page, in the requested sort order." + }, + "first_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the first resource in `data`, or `null` if the page is empty." + }, + "last_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the last resource in `data`, or `null` if the page is empty. Pass this as `after` with the same order and filters." + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more resources to retrieve after this page." + } + }, + "required": [ + "object", + "data", + "first_id", + "last_id", + "has_more" + ], + "additionalProperties": false, + "description": "A page of Agents API resources, with IDs for retrieving additional pages." + }, + "ReasoningEffortResource": { + "type": "string", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "description": "The amount of reasoning effort used by an agent." + }, + "ReasoningSummaryResource": { + "type": "string", + "enum": [ + "concise", + "detailed", + "auto" + ], + "x-enumDescriptions": [ + "Returns a concise reasoning summary when supported.", + "Returns a detailed reasoning summary when supported.", + "Automatically selects the most detailed summary supported by the model." + ], + "description": "The reasoning summary format requested from an agent." + }, + "ReasoningResource": { + "type": "object", + "properties": { + "effort": { + "anyOf": [ + { + "$ref": "#/components/schemas/ReasoningEffortResource" + }, + { + "type": "null" + } + ], + "description": "The requested reasoning effort, or `null` when the model selects its own default." + }, + "summary": { + "anyOf": [ + { + "$ref": "#/components/schemas/ReasoningSummaryResource" + }, + { + "type": "null" + } + ], + "description": "The requested reasoning summary format, or `null` when summaries are disabled." + } + }, + "required": [ + "effort", + "summary" + ], + "additionalProperties": false, + "description": "The reasoning configuration used by an agent." + }, + "TextFormatResourceText": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "text" + ], + "default": "text", + "x-stainless-const": true, + "description": "The type of the object. Always `text`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Generates ordinary text without a structured-output constraint." + }, + "TextFormatResourceJsonSchema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "json_schema" + ], + "default": "json_schema", + "x-stainless-const": true, + "description": "The type of the object. Always `json_schema`." + }, + "schema": { + "type": "object", + "additionalProperties": {}, + "propertyNames": { + "type": "string", + "minLength": 0 + }, + "minProperties": 0, + "description": "The JSON Schema that generated text must match." + } + }, + "required": [ + "type", + "schema" + ], + "additionalProperties": false, + "description": "Constrains generated text to a JSON Schema." + }, + "TextFormatResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/TextFormatResourceText" + }, + { + "$ref": "#/components/schemas/TextFormatResourceJsonSchema" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "text": "#/components/schemas/TextFormatResourceText", + "json_schema": "#/components/schemas/TextFormatResourceJsonSchema" + } + }, + "x-oai-discriminator-values": [ + "text", + "json_schema" + ], + "description": "The effective output format for generated text." + }, + "VerbosityResource": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ], + "description": "The amount of text produced by an agent." + }, + "TextResource": { + "type": "object", + "properties": { + "format": { + "$ref": "#/components/schemas/TextFormatResource", + "description": "The effective output format. Defaults to ordinary text." + }, + "verbosity": { + "$ref": "#/components/schemas/VerbosityResource", + "description": "The amount of text produced by the agent. Defaults to `medium`." + } + }, + "required": [ + "format", + "verbosity" + ], + "additionalProperties": false, + "description": "The text configuration used by an agent." + }, + "ServiceTierResource": { + "type": "string", + "enum": [ + "auto", + "default", + "flex", + "priority", + "fast" + ], + "description": "The service-tier policy configured for an agent." + }, + "PersistedAgentToolResourceFunction": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "function" + ], + "default": "function", + "x-stainless-const": true, + "description": "The type of the object. Always `function`." + }, + "name": { + "type": "string", + "minLength": 0, + "description": "The name of the function." + }, + "description": { + "type": "string", + "minLength": 0, + "description": "A description of what the function does." + }, + "parameters": { + "type": "object", + "additionalProperties": {}, + "propertyNames": { + "type": "string", + "minLength": 0 + }, + "minProperties": 0, + "description": "A JSON Schema object describing the function's arguments." + }, + "defer_loading": { + "type": "boolean", + "description": "Whether the function is deferred and discovered through tool search." + } + }, + "required": [ + "type", + "name", + "description", + "parameters", + "defer_loading" + ], + "additionalProperties": false, + "description": "A function defined by the application." + }, + "PersistedAgentToolResourceToolSearch": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "tool_search" + ], + "default": "tool_search", + "x-stainless-const": true, + "description": "The type of the object. Always `tool_search`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Discovers deferred function tools and loads them into the model context." + }, + "PersistedAgentToolResourceProgrammaticToolCalling": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "programmatic_tool_calling" + ], + "default": "programmatic_tool_calling", + "x-stainless-const": true, + "description": "The type of the object. Always `programmatic_tool_calling`." + }, + "enabled": { + "type": "boolean", + "description": "Whether tools can be called from model-generated code." + } + }, + "required": [ + "type", + "enabled" + ], + "additionalProperties": false, + "description": "Enables calling tools from model-generated code." + }, + "PersistedMcpTransportResourceHttp": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "http" + ], + "default": "http", + "x-stainless-const": true, + "description": "The type of the object. Always `http`." + }, + "server_url": { + "type": "string", + "minLength": 0, + "description": "The URL of the MCP server." + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string", + "minLength": 0 + }, + "propertyNames": { + "type": "string", + "minLength": 0 + }, + "minProperties": 0, + "description": "Non-secret HTTP headers sent to the MCP server." + } + }, + "required": [ + "type", + "server_url", + "headers" + ], + "additionalProperties": false, + "description": "Connects to an MCP server over HTTP." + }, + "PersistedMcpTransportResourceStdio": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "stdio" + ], + "default": "stdio", + "x-stainless-const": true, + "description": "The type of the object. Always `stdio`." + }, + "command": { + "type": "string", + "minLength": 0, + "description": "The command used to start the MCP server." + }, + "args": { + "type": "array", + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "Arguments passed to the MCP server command." + }, + "cwd": { + "type": "string", + "minLength": 0, + "description": "The working directory used to start the MCP server." + }, + "env_vars": { + "type": "array", + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "Environment variable names inherited from the execution environment." + } + }, + "required": [ + "type", + "command", + "args", + "cwd", + "env_vars" + ], + "additionalProperties": false, + "description": "Starts an MCP server as a local process." + }, + "PersistedMcpTransportResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/PersistedMcpTransportResourceHttp" + }, + { + "$ref": "#/components/schemas/PersistedMcpTransportResourceStdio" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "http": "#/components/schemas/PersistedMcpTransportResourceHttp", + "stdio": "#/components/schemas/PersistedMcpTransportResourceStdio" + } + }, + "x-oai-discriminator-values": [ + "http", + "stdio" + ], + "description": "A credential-free transport used to connect to an MCP server." + }, + "McpConnectionOriginResource": { + "type": "string", + "enum": [ + "service", + "environment" + ], + "description": "Where outbound MCP HTTP connections originate." + }, + "PersistedAgentToolResourceMcp": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "mcp" + ], + "default": "mcp", + "x-stainless-const": true, + "description": "The type of the object. Always `mcp`." + }, + "server_label": { + "type": "string", + "minLength": 0, + "description": "A label used to identify the MCP server in tool calls." + }, + "credential_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The vault credential selected for this MCP server, if any." + }, + "transport": { + "$ref": "#/components/schemas/PersistedMcpTransportResource", + "description": "The credential-free transport used to connect to the MCP server." + }, + "request_metadata": { + "type": "object", + "additionalProperties": {}, + "propertyNames": { + "type": "string", + "minLength": 0 + }, + "minProperties": 0, + "description": "Metadata included with requests to this MCP server." + }, + "allowed_tools": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "The MCP tools the agent may call, or null when all server tools are allowed." + }, + "required": { + "type": "boolean", + "description": "Whether this MCP server must initialize before the first turn." + }, + "connection_origin": { + "$ref": "#/components/schemas/McpConnectionOriginResource", + "description": "Where outbound MCP HTTP connections originate." + } + }, + "required": [ + "type", + "server_label", + "credential_id", + "transport", + "request_metadata", + "allowed_tools", + "required", + "connection_origin" + ], + "additionalProperties": false, + "description": "Tools provided by a remote MCP server without stored credentials." + }, + "WebSearchModeResource": { + "type": "string", + "enum": [ + "disabled", + "cached", + "live" + ], + "description": "The source used for web search results." + }, + "WebSearchContextSizeResource": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ], + "description": "The amount of web search context made available to the model." + }, + "WebSearchLocationResource": { + "type": "object", + "properties": { + "country": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The two-letter ISO country code, such as `US`." + }, + "region": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The region or state name." + }, + "city": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The city name." + }, + "timezone": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The IANA timezone, such as `America/Los_Angeles`." + } + }, + "required": [ + "country", + "region", + "city", + "timezone" + ], + "additionalProperties": false, + "description": "Approximate user location used to localize web search results." + }, + "PersistedAgentToolResourceWebSearch": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "web_search" + ], + "default": "web_search", + "x-stainless-const": true, + "description": "The type of the object. Always `web_search`." + }, + "mode": { + "$ref": "#/components/schemas/WebSearchModeResource", + "description": "The source used for web search results." + }, + "context_size": { + "$ref": "#/components/schemas/WebSearchContextSizeResource", + "description": "The amount of search context made available to the model. Defaults to `medium`." + }, + "allowed_domains": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "Allowed search domains, or `null` when the search is unrestricted." + }, + "location": { + "anyOf": [ + { + "$ref": "#/components/schemas/WebSearchLocationResource" + }, + { + "type": "null" + } + ], + "description": "Approximate location used to localize search results, if provided." + } + }, + "required": [ + "type", + "mode", + "context_size", + "allowed_domains", + "location" + ], + "additionalProperties": false, + "description": "Web search." + }, + "PersistedAgentToolResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/PersistedAgentToolResourceFunction" + }, + { + "$ref": "#/components/schemas/PersistedAgentToolResourceToolSearch" + }, + { + "$ref": "#/components/schemas/PersistedAgentToolResourceProgrammaticToolCalling" + }, + { + "$ref": "#/components/schemas/PersistedAgentToolResourceMcp" + }, + { + "$ref": "#/components/schemas/PersistedAgentToolResourceWebSearch" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "function": "#/components/schemas/PersistedAgentToolResourceFunction", + "tool_search": "#/components/schemas/PersistedAgentToolResourceToolSearch", + "programmatic_tool_calling": "#/components/schemas/PersistedAgentToolResourceProgrammaticToolCalling", + "mcp": "#/components/schemas/PersistedAgentToolResourceMcp", + "web_search": "#/components/schemas/PersistedAgentToolResourceWebSearch" + } + }, + "x-oai-discriminator-values": [ + "function", + "tool_search", + "programmatic_tool_calling", + "mcp", + "web_search" + ], + "description": "A credential-free tool available to a reusable agent." + }, + "MultiAgentConfigResource": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether subagent tools are enabled. Defaults to false." + }, + "max_concurrent_subagents": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 1, + "maximum": 4294967295, + "description": "Maximum number of subagents that may run concurrently, or null when disabled. Defaults to 6 when enabled." + } + }, + "required": [ + "enabled", + "max_concurrent_subagents" + ], + "additionalProperties": false, + "description": "The resolved configuration for creating and coordinating subagents." + }, + "AgentResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the reusable agent." + }, + "object": { + "type": "string", + "enum": [ + "agent" + ], + "default": "agent", + "x-stainless-const": true, + "description": "The object type. Always `agent`." + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "The Unix timestamp, in seconds, when the agent was created." + }, + "updated_at": { + "type": "integer", + "format": "int64", + "description": "The Unix timestamp, in seconds, when the agent was last updated." + }, + "name": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "A human-readable name for the agent, or null if it is unnamed." + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string", + "minLength": 0 + }, + "propertyNames": { + "type": "string", + "minLength": 0 + }, + "minProperties": 0, + "description": "Custom string key-value pairs attached to the agent." + }, + "model": { + "type": "string", + "minLength": 0, + "description": "The requested model name used for inference." + }, + "reasoning": { + "$ref": "#/components/schemas/ReasoningResource", + "description": "The resolved reasoning configuration, including the model default for an omitted effort." + }, + "text": { + "$ref": "#/components/schemas/TextResource", + "description": "The resolved configuration for text generated by the agent." + }, + "service_tier": { + "$ref": "#/components/schemas/ServiceTierResource", + "description": "The resolved service-tier policy used for model requests." + }, + "instructions": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "Custom instructions appended to the agent's default base instructions." + }, + "tools": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PersistedAgentToolResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "Tools available to the agent." + }, + "multi_agent": { + "$ref": "#/components/schemas/MultiAgentConfigResource", + "description": "The resolved configuration for creating and coordinating subagents." + }, + "x_agents_core": { + "anyOf": [ + { + "$ref": "#/components/schemas/v1.SavedAgentCore" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "object", + "created_at", + "updated_at", + "name", + "metadata", + "model", + "reasoning", + "text", + "service_tier", + "instructions", + "tools", + "multi_agent" + ], + "additionalProperties": false, + "description": "A reusable agent scoped to the caller's project." + }, + "AgentListResource": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ], + "default": "list", + "x-stainless-const": true, + "description": "The object type, which is always `list`." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgentResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The resources returned in this page, in the requested sort order." + }, + "first_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the first resource in `data`, or `null` if the page is empty." + }, + "last_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the last resource in `data`, or `null` if the page is empty. Pass this as `after` with the same order and filters." + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more resources to retrieve after this page." + } + }, + "required": [ + "object", + "data", + "first_id", + "last_id", + "has_more" + ], + "additionalProperties": false, + "description": "A page of Agents API resources, with IDs for retrieving additional pages." + }, + "ReasoningEffortParam": { + "type": "string", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "description": "The amount of reasoning effort the model should use." + }, + "ReasoningSummaryParam": { + "type": "string", + "enum": [ + "concise", + "detailed", + "auto" + ], + "x-enumDescriptions": [ + "Returns a concise reasoning summary when supported.", + "Returns a detailed reasoning summary when supported.", + "Automatically selects the most detailed summary supported by the model." + ], + "description": "The reasoning summary format requested from the model." + }, + "ReasoningParam": { + "type": "object", + "properties": { + "effort": { + "anyOf": [ + { + "$ref": "#/components/schemas/ReasoningEffortParam" + }, + { + "type": "null" + } + ], + "description": "The amount of reasoning effort the model should use. Omission lets the model select it." + }, + "summary": { + "anyOf": [ + { + "$ref": "#/components/schemas/ReasoningSummaryParam" + }, + { + "type": "null" + } + ], + "description": "Controls whether the response includes a reasoning summary." + } + }, + "additionalProperties": false, + "description": "Reasoning configuration for the agent." + }, + "TextFormatParamText": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "text" + ], + "default": "text", + "x-stainless-const": true, + "description": "The type of the object. Always `text`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Generates ordinary text without a structured-output constraint." + }, + "TextFormatParamJsonSchema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "json_schema" + ], + "default": "json_schema", + "x-stainless-const": true, + "description": "The type of the object. Always `json_schema`." + }, + "schema": { + "type": "object", + "additionalProperties": {}, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "minProperties": 0, + "maxProperties": 1024, + "description": "The JSON Schema that generated text must match." + } + }, + "required": [ + "type", + "schema" + ], + "additionalProperties": false, + "description": "Constrains generated text to a JSON Schema." + }, + "TextFormatParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/TextFormatParamText" + }, + { + "$ref": "#/components/schemas/TextFormatParamJsonSchema" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "text": "#/components/schemas/TextFormatParamText", + "json_schema": "#/components/schemas/TextFormatParamJsonSchema" + } + }, + "x-oai-discriminator-values": [ + "text", + "json_schema" + ], + "description": "The output format for generated text." + }, + "VerbosityParam": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ], + "x-enumDescriptions": [ + "Produces less text.", + "Uses the default amount of text.", + "Produces more text." + ], + "description": "The amount of text the model should produce." + }, + "TextParam": { + "type": "object", + "properties": { + "format": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextFormatParam" + }, + { + "type": "null" + } + ], + "description": "The output format. Omission uses ordinary text (`{\"type\": \"text\"}`)." + }, + "verbosity": { + "anyOf": [ + { + "$ref": "#/components/schemas/VerbosityParam" + }, + { + "type": "null" + } + ], + "description": "The amount of text the model should produce. Defaults to `medium`, matching Responses." + } + }, + "additionalProperties": false, + "description": "Configuration for text generated by the agent." + }, + "ServiceTierParam": { + "type": "string", + "enum": [ + "auto", + "default", + "flex", + "priority", + "fast" + ], + "x-enumDescriptions": [ + "Selects the service tier automatically.", + "Uses the default service tier.", + "Uses the flex service tier.", + "Uses the priority service tier.", + "Uses the fast service tier." + ], + "description": "The service tier used for model requests." + }, + "PersistedAgentToolConfigParamFunction": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "function" + ], + "default": "function", + "x-stainless-const": true, + "description": "The type of the object. Always `function`." + }, + "name": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The name of the function." + }, + "description": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "A description of what the function does." + }, + "parameters": { + "type": "object", + "additionalProperties": {}, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "minProperties": 0, + "maxProperties": 1024, + "description": "A JSON Schema object describing the function's arguments." + }, + "defer_loading": { + "type": "boolean", + "default": false, + "description": "Whether this function is deferred and discovered through tool search. Defaults to `false`." + } + }, + "required": [ + "type", + "name", + "description", + "parameters" + ], + "additionalProperties": false, + "description": "A function defined by the application." + }, + "PersistedAgentToolConfigParamToolSearch": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "tool_search" + ], + "default": "tool_search", + "x-stainless-const": true, + "description": "The type of the object. Always `tool_search`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Discovers deferred function tools and loads them into the model context." + }, + "PersistedAgentToolConfigParamProgrammaticToolCalling": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "programmatic_tool_calling" + ], + "default": "programmatic_tool_calling", + "x-stainless-const": true, + "description": "The type of the object. Always `programmatic_tool_calling`." + }, + "enabled": { + "type": "boolean", + "default": true, + "description": "Whether tools can be called from model-generated code. Defaults to `true`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Enables calling tools from model-generated code." + }, + "PersistedMcpTransportConfigParamHttp": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "http" + ], + "default": "http", + "x-stainless-const": true, + "description": "The type of the object. Always `http`." + }, + "server_url": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The URL of the MCP server." + }, + "headers": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "minProperties": 0, + "maxProperties": 1024, + "description": "Non-secret HTTP headers sent to the MCP server." + } + }, + "required": [ + "type", + "server_url" + ], + "additionalProperties": false, + "description": "Connects to an MCP server over HTTP." + }, + "PersistedMcpTransportConfigParamStdio": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "stdio" + ], + "default": "stdio", + "x-stainless-const": true, + "description": "The type of the object. Always `stdio`." + }, + "command": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The command used to start the MCP server." + }, + "args": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "Arguments passed to the MCP server command." + }, + "cwd": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The working directory used to start the MCP server." + }, + "env_vars": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "Environment variable names to inherit from the selected execution environment." + } + }, + "required": [ + "type", + "command", + "cwd" + ], + "additionalProperties": false, + "description": "Starts an MCP server as a local process." + }, + "PersistedMcpTransportConfigParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/PersistedMcpTransportConfigParamHttp" + }, + { + "$ref": "#/components/schemas/PersistedMcpTransportConfigParamStdio" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "http": "#/components/schemas/PersistedMcpTransportConfigParamHttp", + "stdio": "#/components/schemas/PersistedMcpTransportConfigParamStdio" + } + }, + "x-oai-discriminator-values": [ + "http", + "stdio" + ], + "description": "A credential-free transport used to connect to an MCP server." + }, + "McpConnectionOriginParam": { + "type": "string", + "enum": [ + "service", + "environment" + ], + "x-enumDescriptions": [ + "Uses the Managed Agents service network.", + "Uses the session's execution environment." + ], + "description": "Where outbound MCP HTTP connections originate." + }, + "PersistedAgentToolConfigParamMcp": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "mcp" + ], + "default": "mcp", + "x-stainless-const": true, + "description": "The type of the object. Always `mcp`." + }, + "server_label": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "A label used to identify the MCP server in tool calls." + }, + "credential_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The vault credential selected for this MCP server. Optional when exactly one attached credential matches the server URL." + }, + "transport": { + "$ref": "#/components/schemas/PersistedMcpTransportConfigParam", + "description": "The credential-free transport used to connect to the MCP server." + }, + "request_metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": {}, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "minProperties": 0, + "maxProperties": 1024, + "description": "Metadata included with requests to this MCP server." + }, + "allowed_tools": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "The MCP tools the agent may call. All server tools are allowed when omitted." + }, + "required": { + "type": "boolean", + "default": false, + "description": "Whether this MCP server must initialize before the first turn. Defaults to `false`." + }, + "connection_origin": { + "anyOf": [ + { + "$ref": "#/components/schemas/McpConnectionOriginParam" + }, + { + "type": "null" + } + ], + "description": "Selects where outbound MCP HTTP connections originate." + } + }, + "required": [ + "type", + "server_label", + "transport" + ], + "additionalProperties": false, + "description": "Tools provided by a remote MCP server without stored credentials." + }, + "WebSearchModeParam": { + "type": "string", + "enum": [ + "disabled", + "cached", + "live" + ], + "x-enumDescriptions": [ + "Disables web search.", + "Uses cached search results.", + "Searches the live web." + ], + "description": "The source used for web search results." + }, + "WebSearchContextSizeParam": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ], + "description": "The amount of web search context made available to the model." + }, + "WebSearchLocationParam": { + "type": "object", + "properties": { + "country": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The two-letter ISO country code, such as `US`." + }, + "region": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The region or state name." + }, + "city": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The city name." + }, + "timezone": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The IANA timezone, such as `America/Los_Angeles`." + } + }, + "additionalProperties": false, + "description": "Approximate user location used to localize web search results." + }, + "PersistedAgentToolConfigParamWebSearch": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "web_search" + ], + "default": "web_search", + "x-stainless-const": true, + "description": "The type of the object. Always `web_search`." + }, + "mode": { + "anyOf": [ + { + "$ref": "#/components/schemas/WebSearchModeParam" + }, + { + "type": "null" + } + ], + "description": "The source used for web search results. Defaults to `live`." + }, + "context_size": { + "anyOf": [ + { + "$ref": "#/components/schemas/WebSearchContextSizeParam" + }, + { + "type": "null" + } + ], + "description": "The amount of search context made available to the model. Defaults to `medium`." + }, + "allowed_domains": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "Domains the search may include." + }, + "location": { + "anyOf": [ + { + "$ref": "#/components/schemas/WebSearchLocationParam" + }, + { + "type": "null" + } + ], + "description": "Approximate location used to localize search results." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Web search." + }, + "PersistedAgentToolConfigParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/PersistedAgentToolConfigParamFunction" + }, + { + "$ref": "#/components/schemas/PersistedAgentToolConfigParamToolSearch" + }, + { + "$ref": "#/components/schemas/PersistedAgentToolConfigParamProgrammaticToolCalling" + }, + { + "$ref": "#/components/schemas/PersistedAgentToolConfigParamMcp" + }, + { + "$ref": "#/components/schemas/PersistedAgentToolConfigParamWebSearch" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "function": "#/components/schemas/PersistedAgentToolConfigParamFunction", + "tool_search": "#/components/schemas/PersistedAgentToolConfigParamToolSearch", + "programmatic_tool_calling": "#/components/schemas/PersistedAgentToolConfigParamProgrammaticToolCalling", + "mcp": "#/components/schemas/PersistedAgentToolConfigParamMcp", + "web_search": "#/components/schemas/PersistedAgentToolConfigParamWebSearch" + } + }, + "x-oai-discriminator-values": [ + "function", + "tool_search", + "programmatic_tool_calling", + "mcp", + "web_search" + ], + "description": "A tool that can be stored on a reusable agent without session credentials." + }, + "MultiAgentConfigCurrentParam": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether subagent tools are enabled." + }, + "max_concurrent_subagents": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 4294967295, + "description": "Maximum number of subagents that may run concurrently. Defaults to 6." + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false, + "description": "Explicit configuration for creating and coordinating subagents." + }, + "CreateAgentParams": { + "type": "object", + "properties": { + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string", + "minLength": 0, + "maxLength": 512 + }, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "minProperties": 0, + "maxProperties": 16, + "description": "Up to 16 string key-value pairs, with keys up to 64 and values up to 512 characters. Omission or null defaults to an empty map." + }, + "name": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 128, + "description": "A human-readable name for the agent. Omission or null leaves the agent unnamed." + }, + "model": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The model to use for the agent. The requested model name is preserved." + }, + "reasoning": { + "anyOf": [ + { + "$ref": "#/components/schemas/ReasoningParam" + }, + { + "type": "null" + } + ], + "description": "Configuration for model reasoning. Omission uses the model's default effort." + }, + "text": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextParam" + }, + { + "type": "null" + } + ], + "description": "Configuration for generated text. Defaults to the `text` format and medium verbosity." + }, + "service_tier": { + "anyOf": [ + { + "$ref": "#/components/schemas/ServiceTierParam" + }, + { + "type": "null" + } + ], + "description": "The service tier used for model requests. Defaults to `auto`." + }, + "instructions": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "Additional instructions appended to the agent's default base instructions. Omit or set to null to add no custom instructions." + }, + "tools": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/PersistedAgentToolConfigParam" + }, + "minItems": 0, + "maxItems": 2000, + "description": "Tools available to the agent. Defaults to an empty list." + }, + "multi_agent": { + "anyOf": [ + { + "$ref": "#/components/schemas/MultiAgentConfigCurrentParam" + }, + { + "type": "null" + } + ], + "description": "Configuration for creating and coordinating subagents. Subagent tools are disabled by default." + }, + "x_agents_core": { + "anyOf": [ + { + "$ref": "#/components/schemas/v1.SavedAgentCoreInput" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "model" + ], + "additionalProperties": false, + "description": "Parameters for creating a reusable agent." + }, + "UpdateAgentParams": { + "type": "object", + "properties": { + "model": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The model to use for the agent. The requested model name is preserved." + }, + "reasoning": { + "anyOf": [ + { + "$ref": "#/components/schemas/ReasoningParam" + }, + { + "type": "null" + } + ], + "description": "Configuration for model reasoning. Omit to keep the current settings; pass `null` to reset to the model's default effort." + }, + "text": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextParam" + }, + { + "type": "null" + } + ], + "description": "Configuration for text generated by the agent." + }, + "service_tier": { + "anyOf": [ + { + "$ref": "#/components/schemas/ServiceTierParam" + }, + { + "type": "null" + } + ], + "description": "The service tier used for model requests." + }, + "instructions": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "Additional instructions appended to the agent's default base instructions. Omit to leave unchanged." + }, + "multi_agent": { + "anyOf": [ + { + "$ref": "#/components/schemas/MultiAgentConfigCurrentParam" + }, + { + "type": "null" + } + ], + "description": "Configuration for creating and coordinating subagents." + }, + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string", + "minLength": 0, + "maxLength": 512 + }, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "minProperties": 0, + "maxProperties": 16, + "description": "Replaces all metadata. Omit to leave unchanged, or pass null or {} to clear it. Up to 16 string key-value pairs, with keys up to 64 and values up to 512 characters." + }, + "name": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 128, + "description": "A replacement name. Omit to leave unchanged, or pass null to clear it." + }, + "tools": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/PersistedAgentToolConfigParam" + }, + "minItems": 0, + "maxItems": 2000, + "description": "Tools available to the agent." + }, + "x_agents_core": { + "anyOf": [ + { + "$ref": "#/components/schemas/v1.SavedAgentCoreInput" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "description": "Fields to replace on an existing reusable agent." + }, + "DeletedAgentResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the deleted agent." + }, + "object": { + "type": "string", + "enum": [ + "agent.deleted" + ], + "default": "agent.deleted", + "x-stainless-const": true, + "description": "The object type. Always `agent.deleted`." + }, + "deleted": { + "type": "boolean", + "description": "Whether the agent was deleted. Always `true`." + } + }, + "required": [ + "id", + "object", + "deleted" + ], + "additionalProperties": false, + "description": "A deleted reusable agent." + }, + "EnvironmentPackagesResource": { + "type": "object", + "properties": { + "python": { + "type": "array", + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "Python packages installed in the environment." + }, + "system": { + "type": "array", + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "System packages installed in the environment." + }, + "npm": { + "type": "array", + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "npm packages installed globally in the environment." + } + }, + "required": [ + "python", + "system", + "npm" + ], + "additionalProperties": false, + "description": "Packages installed in an OpenAI-hosted environment." + }, + "NetworkAccessResource": { + "type": "string", + "enum": [ + "enabled", + "disabled", + "restricted" + ], + "x-enumDescriptions": [ + "Allows unrestricted network access.", + "Disables network access.", + "Allows access only to configured domains." + ], + "description": "The network access mode for an OpenAI-hosted environment." + }, + "NetworkPolicyResource": { + "type": "object", + "properties": { + "access": { + "$ref": "#/components/schemas/NetworkAccessResource", + "description": "The environment's network access mode." + }, + "allowed_domains": { + "type": "array", + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "Domains the environment may access when network access is restricted." + } + }, + "required": [ + "access", + "allowed_domains" + ], + "additionalProperties": false, + "description": "Network access for an OpenAI-hosted environment." + }, + "HostedTemplateSkillResourceSkillReference": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "skill_reference" + ], + "default": "skill_reference", + "x-stainless-const": true, + "description": "The type of the object. Always `skill_reference`." + }, + "skill_id": { + "type": "string", + "minLength": 0, + "description": "The referenced skill ID." + }, + "version": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The requested version selector, including `latest`." + } + }, + "required": [ + "type", + "skill_id", + "version" + ], + "additionalProperties": false, + "description": "A skill resolved afresh from the Skills API whenever a session starts." + }, + "HostedTemplateSkillResourceInline": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "inline" + ], + "default": "inline", + "x-stainless-const": true, + "description": "The type of the object. Always `inline`." + }, + "name": { + "type": "string", + "minLength": 0, + "description": "The skill name declared in `SKILL.md`." + }, + "description": { + "type": "string", + "minLength": 0, + "description": "The skill description declared in `SKILL.md`." + } + }, + "required": [ + "type", + "name", + "description" + ], + "additionalProperties": false, + "description": "Safe metadata for an inline skill archive." + }, + "HostedTemplateSkillResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/HostedTemplateSkillResourceSkillReference" + }, + { + "$ref": "#/components/schemas/HostedTemplateSkillResourceInline" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "skill_reference": "#/components/schemas/HostedTemplateSkillResourceSkillReference", + "inline": "#/components/schemas/HostedTemplateSkillResourceInline" + } + }, + "x-oai-discriminator-values": [ + "skill_reference", + "inline" + ], + "description": "Safe metadata for a skill configured by an environment template." + }, + "HostedTemplateFileResourceFileId": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "file_id" + ], + "default": "file_id", + "x-stainless-const": true, + "description": "The type of the object. Always `file_id`." + }, + "file_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the uploaded file." + }, + "path": { + "type": "string", + "minLength": 0, + "description": "The file's absolute path inside the environment." + } + }, + "required": [ + "type", + "file_id", + "path" + ], + "additionalProperties": false, + "description": "A project-scoped Files API reference resolved separately for each session." + }, + "HostedTemplateFileResourceInline": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "inline" + ], + "default": "inline", + "x-stainless-const": true, + "description": "The type of the object. Always `inline`." + }, + "path": { + "type": "string", + "minLength": 0, + "description": "The file's absolute path inside the environment." + }, + "size_bytes": { + "type": "integer", + "format": "int64", + "minimum": 0, + "description": "The decoded size of the inline file in bytes." + } + }, + "required": [ + "type", + "path", + "size_bytes" + ], + "additionalProperties": false, + "description": "Metadata for confidential inline file contents." + }, + "HostedTemplateFileResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/HostedTemplateFileResourceFileId" + }, + { + "$ref": "#/components/schemas/HostedTemplateFileResourceInline" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "file_id": "#/components/schemas/HostedTemplateFileResourceFileId", + "inline": "#/components/schemas/HostedTemplateFileResourceInline" + } + }, + "x-oai-discriminator-values": [ + "file_id", + "inline" + ], + "description": "Safe metadata for a file configured by an environment template." + }, + "EnvironmentTemplateResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the reusable environment template." + }, + "name": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "An optional human-readable display name for the template." + }, + "object": { + "type": "string", + "enum": [ + "agent.environment.template" + ], + "default": "agent.environment.template", + "x-stainless-const": true, + "description": "The object type. Always `agent.environment.template`." + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "The Unix timestamp, in seconds, when the template was created." + }, + "updated_at": { + "type": "integer", + "format": "int64", + "description": "The Unix timestamp, in seconds, when the template was last updated." + }, + "packages": { + "$ref": "#/components/schemas/EnvironmentPackagesResource", + "description": "Packages installed in each fresh OpenAI-hosted environment." + }, + "network": { + "$ref": "#/components/schemas/NetworkPolicyResource", + "description": "Runtime network access for each OpenAI-hosted environment." + }, + "capability_directories": { + "type": "array", + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "Directories that expose capabilities to the agent." + }, + "skills": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HostedTemplateSkillResource" + }, + "minItems": 0, + "maxItems": 200, + "description": "Safe skill metadata, preserving unresolved version selectors." + }, + "plugins": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HostedPluginResource" + }, + "minItems": 0, + "maxItems": 32, + "description": "Safe plugin metadata, excluding inline archive contents." + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HostedTemplateFileResource" + }, + "minItems": 0, + "maxItems": 50, + "description": "Safe file metadata, excluding contents and session-scoped file IDs." + } + }, + "required": [ + "id", + "name", + "object", + "created_at", + "updated_at", + "packages", + "network", + "capability_directories", + "skills", + "plugins", + "files" + ], + "additionalProperties": false, + "description": "Reusable configuration that provisions a fresh OpenAI-hosted environment for each session." + }, + "EnvironmentTemplateListResource": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ], + "default": "list", + "x-stainless-const": true, + "description": "The object type, which is always `list`." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EnvironmentTemplateResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The resources returned in this page, in the requested sort order." + }, + "first_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the first resource in `data`, or `null` if the page is empty." + }, + "last_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the last resource in `data`, or `null` if the page is empty. Pass this as `after` with the same order and filters." + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more resources to retrieve after this page." + } + }, + "required": [ + "object", + "data", + "first_id", + "last_id", + "has_more" + ], + "additionalProperties": false, + "description": "A page of Agents API resources, with IDs for retrieving additional pages." + }, + "EnvironmentPackagesParam": { + "type": "object", + "properties": { + "python": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "Python packages to install. Defaults to an empty list." + }, + "system": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "System packages to install. Defaults to an empty list." + }, + "npm": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "npm packages to install globally. Defaults to an empty list." + } + }, + "additionalProperties": false, + "description": "Packages to install in an OpenAI-hosted environment." + }, + "SetupCommandParam": { + "type": "object", + "properties": { + "command": { + "type": "string", + "minLength": 0, + "maxLength": 65536, + "description": "The shell command to execute." + }, + "cwd": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 4096, + "description": "The absolute working directory. Defaults to `/workspace`." + } + }, + "required": [ + "command" + ], + "additionalProperties": false, + "description": "A confidential setup command executed before the hosted agent starts." + }, + "NetworkAccessParam": { + "type": "string", + "enum": [ + "enabled", + "disabled", + "restricted" + ], + "x-enumDescriptions": [ + "Allows unrestricted network access, matching an omitted network policy.", + "Disables network access.", + "Allows access only to configured domains." + ], + "description": "The network access mode for an OpenAI-hosted environment." + }, + "NetworkPolicyParam": { + "type": "object", + "properties": { + "access": { + "$ref": "#/components/schemas/NetworkAccessParam", + "description": "The environment's network access mode." + }, + "allowed_domains": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "Domains the environment may access when network access is restricted." + } + }, + "required": [ + "access" + ], + "additionalProperties": false, + "description": "Network access for an OpenAI-hosted environment." + }, + "HostedSkillParamSkillReference": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "skill_reference" + ], + "default": "skill_reference", + "x-stainless-const": true, + "description": "The type of the object. Always `skill_reference`." + }, + "skill_id": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "description": "The ID of the skill created through `/v1/skills`." + }, + "version": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The skill version, a positive integer or `latest`; omission selects the default." + } + }, + "required": [ + "type", + "skill_id" + ], + "additionalProperties": false, + "description": "References a skill uploaded through the Skills API." + }, + "InlineCapabilitySourceParamBase64": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "base64" + ], + "default": "base64", + "x-stainless-const": true, + "description": "The type of the object. Always `base64`." + }, + "media_type": { + "type": "string", + "enum": [ + "application/zip" + ], + "default": "application/zip", + "x-stainless-const": true, + "x-enumDescriptions": [ + "A ZIP archive." + ], + "description": "The archive media type, always `application/zip`." + }, + "data": { + "type": "string", + "minLength": 1, + "maxLength": 70254592, + "description": "Standard-base64 encoded ZIP archive bytes." + } + }, + "required": [ + "type", + "media_type", + "data" + ], + "additionalProperties": false, + "description": "Provides ZIP bytes encoded with standard base64." + }, + "InlineCapabilitySourceParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/InlineCapabilitySourceParamBase64" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "base64": "#/components/schemas/InlineCapabilitySourceParamBase64" + } + }, + "x-oai-discriminator-values": [ + "base64" + ], + "description": "The encoded ZIP archive for an inline skill or plugin." + }, + "HostedSkillParamInline": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "inline" + ], + "default": "inline", + "x-stainless-const": true, + "description": "The type of the object. Always `inline`." + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "description": "The skill name declared in `SKILL.md`." + }, + "description": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The skill description declared in `SKILL.md`." + }, + "source": { + "$ref": "#/components/schemas/InlineCapabilitySourceParam", + "description": "The inline ZIP archive." + } + }, + "required": [ + "type", + "name", + "description", + "source" + ], + "additionalProperties": false, + "description": "Supplies a skill ZIP directly in the session request." + }, + "HostedSkillParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/HostedSkillParamSkillReference" + }, + { + "$ref": "#/components/schemas/HostedSkillParamInline" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "skill_reference": "#/components/schemas/HostedSkillParamSkillReference", + "inline": "#/components/schemas/HostedSkillParamInline" + } + }, + "x-oai-discriminator-values": [ + "skill_reference", + "inline" + ], + "description": "A skill installed in an OpenAI-hosted environment." + }, + "HostedPluginParamInline": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "inline" + ], + "default": "inline", + "x-stainless-const": true, + "description": "The type of the object. Always `inline`." + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "description": "The plugin name declared in `.codex-plugin/plugin.json`." + }, + "description": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The plugin description declared in `.codex-plugin/plugin.json`." + }, + "source": { + "$ref": "#/components/schemas/InlineCapabilitySourceParam", + "description": "The inline ZIP archive." + } + }, + "required": [ + "type", + "name", + "description", + "source" + ], + "additionalProperties": false, + "description": "Supplies a plugin ZIP directly in the session request." + }, + "HostedPluginParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/HostedPluginParamInline" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "inline": "#/components/schemas/HostedPluginParamInline" + } + }, + "x-oai-discriminator-values": [ + "inline" + ], + "description": "A plugin installed in an OpenAI-hosted environment." + }, + "CreateEnvironmentTemplateParams": { + "type": "object", + "properties": { + "packages": { + "anyOf": [ + { + "$ref": "#/components/schemas/EnvironmentPackagesParam" + }, + { + "type": "null" + } + ], + "description": "Packages to install in the environment. Defaults to empty package lists." + }, + "setup_commands": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/SetupCommandParam" + }, + "minItems": 0, + "maxItems": 16, + "description": "Ordered, confidential setup commands. Command bodies are never returned." + }, + "network": { + "anyOf": [ + { + "$ref": "#/components/schemas/NetworkPolicyParam" + }, + { + "type": "null" + } + ], + "description": "Network access policy for the environment. Defaults to enabled." + }, + "env": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "minProperties": 0, + "maxProperties": 1024, + "description": "Environment variables made available to the agent." + }, + "capability_directories": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "Directories that contain capabilities exposed to the agent. Defaults to an empty list." + }, + "skills": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/HostedSkillParam" + }, + "minItems": 0, + "maxItems": 200, + "description": "Skills referenced by ID or provided as inline ZIP archives. Defaults to an empty list." + }, + "plugins": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/HostedPluginParam" + }, + "minItems": 0, + "maxItems": 32, + "description": "Plugins provided as inline ZIP archives. Defaults to an empty list." + }, + "files": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/HostedEnvironmentFileParam" + }, + "minItems": 0, + "maxItems": 50, + "description": "Files available before the agent starts. Defaults to an empty list." + }, + "name": { + "type": [ + "string", + "null" + ], + "minLength": 1, + "maxLength": 256, + "description": "An optional human-readable display name for the template." + } + }, + "additionalProperties": false, + "description": "Parameters for creating a reusable, project-scoped OpenAI-hosted environment template." + }, + "UpdateEnvironmentTemplateParams": { + "type": "object", + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "minLength": 1, + "maxLength": 256, + "description": "A replacement human-readable display name, or `null` to clear the name." + }, + "packages": { + "anyOf": [ + { + "$ref": "#/components/schemas/EnvironmentPackagesParam" + }, + { + "type": "null" + } + ], + "description": "Packages installed before the runtime network policy applies." + }, + "setup_commands": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/SetupCommandParam" + }, + "minItems": 0, + "maxItems": 16, + "description": "Replacement confidential setup commands, never included in returned resources." + }, + "network": { + "anyOf": [ + { + "$ref": "#/components/schemas/NetworkPolicyParam" + }, + { + "type": "null" + } + ], + "description": "Network access available after setup completes." + }, + "env": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "minProperties": 0, + "maxProperties": 1024, + "description": "Replacement confidential environment values." + }, + "capability_directories": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "Directories that expose capabilities to the agent." + }, + "skills": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/HostedSkillParam" + }, + "minItems": 0, + "maxItems": 200, + "description": "Replacement skill configuration installed for each new session." + }, + "plugins": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/HostedPluginParam" + }, + "minItems": 0, + "maxItems": 32, + "description": "Replacement plugin configuration installed for each new session." + }, + "files": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/HostedEnvironmentFileParam" + }, + "minItems": 0, + "maxItems": 50, + "description": "Replacement file configuration materialized for each new session." + } + }, + "additionalProperties": false, + "description": "Fields to replace on an existing reusable OpenAI-hosted environment template." + }, + "DeletedEnvironmentTemplateResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the deleted environment template." + }, + "object": { + "type": "string", + "enum": [ + "agent.environment.template.deleted" + ], + "default": "agent.environment.template.deleted", + "x-stainless-const": true, + "description": "The object type. Always `agent.environment.template.deleted`." + }, + "deleted": { + "type": "boolean", + "description": "Whether the environment template was deleted. Always `true`." + } + }, + "required": [ + "id", + "object", + "deleted" + ], + "additionalProperties": false, + "description": "A deleted reusable environment template." + }, + "SessionStatusResource": { + "type": "string", + "enum": [ + "idle", + "in_progress", + "requires_action", + "failed" + ], + "x-enumDescriptions": [ + "The session has no turn in progress and is ready for input. A hosted environment may still be provisioning.", + "The session is processing a turn.", + "The session is waiting for one or more required actions.", + "The session failed." + ], + "description": "The current status of a session." + }, + "SessionRequiredActionResourceFunctionCall": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "function_call" + ], + "default": "function_call", + "x-stainless-const": true, + "description": "The type of the object. Always `function_call`." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that requested the function call." + }, + "call_id": { + "type": "string", + "minLength": 0, + "description": "The ID to include when submitting the function result." + }, + "name": { + "type": "string", + "minLength": 0, + "description": "The function name." + }, + "arguments": { + "description": "The arguments supplied by the model." + } + }, + "required": [ + "type", + "turn_id", + "call_id", + "name", + "arguments" + ], + "additionalProperties": false, + "description": "Run a function tool and submit its result." + }, + "SessionRequiredActionResourceEnvironmentConnection": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "environment_connection" + ], + "default": "environment_connection", + "x-stainless-const": true, + "description": "The type of the object. Always `environment_connection`." + }, + "environment_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the environment to reconnect." + } + }, + "required": [ + "type", + "environment_id" + ], + "additionalProperties": false, + "description": "Reconnect a session environment." + }, + "SessionRequiredActionResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/SessionRequiredActionResourceFunctionCall" + }, + { + "$ref": "#/components/schemas/SessionRequiredActionResourceEnvironmentConnection" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "function_call": "#/components/schemas/SessionRequiredActionResourceFunctionCall", + "environment_connection": "#/components/schemas/SessionRequiredActionResourceEnvironmentConnection" + } + }, + "x-oai-discriminator-values": [ + "function_call", + "environment_connection" + ], + "description": "An action that must be completed before a session can continue." + }, + "AgentToolResourceFunction": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "function" + ], + "default": "function", + "x-stainless-const": true, + "description": "The type of the object. Always `function`." + }, + "name": { + "type": "string", + "minLength": 0, + "description": "The name of the function." + }, + "description": { + "type": "string", + "minLength": 0, + "description": "A description of what the function does." + }, + "parameters": { + "type": "object", + "additionalProperties": {}, + "propertyNames": { + "type": "string", + "minLength": 0 + }, + "minProperties": 0, + "description": "A JSON Schema object describing the function's arguments." + }, + "defer_loading": { + "type": "boolean", + "description": "Whether the function is deferred and discovered through tool search." + } + }, + "required": [ + "type", + "name", + "description", + "parameters", + "defer_loading" + ], + "additionalProperties": false, + "description": "A function defined by the application." + }, + "AgentToolResourceProgrammaticToolCalling": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "programmatic_tool_calling" + ], + "default": "programmatic_tool_calling", + "x-stainless-const": true, + "description": "The type of the object. Always `programmatic_tool_calling`." + }, + "enabled": { + "type": "boolean", + "description": "Whether tools can be called from model-generated code." + } + }, + "required": [ + "type", + "enabled" + ], + "additionalProperties": false, + "description": "Enables calling tools from model-generated code." + }, + "McpTransportResourceHttp": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "http" + ], + "default": "http", + "x-stainless-const": true, + "description": "The type of the object. Always `http`." + }, + "server_url": { + "type": "string", + "minLength": 0, + "description": "The URL of the MCP server." + } + }, + "required": [ + "type", + "server_url" + ], + "additionalProperties": false, + "description": "Connects to an MCP server over HTTP." + }, + "McpTransportResourceStdio": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "stdio" + ], + "default": "stdio", + "x-stainless-const": true, + "description": "The type of the object. Always `stdio`." + }, + "command": { + "type": "string", + "minLength": 0, + "description": "The command used to start the MCP server." + }, + "args": { + "type": "array", + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "Arguments passed to the MCP server command." + }, + "cwd": { + "type": "string", + "minLength": 0, + "description": "The working directory used to start the MCP server." + }, + "env_vars": { + "type": "array", + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "Environment variable names inherited from the execution environment." + } + }, + "required": [ + "type", + "command", + "args", + "cwd", + "env_vars" + ], + "additionalProperties": false, + "description": "Starts an MCP server as a local process." + }, + "McpTransportResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/McpTransportResourceHttp" + }, + { + "$ref": "#/components/schemas/McpTransportResourceStdio" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "http": "#/components/schemas/McpTransportResourceHttp", + "stdio": "#/components/schemas/McpTransportResourceStdio" + } + }, + "x-oai-discriminator-values": [ + "http", + "stdio" + ], + "description": "The transport used to connect to an MCP server." + }, + "AgentToolResourceMcp": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "mcp" + ], + "default": "mcp", + "x-stainless-const": true, + "description": "The type of the object. Always `mcp`." + }, + "server_label": { + "type": "string", + "minLength": 0, + "description": "A label used to identify the MCP server in tool calls." + }, + "credential_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The attached vault credential selected for this MCP server, if any. Optional when exactly one attached credential matches the server URL." + }, + "transport": { + "$ref": "#/components/schemas/McpTransportResource", + "description": "The transport used to connect to the MCP server." + }, + "request_metadata": { + "type": "object", + "additionalProperties": {}, + "propertyNames": { + "type": "string", + "minLength": 0 + }, + "minProperties": 0, + "description": "Metadata included with requests to this MCP server." + }, + "allowed_tools": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "The MCP tools the agent may call." + }, + "required": { + "type": "boolean", + "description": "Whether this MCP server must initialize before the first turn." + }, + "connection_origin": { + "$ref": "#/components/schemas/McpConnectionOriginResource", + "description": "Where outbound MCP HTTP connections originate." + } + }, + "required": [ + "type", + "server_label", + "credential_id", + "transport", + "request_metadata", + "allowed_tools", + "required", + "connection_origin" + ], + "additionalProperties": false, + "description": "Tools provided by a remote MCP server." + }, + "AgentToolResourceWebSearch": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "web_search" + ], + "default": "web_search", + "x-stainless-const": true, + "description": "The type of the object. Always `web_search`." + }, + "mode": { + "$ref": "#/components/schemas/WebSearchModeResource", + "description": "The source used for web search results." + }, + "context_size": { + "$ref": "#/components/schemas/WebSearchContextSizeResource", + "description": "The amount of search context made available to the model. Defaults to `medium`." + }, + "allowed_domains": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "Allowed search domains, or `null` when the search is unrestricted." + }, + "location": { + "anyOf": [ + { + "$ref": "#/components/schemas/WebSearchLocationResource" + }, + { + "type": "null" + } + ], + "description": "Approximate location used to localize search results, if provided." + } + }, + "required": [ + "type", + "mode", + "context_size", + "allowed_domains", + "location" + ], + "additionalProperties": false, + "description": "Web search." + }, + "AgentToolResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/AgentToolResourceFunction" + }, + { + "$ref": "#/components/schemas/AgentToolResourceProgrammaticToolCalling" + }, + { + "$ref": "#/components/schemas/AgentToolResourceMcp" + }, + { + "$ref": "#/components/schemas/AgentToolResourceWebSearch" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "function": "#/components/schemas/AgentToolResourceFunction", + "programmatic_tool_calling": "#/components/schemas/AgentToolResourceProgrammaticToolCalling", + "mcp": "#/components/schemas/AgentToolResourceMcp", + "web_search": "#/components/schemas/AgentToolResourceWebSearch" + } + }, + "x-oai-discriminator-values": [ + "function", + "programmatic_tool_calling", + "mcp", + "web_search" + ], + "description": "A tool available to the agent." + }, + "SessionAgentResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the agent." + }, + "name": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The reusable agent's name when the session was created, or null if no name was saved. Later changes to the agent's name do not affect this value." + }, + "model": { + "type": "string", + "minLength": 0, + "description": "The model used by the agent." + }, + "reasoning": { + "$ref": "#/components/schemas/ReasoningResource", + "description": "The agent's reasoning configuration." + }, + "text": { + "$ref": "#/components/schemas/TextResource", + "description": "Configuration for text generated by the agent." + }, + "service_tier": { + "$ref": "#/components/schemas/ServiceTierResource", + "description": "The effective service-tier policy for model requests. Defaults to `auto`." + }, + "instructions": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "Custom instructions appended to the agent's default base instructions." + }, + "tools": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgentToolResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "Tools available to the agent." + }, + "multi_agent": { + "$ref": "#/components/schemas/MultiAgentConfigResource", + "description": "Configuration for creating and coordinating subagents." + }, + "x_agents_core": { + "anyOf": [ + { + "$ref": "#/components/schemas/v1.AgentsCore" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name", + "model", + "reasoning", + "text", + "service_tier", + "instructions", + "tools", + "multi_agent" + ], + "additionalProperties": false, + "description": "The effective agent configuration for a session." + }, + "EnvironmentResourceNone": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "none" + ], + "default": "none", + "x-stainless-const": true, + "description": "The type of the object. Always `none`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "The session talks to CCA without selecting or provisioning an execution environment." + }, + "EnvironmentResourceOpenaiHosted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "openai_hosted" + ], + "default": "openai_hosted", + "x-stainless-const": true, + "description": "The type of the object. Always `openai_hosted`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The public ID of the environment." + }, + "packages": { + "$ref": "#/components/schemas/EnvironmentPackagesResource", + "description": "Packages installed in the environment." + }, + "network": { + "$ref": "#/components/schemas/NetworkPolicyResource", + "description": "The effective network access policy for the environment." + }, + "capability_directories": { + "type": "array", + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "Directories that contain capabilities exposed to the agent." + }, + "skills": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HostedSkillResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "Skills installed in the environment, excluding their archive contents." + }, + "plugins": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HostedPluginResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "Plugins installed in the environment, excluding their archive contents." + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HostedEnvironmentFileResource" + }, + "minItems": 0, + "maxItems": 50, + "description": "Files available in the environment, excluding their contents." + } + }, + "required": [ + "type", + "id", + "packages", + "network", + "capability_directories", + "skills", + "plugins", + "files" + ], + "additionalProperties": false, + "description": "An environment hosted by OpenAI." + }, + "EnvironmentResourceSelfHosted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "self_hosted" + ], + "default": "self_hosted", + "x-stainless-const": true, + "description": "The type of the object. Always `self_hosted`." + }, + "remote_url": { + "type": "string", + "minLength": 0, + "description": "Pass this URL unchanged to `codex exec-server --remote` when connecting this environment." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The public ID of the environment." + }, + "workspace_directory": { + "type": "string", + "minLength": 0, + "description": "The absolute project directory inside the environment. Defaults to `/workspace`." + }, + "capability_directories": { + "type": "array", + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "Directories that contain capabilities exposed to the agent." + } + }, + "required": [ + "type", + "remote_url", + "id", + "workspace_directory", + "capability_directories" + ], + "additionalProperties": false, + "description": "An environment hosted by the application." + }, + "EnvironmentResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/EnvironmentResourceNone" + }, + { + "$ref": "#/components/schemas/EnvironmentResourceOpenaiHosted" + }, + { + "$ref": "#/components/schemas/EnvironmentResourceSelfHosted" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "none": "#/components/schemas/EnvironmentResourceNone", + "openai_hosted": "#/components/schemas/EnvironmentResourceOpenaiHosted", + "self_hosted": "#/components/schemas/EnvironmentResourceSelfHosted" + } + }, + "x-oai-discriminator-values": [ + "none", + "openai_hosted", + "self_hosted" + ], + "description": "The execution environment for a session." + }, + "SessionResource": { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string", + "minLength": 0 + }, + "propertyNames": { + "type": "string", + "minLength": 0 + }, + "minProperties": 0, + "description": "Custom string key-value pairs attached to the session." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session." + }, + "object": { + "type": "string", + "enum": [ + "agent.session" + ], + "default": "agent.session", + "x-stainless-const": true, + "description": "The object type. Always `agent.session`." + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "The Unix timestamp, in seconds, when the session was created." + }, + "last_active_at": { + "type": "integer", + "format": "int64", + "description": "The Unix timestamp, in seconds, when the session was last active." + }, + "status": { + "$ref": "#/components/schemas/SessionStatusResource", + "description": "The current status of the session." + }, + "required_actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionRequiredActionResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "Actions that must be completed before the session can continue." + }, + "error": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The error that caused the session to fail, if any." + }, + "agent": { + "$ref": "#/components/schemas/SessionAgentResource", + "description": "The agent running in the session." + }, + "environment": { + "$ref": "#/components/schemas/EnvironmentResource", + "description": "The execution environment for the session." + }, + "vault_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0, + "maxItems": 2000, + "description": "The IDs of vaults made available to the session." + }, + "usage": { + "anyOf": [ + { + "$ref": "#/components/schemas/TokenUsageResource" + }, + { + "type": "null" + } + ], + "description": "Best-effort token usage for the session, or null if unknown. Recorded usage may change." + }, + "x_agents_core": { + "anyOf": [ + { + "$ref": "#/components/schemas/v1.SessionCore" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "metadata", + "id", + "object", + "created_at", + "last_active_at", + "status", + "required_actions", + "error", + "agent", + "environment", + "vault_ids", + "usage" + ], + "additionalProperties": false, + "description": "A Managed Agents session." + }, + "SessionListResource": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ], + "default": "list", + "x-stainless-const": true, + "description": "The object type, which is always `list`." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The resources returned in this page, in the requested sort order." + }, + "first_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the first resource in `data`, or `null` if the page is empty." + }, + "last_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the last resource in `data`, or `null` if the page is empty. Pass this as `after` with the same order and filters." + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more resources to retrieve after this page." + } + }, + "required": [ + "object", + "data", + "first_id", + "last_id", + "has_more" + ], + "additionalProperties": false, + "description": "A page of Agents API resources, with IDs for retrieving additional pages." + }, + "AgentToolConfigParamFunction": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "function" + ], + "default": "function", + "x-stainless-const": true, + "description": "The type of the object. Always `function`." + }, + "name": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The name of the function." + }, + "description": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "A description of what the function does." + }, + "parameters": { + "type": "object", + "additionalProperties": {}, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "minProperties": 0, + "maxProperties": 1024, + "description": "A JSON Schema object describing the function's arguments." + }, + "defer_loading": { + "type": "boolean", + "default": false, + "description": "Whether this function is deferred and discovered through tool search. Defaults to `false`." + } + }, + "required": [ + "type", + "name", + "description", + "parameters" + ], + "additionalProperties": false, + "description": "A function defined by the application." + }, + "AgentToolConfigParamToolSearch": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "tool_search" + ], + "default": "tool_search", + "x-stainless-const": true, + "description": "The type of the object. Always `tool_search`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Discovers deferred function tools and loads them into the model context." + }, + "AgentToolConfigParamProgrammaticToolCalling": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "programmatic_tool_calling" + ], + "default": "programmatic_tool_calling", + "x-stainless-const": true, + "description": "The type of the object. Always `programmatic_tool_calling`." + }, + "enabled": { + "type": "boolean", + "default": true, + "description": "Whether tools can be called from model-generated code. Defaults to `true`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Enables calling tools from model-generated code." + }, + "McpTransportConfigParamHttp": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "http" + ], + "default": "http", + "x-stainless-const": true, + "description": "The type of the object. Always `http`." + }, + "server_url": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The URL of the MCP server." + }, + "authorization": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The authorization value sent to the MCP server, if any." + }, + "headers": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "minProperties": 0, + "maxProperties": 1024, + "description": "Additional HTTP headers sent to the MCP server." + } + }, + "required": [ + "type", + "server_url" + ], + "additionalProperties": false, + "description": "Connects to an MCP server over HTTP." + }, + "McpTransportConfigParamStdio": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "stdio" + ], + "default": "stdio", + "x-stainless-const": true, + "description": "The type of the object. Always `stdio`." + }, + "command": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The command used to start the MCP server." + }, + "args": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "Arguments passed to the MCP server command." + }, + "cwd": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The working directory used to start the MCP server." + }, + "env": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "minProperties": 0, + "maxProperties": 1024, + "description": "Environment variables set for the MCP server process." + }, + "env_vars": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "Environment variable names to inherit from the selected execution environment." + } + }, + "required": [ + "type", + "command", + "cwd" + ], + "additionalProperties": false, + "description": "Starts an MCP server as a local process." + }, + "McpTransportConfigParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/McpTransportConfigParamHttp" + }, + { + "$ref": "#/components/schemas/McpTransportConfigParamStdio" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "http": "#/components/schemas/McpTransportConfigParamHttp", + "stdio": "#/components/schemas/McpTransportConfigParamStdio" + } + }, + "x-oai-discriminator-values": [ + "http", + "stdio" + ], + "description": "The transport used to connect to an MCP server." + }, + "AgentToolConfigParamMcp": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "mcp" + ], + "default": "mcp", + "x-stainless-const": true, + "description": "The type of the object. Always `mcp`." + }, + "server_label": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "A label used to identify the MCP server in tool calls." + }, + "credential_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The attached vault credential used to authenticate this MCP server. Optional when exactly one attached credential matches the server URL." + }, + "transport": { + "$ref": "#/components/schemas/McpTransportConfigParam", + "description": "The transport used to connect to the MCP server." + }, + "request_metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": {}, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "minProperties": 0, + "maxProperties": 1024, + "description": "Metadata included with requests to this MCP server." + }, + "allowed_tools": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "The MCP tools the agent may call. All server tools are allowed when omitted." + }, + "required": { + "type": "boolean", + "default": false, + "description": "Whether this MCP server must initialize before the first turn. Defaults to `false`." + }, + "connection_origin": { + "anyOf": [ + { + "$ref": "#/components/schemas/McpConnectionOriginParam" + }, + { + "type": "null" + } + ], + "description": "Selects where outbound MCP HTTP connections originate. Omitted or `service` uses the Managed Agents service network; `environment` uses the session's selected environment." + } + }, + "required": [ + "type", + "server_label", + "transport" + ], + "additionalProperties": false, + "description": "Tools provided by a remote MCP server." + }, + "AgentToolConfigParamWebSearch": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "web_search" + ], + "default": "web_search", + "x-stainless-const": true, + "description": "The type of the object. Always `web_search`." + }, + "mode": { + "anyOf": [ + { + "$ref": "#/components/schemas/WebSearchModeParam" + }, + { + "type": "null" + } + ], + "description": "The source used for web search results. Defaults to `live`." + }, + "context_size": { + "anyOf": [ + { + "$ref": "#/components/schemas/WebSearchContextSizeParam" + }, + { + "type": "null" + } + ], + "description": "The amount of search context made available to the model. Defaults to `medium`." + }, + "allowed_domains": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "Domains the search may include." + }, + "location": { + "anyOf": [ + { + "$ref": "#/components/schemas/WebSearchLocationParam" + }, + { + "type": "null" + } + ], + "description": "Approximate location used to localize search results." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Web search." + }, + "AgentToolConfigParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/AgentToolConfigParamFunction" + }, + { + "$ref": "#/components/schemas/AgentToolConfigParamToolSearch" + }, + { + "$ref": "#/components/schemas/AgentToolConfigParamProgrammaticToolCalling" + }, + { + "$ref": "#/components/schemas/AgentToolConfigParamMcp" + }, + { + "$ref": "#/components/schemas/AgentToolConfigParamWebSearch" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "function": "#/components/schemas/AgentToolConfigParamFunction", + "tool_search": "#/components/schemas/AgentToolConfigParamToolSearch", + "programmatic_tool_calling": "#/components/schemas/AgentToolConfigParamProgrammaticToolCalling", + "mcp": "#/components/schemas/AgentToolConfigParamMcp", + "web_search": "#/components/schemas/AgentToolConfigParamWebSearch" + } + }, + "x-oai-discriminator-values": [ + "function", + "tool_search", + "programmatic_tool_calling", + "mcp", + "web_search" + ], + "description": "A tool available to the agent." + }, + "SessionAgentConfigParam": { + "type": "object", + "properties": { + "model": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The model to use for the agent. The requested model name is preserved." + }, + "reasoning": { + "anyOf": [ + { + "$ref": "#/components/schemas/ReasoningParam" + }, + { + "type": "null" + } + ], + "description": "Configuration for model reasoning. Omit to keep the current settings; pass `null` to reset to the model's default effort." + }, + "text": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextParam" + }, + { + "type": "null" + } + ], + "description": "Configuration for text generated by the agent." + }, + "service_tier": { + "anyOf": [ + { + "$ref": "#/components/schemas/ServiceTierParam" + }, + { + "type": "null" + } + ], + "description": "The service tier used for model requests." + }, + "instructions": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "Additional instructions appended to the agent's default base instructions. Omit to leave unchanged." + }, + "multi_agent": { + "anyOf": [ + { + "$ref": "#/components/schemas/MultiAgentConfigCurrentParam" + }, + { + "type": "null" + } + ], + "description": "Configuration for creating and coordinating subagents." + }, + "tools": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/AgentToolConfigParam" + }, + "minItems": 0, + "maxItems": 16384, + "description": "Tools available to the agent. Omit to inherit, or pass null to clear them." + }, + "x_agents_core": { + "anyOf": [ + { + "$ref": "#/components/schemas/v1.AgentsCore" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "description": "Agent configuration for a session. Omitted fields inherit from `agent_id` when supplied. Supplied objects and arrays replace the whole field; null resets nullable fields." + }, + "EnvironmentParamNone": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "none" + ], + "default": "none", + "x-stainless-const": true, + "description": "The type of the object. Always `none`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Runs the agent without an execution environment." + }, + "EnvironmentParamOpenaiHosted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "openai_hosted" + ], + "default": "openai_hosted", + "x-stainless-const": true, + "description": "The type of the object. Always `openai_hosted`." + }, + "packages": { + "anyOf": [ + { + "$ref": "#/components/schemas/EnvironmentPackagesParam" + }, + { + "type": "null" + } + ], + "description": "Packages to install in the environment. Defaults to empty package lists." + }, + "setup_commands": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/SetupCommandParam" + }, + "minItems": 0, + "maxItems": 16, + "description": "Ordered, confidential setup commands. Command bodies are never returned." + }, + "network": { + "anyOf": [ + { + "$ref": "#/components/schemas/NetworkPolicyParam" + }, + { + "type": "null" + } + ], + "description": "Network access policy for the environment. Defaults to enabled." + }, + "env": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "minProperties": 0, + "maxProperties": 1024, + "description": "Environment variables made available to the agent." + }, + "capability_directories": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "Directories that contain capabilities exposed to the agent. Defaults to an empty list." + }, + "skills": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/HostedSkillParam" + }, + "minItems": 0, + "maxItems": 200, + "description": "Skills referenced by ID or provided as inline ZIP archives. Defaults to an empty list." + }, + "plugins": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/HostedPluginParam" + }, + "minItems": 0, + "maxItems": 32, + "description": "Plugins provided as inline ZIP archives. Defaults to an empty list." + }, + "files": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/HostedEnvironmentFileParam" + }, + "minItems": 0, + "maxItems": 50, + "description": "Files available before the agent starts. Defaults to an empty list." + }, + "environment_template_id": { + "type": "string", + "minLength": 0, + "maxLength": 64, + "description": "A reusable hosted template applied before inline session configuration. Omitted fields inherit the template; network overrides cannot broaden its policy." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "An OpenAI-hosted environment, optionally based on a reusable template." + }, + "EnvironmentParamSelfHosted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "self_hosted" + ], + "default": "self_hosted", + "x-stainless-const": true, + "description": "The type of the object. Always `self_hosted`." + }, + "workspace_directory": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "Absolute project directory inside the self-hosted environment." + }, + "capability_directories": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "Directories that contain capabilities exposed to the agent. Defaults to an empty list." + } + }, + "required": [ + "type", + "workspace_directory" + ], + "additionalProperties": false, + "description": "An application-hosted environment configured inline." + }, + "EnvironmentParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/EnvironmentParamNone" + }, + { + "$ref": "#/components/schemas/EnvironmentParamOpenaiHosted" + }, + { + "$ref": "#/components/schemas/EnvironmentParamSelfHosted" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "none": "#/components/schemas/EnvironmentParamNone", + "openai_hosted": "#/components/schemas/EnvironmentParamOpenaiHosted", + "self_hosted": "#/components/schemas/EnvironmentParamSelfHosted" + } + }, + "x-oai-discriminator-values": [ + "none", + "openai_hosted", + "self_hosted" + ], + "description": "The execution environment and optional reusable template for a session." + }, + "InputContentParamInputText": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "input_text" + ], + "default": "input_text", + "x-stainless-const": true, + "description": "The type of the object. Always `input_text`." + }, + "text": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The text sent to the model." + } + }, + "required": [ + "type", + "text" + ], + "additionalProperties": false, + "description": "Text input to the model." + }, + "InputContentParamInputImage": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "input_image" + ], + "default": "input_image", + "x-stainless-const": true, + "description": "The type of the object. Always `input_image`." + }, + "image_url": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The URL of the image sent to the model." + } + }, + "required": [ + "type", + "image_url" + ], + "additionalProperties": false, + "description": "Image input to the model." + }, + "InputContentParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/InputContentParamInputText" + }, + { + "$ref": "#/components/schemas/InputContentParamInputImage" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "input_text": "#/components/schemas/InputContentParamInputText", + "input_image": "#/components/schemas/InputContentParamInputImage" + } + }, + "x-oai-discriminator-values": [ + "input_text", + "input_image" + ], + "description": "Content included in an input message." + }, + "InputMessageParam": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "message" + ], + "default": "message", + "x-stainless-const": true, + "description": "The type of the input item. Always `message`." + }, + "role": { + "type": "string", + "enum": [ + "user" + ], + "default": "user", + "x-stainless-const": true, + "description": "The role of the message author. Always `user`." + }, + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InputContentParam" + }, + "minItems": 0, + "maxItems": 16384, + "description": "The content of the message." + } + }, + "required": [ + "role", + "content" + ], + "additionalProperties": false, + "description": "A user message submitted to a session." + }, + "CreateSessionInputParam": { + "oneOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 1048576 + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/InputMessageParam" + }, + "minItems": 0, + "maxItems": 16384 + } + ], + "description": "Initial input submitted when creating a session." + }, + "CreateAgentSessionParams": { + "type": "object", + "properties": { + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string", + "minLength": 0, + "maxLength": 512 + }, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "minProperties": 0, + "maxProperties": 16, + "description": "Up to 16 string key-value pairs, with keys up to 64 and values up to 512 characters. Omission or null defaults to an empty map." + }, + "agent": { + "$ref": "#/components/schemas/SessionAgentConfigParam", + "description": "Agent configuration. With `agent_id`, supplied fields override the saved agent for this session. Without `agent_id`, `model` is required." + }, + "agent_id": { + "type": "string", + "minLength": 0, + "maxLength": 64, + "description": "The ID of a saved reusable agent. Omit `agent` to use its configuration unchanged." + }, + "environment": { + "$ref": "#/components/schemas/EnvironmentParam", + "description": "An inline execution environment or a reference to an environment template." + }, + "vault_ids": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "minItems": 0, + "maxItems": 16384, + "description": "The IDs of vaults made available to the session." + }, + "input": { + "anyOf": [ + { + "$ref": "#/components/schemas/CreateSessionInputParam" + }, + { + "type": "null" + } + ], + "description": "Initial input to submit when the session is created. A string is shorthand for a single user message. Required when `environment.type` is `none`, or when `stream` is `true` for an environment that is not `self_hosted`; optional for self-hosted and non-streaming execution environments." + }, + "stream": { + "type": "boolean", + "default": false, + "description": "Whether to stream session events as server-sent events. Defaults to `false`." + }, + "x_agents_core": { + "anyOf": [ + { + "$ref": "#/components/schemas/v1.SessionExecutionInput" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "environment" + ], + "additionalProperties": false, + "description": "Parameters for creating a Managed Agents session." + }, + "SessionErrorResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "minLength": 0, + "description": "The error type." + }, + "code": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The machine-readable error code, if any." + }, + "message": { + "type": "string", + "minLength": 0, + "description": "A customer-safe explanation of the error." + }, + "param": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The request parameter associated with the error, if any." + } + }, + "required": [ + "type", + "code", + "message", + "param" + ], + "additionalProperties": false, + "description": "An error payload with the same public fields as Responses API streaming errors." + }, + "SessionEventError": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "error" + ], + "default": "error", + "x-stainless-const": true, + "description": "The type of the object. Always `error`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "error": { + "$ref": "#/components/schemas/SessionErrorResource", + "description": "The error that occurred." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "error" + ], + "additionalProperties": false, + "description": "Emitted when a turn or session fails.", + "x-oaiMeta": { + "example": { + "type": "error", + "event_id": "event_123", + "session_id": "sess_123", + "error": { + "type": "server_error", + "code": null, + "message": "The session failed due to an internal server error.", + "param": null + } + } + } + }, + "SessionEnvironmentStatusResource": { + "type": "string", + "enum": [ + "pending", + "ready", + "connected", + "disconnected", + "failed" + ], + "x-enumDescriptions": [ + "The environment is being prepared.", + "The environment is ready to connect.", + "The environment is connected.", + "The environment is disconnected.", + "The environment failed to connect." + ], + "description": "The connection status of a session environment." + }, + "SessionEnvironmentErrorResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "minLength": 0, + "description": "The error type." + }, + "code": { + "type": "string", + "minLength": 0, + "description": "A machine-readable error code." + }, + "message": { + "type": "string", + "minLength": 0, + "description": "A human-readable error message." + } + }, + "required": [ + "type", + "code", + "message" + ], + "additionalProperties": false, + "description": "An error reported while preparing a session environment." + }, + "SessionEnvironmentStateResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The public ID of the environment." + }, + "type": { + "type": "string", + "minLength": 0, + "description": "The environment type." + }, + "status": { + "$ref": "#/components/schemas/SessionEnvironmentStatusResource", + "description": "The environment's connection status." + }, + "error": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionEnvironmentErrorResource" + }, + { + "type": "null" + } + ], + "description": "The error reported while preparing the environment, if any." + } + }, + "required": [ + "id", + "type", + "status", + "error" + ], + "additionalProperties": false, + "description": "The current state of a session environment." + }, + "SessionEventAgentSessionEnvironmentReady": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.environment.ready" + ], + "default": "agent.session.environment.ready", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.environment.ready`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "environment": { + "$ref": "#/components/schemas/SessionEnvironmentStateResource", + "description": "The current environment state." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "environment" + ], + "additionalProperties": false, + "description": "Emitted when a hosted session environment is ready to connect." + }, + "SessionEventAgentOutputCommandExecutionOutputDelta": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.output.command_execution_output.delta" + ], + "default": "agent.output.command_execution_output.delta", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.output.command_execution_output.delta`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "item_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the command execution item." + }, + "output_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the item in the turn output." + }, + "delta": { + "type": "string", + "minLength": 0, + "description": "The output text that was appended." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "item_id", + "output_index", + "delta" + ], + "additionalProperties": false, + "description": "Emitted when command execution produces an output delta." + }, + "SessionEventAgentSessionCreated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.created" + ], + "default": "agent.session.created", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.created`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session": { + "$ref": "#/components/schemas/SessionResource", + "description": "The session that was created." + } + }, + "required": [ + "type", + "event_id", + "session" + ], + "additionalProperties": false, + "description": "Emitted when a session is created." + }, + "SessionEventAgentSessionTurnCreated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.created" + ], + "default": "agent.session.turn.created", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.created`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn associated with the event." + }, + "turn": { + "$ref": "#/components/schemas/TurnResource", + "description": "The turn at the time it was created." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "turn" + ], + "additionalProperties": false, + "description": "Emitted when a turn is created." + }, + "SessionEventAgentSessionTurnInProgress": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.in_progress" + ], + "default": "agent.session.turn.in_progress", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.in_progress`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn associated with the event." + }, + "turn": { + "$ref": "#/components/schemas/TurnResource", + "description": "The turn at the time it started running." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "turn" + ], + "additionalProperties": false, + "description": "Emitted when a turn starts running." + }, + "SessionEventAgentSessionTurnCompleted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.completed" + ], + "default": "agent.session.turn.completed", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.completed`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn associated with the event." + }, + "turn": { + "$ref": "#/components/schemas/TurnResource", + "description": "The completed turn." + }, + "usage": { + "anyOf": [ + { + "$ref": "#/components/schemas/TokenUsageResource" + }, + { + "type": "null" + } + ], + "description": "Token usage by the root agent during the turn, when available." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "turn", + "usage" + ], + "additionalProperties": false, + "description": "Emitted when a turn completes." + }, + "SessionEventAgentSessionTurnFailed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.failed" + ], + "default": "agent.session.turn.failed", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.failed`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn associated with the event." + }, + "turn": { + "$ref": "#/components/schemas/TurnResource", + "description": "The failed turn." + }, + "usage": { + "anyOf": [ + { + "$ref": "#/components/schemas/TokenUsageResource" + }, + { + "type": "null" + } + ], + "description": "Token usage by the root agent during the turn, when available." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "turn", + "usage" + ], + "additionalProperties": false, + "description": "Emitted when a turn fails." + }, + "SessionEventAgentSessionTurnCancelled": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.cancelled" + ], + "default": "agent.session.turn.cancelled", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.cancelled`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn associated with the event." + }, + "turn": { + "$ref": "#/components/schemas/TurnResource", + "description": "The cancelled turn." + }, + "usage": { + "anyOf": [ + { + "$ref": "#/components/schemas/TokenUsageResource" + }, + { + "type": "null" + } + ], + "description": "Token usage by the root agent during the turn, when available." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "turn", + "usage" + ], + "additionalProperties": false, + "description": "Emitted when a turn is cancelled." + }, + "SessionEventAgentSessionTurnItemAdded": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.item.added" + ], + "default": "agent.session.turn.item.added", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.item.added`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "output_index": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the item in the turn output, when the item is agent output." + }, + "item": { + "$ref": "#/components/schemas/SessionTurnItemResource", + "description": "The item that was added." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "output_index", + "item" + ], + "additionalProperties": false, + "description": "Emitted when an item is added to a turn." + }, + "SessionEventAgentSessionIdle": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.idle" + ], + "default": "agent.session.idle", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.idle`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session": { + "$ref": "#/components/schemas/SessionResource", + "description": "The session that became idle." + } + }, + "required": [ + "type", + "event_id", + "session" + ], + "additionalProperties": false, + "description": "Emitted when a session becomes idle." + }, + "SessionEventAgentSessionInProgress": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.in_progress" + ], + "default": "agent.session.in_progress", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.in_progress`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session": { + "$ref": "#/components/schemas/SessionResource", + "description": "The session that started processing." + } + }, + "required": [ + "type", + "event_id", + "session" + ], + "additionalProperties": false, + "description": "Emitted when a session starts processing a turn." + }, + "SessionEventAgentSessionRequiresAction": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.requires_action" + ], + "default": "agent.session.requires_action", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.requires_action`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session": { + "$ref": "#/components/schemas/SessionResource", + "description": "The session and its current required actions." + } + }, + "required": [ + "type", + "event_id", + "session" + ], + "additionalProperties": false, + "description": "Emitted when a session is waiting for one or more required actions." + }, + "SessionEventAgentSessionFailed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.failed" + ], + "default": "agent.session.failed", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.failed`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session": { + "$ref": "#/components/schemas/SessionResource", + "description": "The failed session." + } + }, + "required": [ + "type", + "event_id", + "session" + ], + "additionalProperties": false, + "description": "Emitted when a session fails." + }, + "SessionEventAgentSessionEnvironmentPending": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.environment.pending" + ], + "default": "agent.session.environment.pending", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.environment.pending`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "environment": { + "$ref": "#/components/schemas/SessionEnvironmentStateResource", + "description": "The current environment state." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "environment" + ], + "additionalProperties": false, + "description": "Emitted while a session environment is being prepared." + }, + "SessionEventAgentSessionEnvironmentConnected": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.environment.connected" + ], + "default": "agent.session.environment.connected", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.environment.connected`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "environment": { + "$ref": "#/components/schemas/SessionEnvironmentStateResource", + "description": "The current environment state." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "environment" + ], + "additionalProperties": false, + "description": "Emitted when a session environment connects." + }, + "SessionEventAgentSessionEnvironmentDisconnected": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.environment.disconnected" + ], + "default": "agent.session.environment.disconnected", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.environment.disconnected`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "environment": { + "$ref": "#/components/schemas/SessionEnvironmentStateResource", + "description": "The current environment state." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "environment" + ], + "additionalProperties": false, + "description": "Emitted when a session environment disconnects." + }, + "SessionEventAgentSessionEnvironmentFailed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.environment.failed" + ], + "default": "agent.session.environment.failed", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.environment.failed`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "environment": { + "$ref": "#/components/schemas/SessionEnvironmentStateResource", + "description": "The current environment state." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "environment" + ], + "additionalProperties": false, + "description": "Emitted when a session environment fails." + }, + "SessionEventAgentSessionSubagentCreated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.subagent.created" + ], + "default": "agent.session.subagent.created", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.subagent.created`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "subagent": { + "$ref": "#/components/schemas/SubagentResource", + "description": "The subagent that was created." + } + }, + "required": [ + "type", + "event_id", + "subagent" + ], + "additionalProperties": false, + "description": "Emitted when a subagent is created." + }, + "SessionEventAgentSessionSubagentActive": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.subagent.active" + ], + "default": "agent.session.subagent.active", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.subagent.active`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "subagent": { + "$ref": "#/components/schemas/SubagentResource", + "description": "The subagent that resumed." + } + }, + "required": [ + "type", + "event_id", + "subagent" + ], + "additionalProperties": false, + "description": "Emitted when a closed subagent successfully resumes." + }, + "SessionEventAgentSessionSubagentClosed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.subagent.closed" + ], + "default": "agent.session.subagent.closed", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.subagent.closed`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "subagent": { + "$ref": "#/components/schemas/SubagentResource", + "description": "The subagent that was closed." + } + }, + "required": [ + "type", + "event_id", + "subagent" + ], + "additionalProperties": false, + "description": "Emitted when a subagent is closed." + }, + "AssistantMessageItemResource": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "message" + ], + "default": "message", + "x-stainless-const": true, + "description": "The item type. Always `message`." + }, + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the message." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the turn that contains this item." + }, + "role": { + "type": "string", + "enum": [ + "assistant" + ], + "default": "assistant", + "x-stainless-const": true, + "description": "The role of the message author. Always `assistant`." + }, + "status": { + "$ref": "#/components/schemas/OutputItemStatusResource", + "description": "The status of the message." + }, + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OutputTextResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The content of the message." + }, + "phase": { + "anyOf": [ + { + "$ref": "#/components/schemas/MessagePhaseResource" + }, + { + "type": "null" + } + ], + "description": "The phase of the assistant message." + } + }, + "required": [ + "type", + "id", + "turn_id", + "role", + "status", + "content", + "phase" + ], + "additionalProperties": false, + "description": "An assistant message produced by the agent." + }, + "AgentOutputItemResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/AssistantMessageItemResource" + }, + { + "$ref": "#/components/schemas/ReasoningItemResource" + }, + { + "$ref": "#/components/schemas/FunctionCallItemResource" + }, + { + "$ref": "#/components/schemas/McpCallItemResource" + }, + { + "$ref": "#/components/schemas/WebSearchCallItemResource" + }, + { + "$ref": "#/components/schemas/CommandExecutionItemResource" + }, + { + "$ref": "#/components/schemas/CreateSubagentCallItemResource" + }, + { + "$ref": "#/components/schemas/SendSubagentInputCallItemResource" + }, + { + "$ref": "#/components/schemas/ResumeSubagentCallItemResource" + }, + { + "$ref": "#/components/schemas/WaitForSubagentsCallItemResource" + }, + { + "$ref": "#/components/schemas/InterruptSubagentCallItemResource" + }, + { + "$ref": "#/components/schemas/CloseSubagentCallItemResource" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "message": "#/components/schemas/AssistantMessageItemResource", + "reasoning": "#/components/schemas/ReasoningItemResource", + "function_call": "#/components/schemas/FunctionCallItemResource", + "mcp_call": "#/components/schemas/McpCallItemResource", + "web_search_call": "#/components/schemas/WebSearchCallItemResource", + "command_execution": "#/components/schemas/CommandExecutionItemResource", + "interrupt_subagent_call": "#/components/schemas/InterruptSubagentCallItemResource", + "create_subagent_call": "#/components/schemas/CreateSubagentCallItemResource", + "send_subagent_input_call": "#/components/schemas/SendSubagentInputCallItemResource", + "resume_subagent_call": "#/components/schemas/ResumeSubagentCallItemResource", + "wait_for_subagents_call": "#/components/schemas/WaitForSubagentsCallItemResource", + "close_subagent_call": "#/components/schemas/CloseSubagentCallItemResource" + } + }, + "x-oai-discriminator-values": [ + "message", + "reasoning", + "function_call", + "mcp_call", + "web_search_call", + "command_execution", + "create_subagent_call", + "send_subagent_input_call", + "resume_subagent_call", + "wait_for_subagents_call", + "interrupt_subagent_call", + "close_subagent_call" + ], + "description": "An output item produced by an agent." + }, + "SessionEventAgentSessionTurnItemDone": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.item.done" + ], + "default": "agent.session.turn.item.done", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.item.done`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "output_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the output item in the turn output." + }, + "item": { + "$ref": "#/components/schemas/AgentOutputItemResource", + "description": "The completed output item." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "output_index", + "item" + ], + "additionalProperties": false, + "description": "Emitted when an output item is complete." + }, + "SessionEventAgentSessionTurnContentPartAdded": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.content_part.added" + ], + "default": "agent.session.turn.content_part.added", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.content_part.added`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "item_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the message item." + }, + "output_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the item in the turn output." + }, + "content_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the content part in the message." + }, + "part": { + "$ref": "#/components/schemas/OutputTextResource", + "description": "The initial content part." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "item_id", + "output_index", + "content_index", + "part" + ], + "additionalProperties": false, + "description": "Emitted when an output text content part is added." + }, + "SessionEventAgentSessionTurnContentPartDone": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.content_part.done" + ], + "default": "agent.session.turn.content_part.done", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.content_part.done`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "item_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the message item." + }, + "output_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the item in the turn output." + }, + "content_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the content part in the message." + }, + "part": { + "$ref": "#/components/schemas/OutputTextResource", + "description": "The completed content part." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "item_id", + "output_index", + "content_index", + "part" + ], + "additionalProperties": false, + "description": "Emitted when an output content part is complete." + }, + "SessionEventAgentSessionTurnOutputTextDelta": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.output_text.delta" + ], + "default": "agent.session.turn.output_text.delta", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.output_text.delta`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "item_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the message item." + }, + "output_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the item in the turn output." + }, + "content_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the content part in the message." + }, + "delta": { + "type": "string", + "minLength": 0, + "description": "The text that was appended." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "item_id", + "output_index", + "content_index", + "delta" + ], + "additionalProperties": false, + "description": "Emitted when text is appended to an output text content part." + }, + "SessionEventAgentSessionTurnOutputTextDone": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.output_text.done" + ], + "default": "agent.session.turn.output_text.done", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.output_text.done`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "item_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the message item." + }, + "output_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the item in the turn output." + }, + "content_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the content part in the message." + }, + "text": { + "type": "string", + "minLength": 0, + "description": "The complete output text." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "item_id", + "output_index", + "content_index", + "text" + ], + "additionalProperties": false, + "description": "Emitted when an output text content part is complete." + }, + "SessionEventAgentSessionTurnReasoningSummaryPartAdded": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.reasoning_summary_part.added" + ], + "default": "agent.session.turn.reasoning_summary_part.added", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.reasoning_summary_part.added`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "item_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the reasoning item." + }, + "output_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the item in the turn output." + }, + "summary_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the summary content part." + }, + "part": { + "$ref": "#/components/schemas/SummaryTextResource", + "description": "The initial summary part." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "item_id", + "output_index", + "summary_index", + "part" + ], + "additionalProperties": false, + "description": "Emitted when a reasoning summary content part is added." + }, + "SessionEventAgentSessionTurnReasoningSummaryPartDone": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.reasoning_summary_part.done" + ], + "default": "agent.session.turn.reasoning_summary_part.done", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.reasoning_summary_part.done`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "item_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the reasoning item." + }, + "output_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the item in the turn output." + }, + "summary_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the summary part." + }, + "part": { + "$ref": "#/components/schemas/SummaryTextResource", + "description": "The completed summary part." + }, + "status": { + "type": [ + "string", + "null" + ], + "enum": [ + "incomplete", + null + ], + "description": "Present as `incomplete` when summary generation was interrupted.", + "x-stainless-const": true + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "item_id", + "output_index", + "summary_index", + "part", + "status" + ], + "additionalProperties": false, + "description": "Emitted when a reasoning summary part is complete." + }, + "SessionEventAgentSessionTurnReasoningSummaryTextDelta": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.reasoning_summary_text.delta" + ], + "default": "agent.session.turn.reasoning_summary_text.delta", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.reasoning_summary_text.delta`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "item_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the reasoning item." + }, + "output_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the item in the turn output." + }, + "summary_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the summary content part." + }, + "delta": { + "type": "string", + "minLength": 0, + "description": "The summary text that was appended." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "item_id", + "output_index", + "summary_index", + "delta" + ], + "additionalProperties": false, + "description": "Emitted when text is appended to a reasoning summary." + }, + "SessionEventAgentSessionTurnReasoningSummaryTextDone": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.turn.reasoning_summary_text.done" + ], + "default": "agent.session.turn.reasoning_summary_text.done", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.turn.reasoning_summary_text.done`." + }, + "event_id": { + "type": "string", + "minLength": 0, + "description": "The unique ID of the event." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session associated with the event." + }, + "turn_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the turn associated with the event, when applicable." + }, + "item_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the reasoning item." + }, + "output_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the item in the turn output." + }, + "summary_index": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 4294967295, + "description": "The index of the summary content part." + }, + "text": { + "type": "string", + "minLength": 0, + "description": "The complete reasoning summary text." + } + }, + "required": [ + "type", + "event_id", + "session_id", + "turn_id", + "item_id", + "output_index", + "summary_index", + "text" + ], + "additionalProperties": false, + "description": "Emitted when a reasoning summary content part is complete." + }, + "SessionEvent": { + "oneOf": [ + { + "$ref": "#/components/schemas/SessionEventError" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionEnvironmentReady" + }, + { + "$ref": "#/components/schemas/SessionEventAgentOutputCommandExecutionOutputDelta" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionCreated" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnCreated" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnInProgress" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnCompleted" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnFailed" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnCancelled" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnItemAdded" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionIdle" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionInProgress" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionRequiresAction" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionFailed" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionEnvironmentPending" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionEnvironmentConnected" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionEnvironmentDisconnected" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionEnvironmentFailed" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionSubagentCreated" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionSubagentActive" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionSubagentClosed" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnItemDone" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnContentPartAdded" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnContentPartDone" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnOutputTextDelta" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnOutputTextDone" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnReasoningSummaryPartAdded" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnReasoningSummaryPartDone" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnReasoningSummaryTextDelta" + }, + { + "$ref": "#/components/schemas/SessionEventAgentSessionTurnReasoningSummaryTextDone" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "error": "#/components/schemas/SessionEventError", + "agent.session.environment.ready": "#/components/schemas/SessionEventAgentSessionEnvironmentReady", + "agent.output.command_execution_output.delta": "#/components/schemas/SessionEventAgentOutputCommandExecutionOutputDelta", + "agent.session.created": "#/components/schemas/SessionEventAgentSessionCreated", + "agent.session.turn.created": "#/components/schemas/SessionEventAgentSessionTurnCreated", + "agent.session.turn.in_progress": "#/components/schemas/SessionEventAgentSessionTurnInProgress", + "agent.session.turn.completed": "#/components/schemas/SessionEventAgentSessionTurnCompleted", + "agent.session.turn.failed": "#/components/schemas/SessionEventAgentSessionTurnFailed", + "agent.session.turn.cancelled": "#/components/schemas/SessionEventAgentSessionTurnCancelled", + "agent.session.turn.item.added": "#/components/schemas/SessionEventAgentSessionTurnItemAdded", + "agent.session.idle": "#/components/schemas/SessionEventAgentSessionIdle", + "agent.session.in_progress": "#/components/schemas/SessionEventAgentSessionInProgress", + "agent.session.requires_action": "#/components/schemas/SessionEventAgentSessionRequiresAction", + "agent.session.failed": "#/components/schemas/SessionEventAgentSessionFailed", + "agent.session.environment.pending": "#/components/schemas/SessionEventAgentSessionEnvironmentPending", + "agent.session.environment.connected": "#/components/schemas/SessionEventAgentSessionEnvironmentConnected", + "agent.session.environment.disconnected": "#/components/schemas/SessionEventAgentSessionEnvironmentDisconnected", + "agent.session.environment.failed": "#/components/schemas/SessionEventAgentSessionEnvironmentFailed", + "agent.session.subagent.created": "#/components/schemas/SessionEventAgentSessionSubagentCreated", + "agent.session.subagent.active": "#/components/schemas/SessionEventAgentSessionSubagentActive", + "agent.session.subagent.closed": "#/components/schemas/SessionEventAgentSessionSubagentClosed", + "agent.session.turn.item.done": "#/components/schemas/SessionEventAgentSessionTurnItemDone", + "agent.session.turn.content_part.added": "#/components/schemas/SessionEventAgentSessionTurnContentPartAdded", + "agent.session.turn.content_part.done": "#/components/schemas/SessionEventAgentSessionTurnContentPartDone", + "agent.session.turn.output_text.delta": "#/components/schemas/SessionEventAgentSessionTurnOutputTextDelta", + "agent.session.turn.output_text.done": "#/components/schemas/SessionEventAgentSessionTurnOutputTextDone", + "agent.session.turn.reasoning_summary_part.added": "#/components/schemas/SessionEventAgentSessionTurnReasoningSummaryPartAdded", + "agent.session.turn.reasoning_summary_part.done": "#/components/schemas/SessionEventAgentSessionTurnReasoningSummaryPartDone", + "agent.session.turn.reasoning_summary_text.delta": "#/components/schemas/SessionEventAgentSessionTurnReasoningSummaryTextDelta", + "agent.session.turn.reasoning_summary_text.done": "#/components/schemas/SessionEventAgentSessionTurnReasoningSummaryTextDone" + } + }, + "x-oai-discriminator-values": [ + "error", + "agent.session.environment.ready", + "agent.output.command_execution_output.delta", + "agent.session.created", + "agent.session.turn.created", + "agent.session.turn.in_progress", + "agent.session.turn.completed", + "agent.session.turn.failed", + "agent.session.turn.cancelled", + "agent.session.turn.item.added", + "agent.session.idle", + "agent.session.in_progress", + "agent.session.requires_action", + "agent.session.failed", + "agent.session.environment.pending", + "agent.session.environment.connected", + "agent.session.environment.disconnected", + "agent.session.environment.failed", + "agent.session.subagent.created", + "agent.session.subagent.active", + "agent.session.subagent.closed", + "agent.session.turn.item.done", + "agent.session.turn.content_part.added", + "agent.session.turn.content_part.done", + "agent.session.turn.output_text.delta", + "agent.session.turn.output_text.done", + "agent.session.turn.reasoning_summary_part.added", + "agent.session.turn.reasoning_summary_part.done", + "agent.session.turn.reasoning_summary_text.delta", + "agent.session.turn.reasoning_summary_text.done" + ], + "description": "An event emitted by a Managed Agents session." + }, + "UpdateAgentSessionParams": { + "type": "object", + "properties": { + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string", + "minLength": 0, + "maxLength": 512 + }, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "minProperties": 0, + "maxProperties": 16, + "description": "Replaces all metadata. Omit to leave unchanged, or pass null or {} to clear it. Up to 16 string key-value pairs, with keys up to 64 and values up to 512 characters." + } + }, + "additionalProperties": false, + "description": "Fields to update on an existing session." + }, + "DeletedSessionResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the deleted session." + }, + "object": { + "type": "string", + "enum": [ + "agent.session.deleted" + ], + "default": "agent.session.deleted", + "x-stainless-const": true, + "description": "The object type. Always `agent.session.deleted`." + }, + "deleted": { + "type": "boolean", + "description": "Whether the session has been removed from the public API. Always `true`. Physical cleanup may still be in progress." + } + }, + "required": [ + "id", + "object", + "deleted" + ], + "additionalProperties": false, + "description": "A Managed Agents session removed from the public API. Physical cleanup may continue asynchronously." + }, + "SessionArtifactResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The immutable artifact ID." + }, + "object": { + "type": "string", + "enum": [ + "agent.session.artifact" + ], + "default": "agent.session.artifact", + "x-stainless-const": true, + "description": "The object type. Always `agent.session.artifact`." + }, + "session_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the session that owns the artifact." + }, + "environment_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the environment that produced the artifact." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the completed turn that published the artifact." + }, + "path": { + "type": "string", + "minLength": 0, + "description": "The original absolute file path in the execution environment." + }, + "size_bytes": { + "type": "integer", + "format": "int64", + "minimum": 0, + "description": "The immutable artifact size in bytes." + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "The Unix timestamp, in seconds, when the artifact was published." + } + }, + "required": [ + "id", + "object", + "session_id", + "environment_id", + "turn_id", + "path", + "size_bytes", + "created_at" + ], + "additionalProperties": false, + "description": "An immutable file published by a completed hosted session turn." + }, + "SessionArtifactListResource": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ], + "default": "list", + "x-stainless-const": true, + "description": "The object type, which is always `list`." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionArtifactResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The resources returned in this page, in the requested sort order." + }, + "first_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the first resource in `data`, or `null` if the page is empty." + }, + "last_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the last resource in `data`, or `null` if the page is empty. Pass this as `after` with the same order and filters." + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more resources to retrieve after this page." + } + }, + "required": [ + "object", + "data", + "first_id", + "last_id", + "has_more" + ], + "additionalProperties": false, + "description": "A page of Agents API resources, with IDs for retrieving additional pages." + }, + "DeletedSessionArtifactResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the deleted session artifact." + }, + "object": { + "type": "string", + "enum": [ + "agent.session.artifact.deleted" + ], + "default": "agent.session.artifact.deleted", + "x-stainless-const": true, + "description": "The object type. Always `agent.session.artifact.deleted`." + }, + "deleted": { + "type": "boolean", + "description": "Whether the session artifact was deleted. Always `true`." + } + }, + "required": [ + "id", + "object", + "deleted" + ], + "additionalProperties": false, + "description": "Confirmation that an immutable session artifact was deleted." + }, + "SessionInputParamAgentSessionInputMessage": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.input.message" + ], + "default": "agent.session.input.message", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.input.message`." + }, + "input": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InputMessageParam" + }, + "minItems": 0, + "maxItems": 16384, + "description": "The user messages to add to the session." + } + }, + "required": [ + "type", + "input" + ], + "additionalProperties": false, + "description": "Adds one or more user messages and starts a turn." + }, + "SessionInputParamAgentSessionInputCancel": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.input.cancel" + ], + "default": "agent.session.input.cancel", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.input.cancel`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Cancels the session's active turn." + }, + "FunctionCallOutputParam": { + "oneOf": [ + { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/InputContentParam" + }, + "minItems": 0, + "maxItems": 16384 + } + ], + "description": "A function result represented as text or supported model-input content." + }, + "SessionInputParamAgentSessionInputToolResult": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "agent.session.input.tool_result" + ], + "default": "agent.session.input.tool_result", + "x-stainless-const": true, + "description": "The type of the object. Always `agent.session.input.tool_result`." + }, + "turn_id": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The ID of the turn that requested the function call." + }, + "call_id": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The ID of the function call." + }, + "success": { + "type": "boolean", + "description": "Whether the function call succeeded." + }, + "output": { + "anyOf": [ + { + "$ref": "#/components/schemas/FunctionCallOutputParam" + }, + { + "type": "null" + } + ], + "description": "The function result when the call succeeded." + }, + "error": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The error message when the call failed." + } + }, + "required": [ + "type", + "turn_id", + "call_id", + "success" + ], + "additionalProperties": false, + "description": "Submits the result of a function call." + }, + "SessionInputParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/SessionInputParamAgentSessionInputMessage" + }, + { + "$ref": "#/components/schemas/SessionInputParamAgentSessionInputCancel" + }, + { + "$ref": "#/components/schemas/SessionInputParamAgentSessionInputToolResult" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "agent.session.input.message": "#/components/schemas/SessionInputParamAgentSessionInputMessage", + "agent.session.input.cancel": "#/components/schemas/SessionInputParamAgentSessionInputCancel", + "agent.session.input.tool_result": "#/components/schemas/SessionInputParamAgentSessionInputToolResult" + } + }, + "x-oai-discriminator-values": [ + "agent.session.input.message", + "agent.session.input.cancel", + "agent.session.input.tool_result" + ], + "description": "Input submitted to an existing session." + }, + "CreateSessionEventsParams": { + "type": "object", + "properties": { + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionInputParam" + }, + "minItems": 0, + "maxItems": 16384, + "description": "The input events to submit to the session." + } + }, + "required": [ + "events" + ], + "additionalProperties": false, + "description": "Input events submitted to an existing session." + }, + "VaultStatusParam": { + "type": "string", + "enum": [ + "active", + "archived" + ], + "description": "Whether a vault or credential is active or archived." + }, + "VaultStatusFilterParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/VaultStatusParam" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/VaultStatusParam" + }, + "minItems": 0, + "maxItems": 16384 + } + ], + "description": "One or more lifecycle statuses to include when listing vaults or credentials." + }, + "VaultResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the vault." + }, + "object": { + "type": "string", + "enum": [ + "vault" + ], + "default": "vault", + "x-stainless-const": true, + "description": "The object type. Always `vault`." + }, + "name": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The human-readable name of the vault, if set." + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string", + "minLength": 0 + }, + "propertyNames": { + "type": "string", + "minLength": 0 + }, + "minProperties": 0, + "description": "Key-value pairs associated with the vault, such as an application or team identifier." + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "The Unix timestamp, in seconds, when the vault was created." + } + }, + "required": [ + "id", + "object", + "name", + "metadata", + "created_at" + ], + "additionalProperties": false, + "description": "A collection of credentials that agent tools can use to authenticate to MCP servers." + }, + "VaultListResource": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ], + "default": "list", + "x-stainless-const": true, + "description": "The object type, which is always `list`." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VaultResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The resources returned in this page, in the requested sort order." + }, + "first_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the first resource in `data`, or `null` if the page is empty." + }, + "last_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the last resource in `data`, or `null` if the page is empty. Pass this as `after` with the same order and filters." + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more resources to retrieve after this page." + } + }, + "required": [ + "object", + "data", + "first_id", + "last_id", + "has_more" + ], + "additionalProperties": false, + "description": "A page of Agents API resources, with IDs for retrieving additional pages." + }, + "CreateVaultParams": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 1048576, + "description": "The name is trimmed before storage. It must contain 1 to 256 UTF-8 bytes after trimming." + }, + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string", + "minLength": 0, + "maxLength": 1048576 + }, + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "minProperties": 0, + "maxProperties": 1024, + "description": "Key-value pairs to associate with the vault, such as an application or team identifier." + } + }, + "additionalProperties": false, + "description": "Parameters for creating a vault to store credentials used by agent tools." + }, + "DeletedVaultResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the deleted vault." + }, + "object": { + "type": "string", + "enum": [ + "vault.deleted" + ], + "default": "vault.deleted", + "x-stainless-const": true, + "description": "The object type. Always `vault.deleted`." + }, + "deleted": { + "type": "boolean", + "description": "Whether the resource was deleted. Always `true`." + } + }, + "required": [ + "id", + "object", + "deleted" + ], + "additionalProperties": false, + "description": "Confirmation that a vault was deleted." + }, + "McpOauthTokenEndpointAuthResourceNone": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "none" + ], + "default": "none", + "x-stainless-const": true, + "description": "The type of the object. Always `none`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Sends the client ID without a client secret." + }, + "McpOauthTokenEndpointAuthResourceClientSecretBasic": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "client_secret_basic" + ], + "default": "client_secret_basic", + "x-stainless-const": true, + "description": "The type of the object. Always `client_secret_basic`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Sends the client ID and secret using HTTP Basic authentication." + }, + "McpOauthTokenEndpointAuthResourceClientSecretPost": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "client_secret_post" + ], + "default": "client_secret_post", + "x-stainless-const": true, + "description": "The type of the object. Always `client_secret_post`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Sends the client ID and secret in the token request body." + }, + "McpOauthTokenEndpointAuthResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/McpOauthTokenEndpointAuthResourceNone" + }, + { + "$ref": "#/components/schemas/McpOauthTokenEndpointAuthResourceClientSecretBasic" + }, + { + "$ref": "#/components/schemas/McpOauthTokenEndpointAuthResourceClientSecretPost" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "none": "#/components/schemas/McpOauthTokenEndpointAuthResourceNone", + "client_secret_basic": "#/components/schemas/McpOauthTokenEndpointAuthResourceClientSecretBasic", + "client_secret_post": "#/components/schemas/McpOauthTokenEndpointAuthResourceClientSecretPost" + } + }, + "x-oai-discriminator-values": [ + "none", + "client_secret_basic", + "client_secret_post" + ], + "description": "The client authentication method used for OAuth token refresh." + }, + "McpOauthRefreshResource": { + "type": "object", + "properties": { + "token_endpoint": { + "type": "string", + "minLength": 0, + "description": "The HTTPS OAuth token endpoint used for refresh." + }, + "client_id": { + "type": "string", + "minLength": 0, + "description": "The OAuth client ID used when requesting a new access token." + }, + "resource": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The resource URI sent to the OAuth token endpoint during refresh, if configured." + }, + "scope": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "Space-separated OAuth scopes requested during refresh, if configured." + }, + "token_endpoint_auth": { + "$ref": "#/components/schemas/McpOauthTokenEndpointAuthResource", + "description": "How the OAuth client authenticates to the token endpoint, excluding its client secret." + } + }, + "required": [ + "token_endpoint", + "client_id", + "resource", + "scope", + "token_endpoint_auth" + ], + "additionalProperties": false, + "description": "Configuration used to refresh an MCP OAuth access token, excluding secret values." + }, + "VaultCredentialAuthResourceMcpOauth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "mcp_oauth" + ], + "default": "mcp_oauth", + "x-stainless-const": true, + "description": "The type of the object. Always `mcp_oauth`." + }, + "mcp_server_url": { + "type": "string", + "minLength": 0, + "description": "The HTTPS MCP server URL authorized by this credential." + }, + "expires_at": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "When the OAuth access token expires, as an RFC 3339 timestamp, if known." + }, + "refresh": { + "anyOf": [ + { + "$ref": "#/components/schemas/McpOauthRefreshResource" + }, + { + "type": "null" + } + ], + "description": "Public refresh metadata without refresh tokens or OAuth client secrets." + } + }, + "required": [ + "type", + "mcp_server_url", + "expires_at", + "refresh" + ], + "additionalProperties": false, + "description": "Public metadata for an OAuth credential; tokens and client secrets are never returned." + }, + "VaultCredentialAuthResourceStaticBearer": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "static_bearer" + ], + "default": "static_bearer", + "x-stainless-const": true, + "description": "The type of the object. Always `static_bearer`." + }, + "mcp_server_url": { + "type": "string", + "minLength": 0, + "description": "The HTTPS MCP server URL authorized by this credential." + } + }, + "required": [ + "type", + "mcp_server_url" + ], + "additionalProperties": false, + "description": "Metadata for a bearer-token credential, without automatic OAuth refresh." + }, + "VaultCredentialAuthResource": { + "oneOf": [ + { + "$ref": "#/components/schemas/VaultCredentialAuthResourceMcpOauth" + }, + { + "$ref": "#/components/schemas/VaultCredentialAuthResourceStaticBearer" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "mcp_oauth": "#/components/schemas/VaultCredentialAuthResourceMcpOauth", + "static_bearer": "#/components/schemas/VaultCredentialAuthResourceStaticBearer" + } + }, + "x-oai-discriminator-values": [ + "mcp_oauth", + "static_bearer" + ], + "description": "The MCP server and authentication configuration of a vault credential, excluding secrets." + }, + "VaultCredentialResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the credential." + }, + "object": { + "type": "string", + "enum": [ + "vault.credential" + ], + "default": "vault.credential", + "x-stainless-const": true, + "description": "The object type. Always `vault.credential`." + }, + "vault_id": { + "type": "string", + "minLength": 0, + "description": "The ID of the vault containing this credential." + }, + "name": { + "type": "string", + "minLength": 0, + "description": "The human-readable name of the credential." + }, + "auth": { + "$ref": "#/components/schemas/VaultCredentialAuthResource", + "description": "The authentication method and non-secret configuration for the MCP server." + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "The Unix timestamp, in seconds, when the credential was created." + }, + "updated_at": { + "type": "integer", + "format": "int64", + "description": "The Unix timestamp, in seconds, when the credential was last updated." + } + }, + "required": [ + "id", + "object", + "vault_id", + "name", + "auth", + "created_at", + "updated_at" + ], + "additionalProperties": false, + "description": "Metadata for a stored MCP server credential. Secret values are never returned." + }, + "VaultCredentialListResource": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ], + "default": "list", + "x-stainless-const": true, + "description": "The object type, which is always `list`." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VaultCredentialResource" + }, + "minItems": 0, + "maxItems": 2000, + "description": "The resources returned in this page, in the requested sort order." + }, + "first_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the first resource in `data`, or `null` if the page is empty." + }, + "last_id": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "description": "The ID of the last resource in `data`, or `null` if the page is empty. Pass this as `after` with the same order and filters." + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more resources to retrieve after this page." + } + }, + "required": [ + "object", + "data", + "first_id", + "last_id", + "has_more" + ], + "additionalProperties": false, + "description": "A page of Agents API resources, with IDs for retrieving additional pages." + }, + "CreateMcpOauthTokenEndpointAuthParamNone": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "none" + ], + "default": "none", + "x-stainless-const": true, + "description": "The type of the object. Always `none`." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Sends the client ID without a client secret." + }, + "CreateMcpOauthTokenEndpointAuthParamClientSecretBasic": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "client_secret_basic" + ], + "default": "client_secret_basic", + "x-stainless-const": true, + "description": "The type of the object. Always `client_secret_basic`." + }, + "client_secret": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The OAuth client secret to store. Never returned in credential resources." + } + }, + "required": [ + "type", + "client_secret" + ], + "additionalProperties": false, + "description": "Sends the client ID and secret using HTTP Basic authentication." + }, + "CreateMcpOauthTokenEndpointAuthParamClientSecretPost": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "client_secret_post" + ], + "default": "client_secret_post", + "x-stainless-const": true, + "description": "The type of the object. Always `client_secret_post`." + }, + "client_secret": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The OAuth client secret to store. Never returned in credential resources." + } + }, + "required": [ + "type", + "client_secret" + ], + "additionalProperties": false, + "description": "Sends the client ID and secret in the token request body." + }, + "CreateMcpOauthTokenEndpointAuthParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateMcpOauthTokenEndpointAuthParamNone" + }, + { + "$ref": "#/components/schemas/CreateMcpOauthTokenEndpointAuthParamClientSecretBasic" + }, + { + "$ref": "#/components/schemas/CreateMcpOauthTokenEndpointAuthParamClientSecretPost" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "none": "#/components/schemas/CreateMcpOauthTokenEndpointAuthParamNone", + "client_secret_basic": "#/components/schemas/CreateMcpOauthTokenEndpointAuthParamClientSecretBasic", + "client_secret_post": "#/components/schemas/CreateMcpOauthTokenEndpointAuthParamClientSecretPost" + } + }, + "x-oai-discriminator-values": [ + "none", + "client_secret_basic", + "client_secret_post" + ], + "description": "Client authentication credentials for OAuth token refresh." + }, + "CreateMcpOauthRefreshParam": { + "type": "object", + "properties": { + "token_endpoint": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The HTTPS OAuth token endpoint used to exchange the refresh token for a new access token." + }, + "client_id": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The OAuth client ID used when requesting a new access token." + }, + "resource": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The resource URI to send to the OAuth token endpoint during refresh, if required." + }, + "scope": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "Space-separated OAuth scopes to request during refresh, if required." + }, + "refresh_token": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The refresh token to store. This secret is never returned in credential resources." + }, + "token_endpoint_auth": { + "$ref": "#/components/schemas/CreateMcpOauthTokenEndpointAuthParam", + "description": "How the OAuth client authenticates to the token endpoint." + } + }, + "required": [ + "token_endpoint", + "client_id", + "refresh_token", + "token_endpoint_auth" + ], + "additionalProperties": false, + "description": "Configuration for refreshing the access token of an MCP OAuth credential." + }, + "CreateVaultCredentialAuthParamMcpOauth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "mcp_oauth" + ], + "default": "mcp_oauth", + "x-stainless-const": true, + "description": "The type of the object. Always `mcp_oauth`." + }, + "mcp_server_url": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The HTTPS MCP server URL authorized by this credential." + }, + "access_token": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "A write-only OAuth access token; never returned by credential resources." + }, + "expires_at": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "When the OAuth access token expires, as an RFC 3339 timestamp, if known." + }, + "refresh": { + "anyOf": [ + { + "$ref": "#/components/schemas/CreateMcpOauthRefreshParam" + }, + { + "type": "null" + } + ], + "description": "Optional refresh configuration for an HTTPS OAuth token endpoint." + } + }, + "required": [ + "type", + "mcp_server_url", + "access_token" + ], + "additionalProperties": false, + "description": "An OAuth credential for an HTTPS MCP destination." + }, + "CreateVaultCredentialAuthParamStaticBearer": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "static_bearer" + ], + "default": "static_bearer", + "x-stainless-const": true, + "description": "The type of the object. Always `static_bearer`." + }, + "mcp_server_url": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The HTTPS MCP server URL authorized by this credential." + }, + "token": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The bearer token to store. This secret is never returned in credential resources." + } + }, + "required": [ + "type", + "mcp_server_url", + "token" + ], + "additionalProperties": false, + "description": "A bearer token for an MCP server, without automatic OAuth refresh." + }, + "CreateVaultCredentialAuthParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateVaultCredentialAuthParamMcpOauth" + }, + { + "$ref": "#/components/schemas/CreateVaultCredentialAuthParamStaticBearer" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "mcp_oauth": "#/components/schemas/CreateVaultCredentialAuthParamMcpOauth", + "static_bearer": "#/components/schemas/CreateVaultCredentialAuthParamStaticBearer" + } + }, + "x-oai-discriminator-values": [ + "mcp_oauth", + "static_bearer" + ], + "description": "Authentication credentials for an MCP server used by agent tools." + }, + "CreateVaultCredentialParams": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 1048576, + "description": "The name is trimmed before storage. It must contain 1 to 256 UTF-8 bytes after trimming." + }, + "auth": { + "$ref": "#/components/schemas/CreateVaultCredentialAuthParam", + "description": "The authentication method and secret values to store for the MCP server." + } + }, + "required": [ + "auth", + "name" + ], + "additionalProperties": false, + "description": "Parameters for storing a credential that authorizes access to an MCP server." + }, + "RotateMcpOauthTokenEndpointAuthParamClientSecretBasic": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "client_secret_basic" + ], + "default": "client_secret_basic", + "x-stainless-const": true, + "description": "The type of the object. Always `client_secret_basic`." + }, + "client_secret": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The replacement OAuth client secret. Omit or pass `null` to keep the stored secret. This secret is never returned in resources." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Updates credentials sent using HTTP Basic authentication." + }, + "RotateMcpOauthTokenEndpointAuthParamClientSecretPost": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "client_secret_post" + ], + "default": "client_secret_post", + "x-stainless-const": true, + "description": "The type of the object. Always `client_secret_post`." + }, + "client_secret": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The replacement OAuth client secret. Omit or pass `null` to keep the stored secret. This secret is never returned in resources." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Updates credentials sent in the token request body." + }, + "RotateMcpOauthTokenEndpointAuthParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/RotateMcpOauthTokenEndpointAuthParamClientSecretBasic" + }, + { + "$ref": "#/components/schemas/RotateMcpOauthTokenEndpointAuthParamClientSecretPost" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "client_secret_basic": "#/components/schemas/RotateMcpOauthTokenEndpointAuthParamClientSecretBasic", + "client_secret_post": "#/components/schemas/RotateMcpOauthTokenEndpointAuthParamClientSecretPost" + } + }, + "x-oai-discriminator-values": [ + "client_secret_basic", + "client_secret_post" + ], + "description": "Client-secret updates that preserve the credential's OAuth authentication method." + }, + "RotateMcpOauthRefreshParam": { + "type": "object", + "properties": { + "refresh_token": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The replacement refresh token. Omit or pass `null` to keep the stored token. This secret is never returned in resources." + }, + "scope": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "Replacement space-separated OAuth scopes for refresh requests. Omit to keep the scopes, or pass `null` to stop sending a scope parameter." + }, + "token_endpoint_auth": { + "anyOf": [ + { + "$ref": "#/components/schemas/RotateMcpOauthTokenEndpointAuthParam" + }, + { + "type": "null" + } + ], + "description": "Client-secret updates for the existing token endpoint authentication method." + } + }, + "additionalProperties": false, + "description": "Updates to an MCP credential's existing OAuth refresh configuration." + }, + "RotateVaultCredentialAuthParamMcpOauth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "mcp_oauth" + ], + "default": "mcp_oauth", + "x-stainless-const": true, + "description": "The type of the object. Always `mcp_oauth`." + }, + "access_token": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "A write-only replacement OAuth access token." + }, + "expires_at": { + "type": [ + "string", + "null" + ], + "minLength": 0, + "maxLength": 1048576, + "description": "The replacement expiry as an RFC 3339 timestamp, or `null` to clear it. Omitting this field preserves the expiry unless a new access token is supplied, in which case the expiry is cleared." + }, + "refresh": { + "anyOf": [ + { + "$ref": "#/components/schemas/RotateMcpOauthRefreshParam" + }, + { + "type": "null" + } + ], + "description": "Optional write-only refresh-token and client-secret updates." + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Rotate an OAuth credential for an HTTPS MCP destination." + }, + "RotateVaultCredentialAuthParamStaticBearer": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "static_bearer" + ], + "default": "static_bearer", + "x-stainless-const": true, + "description": "The type of the object. Always `static_bearer`." + }, + "token": { + "type": "string", + "minLength": 0, + "maxLength": 1048576, + "description": "The replacement bearer token. This secret is never returned in credential resources." + } + }, + "required": [ + "type", + "token" + ], + "additionalProperties": false, + "description": "Replace the bearer token for the credential's MCP server." + }, + "RotateVaultCredentialAuthParam": { + "oneOf": [ + { + "$ref": "#/components/schemas/RotateVaultCredentialAuthParamMcpOauth" + }, + { + "$ref": "#/components/schemas/RotateVaultCredentialAuthParamStaticBearer" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "mcp_oauth": "#/components/schemas/RotateVaultCredentialAuthParamMcpOauth", + "static_bearer": "#/components/schemas/RotateVaultCredentialAuthParamStaticBearer" + } + }, + "x-oai-discriminator-values": [ + "mcp_oauth", + "static_bearer" + ], + "description": "Updates to a vault credential without changing its authentication method or MCP server." + }, + "RotateVaultCredentialParams": { + "type": "object", + "properties": { + "auth": { + "$ref": "#/components/schemas/RotateVaultCredentialAuthParam", + "description": "Replacement values for the credential's existing authentication method." + } + }, + "required": [ + "auth" + ], + "additionalProperties": false, + "description": "Secret, expiry, and OAuth refresh scope updates for an existing vault credential." + }, + "DeletedVaultCredentialResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 0, + "description": "The ID of the deleted credential." + }, + "object": { + "type": "string", + "enum": [ + "vault.credential.deleted" + ], + "default": "vault.credential.deleted", + "x-stainless-const": true, + "description": "The object type. Always `vault.credential.deleted`." + }, + "deleted": { + "type": "boolean", + "description": "Whether the resource was deleted. Always `true`." + } + }, + "required": [ + "id", + "object", + "deleted" + ], + "additionalProperties": false, + "description": "Confirmation that a vault credential was deleted." + }, + "v1.AgentsCore": { + "properties": { + "harness": { + "enum": [ + "claude_sdk", + "codex", + "mcode" + ], + "type": "string" + }, + "harness_config": { + "type": "object" + } + }, + "type": "object" + }, + "v1.EnvironmentInstallation": { + "properties": { + "commands": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "expires_at": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "status": { + "enum": [ + "available", + "unavailable" + ], + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "v1.ModelProviderInput": { + "properties": { + "api_key": { + "type": "string" + }, + "base_url": { + "type": "string" + }, + "context_window": { + "type": "integer" + }, + "max_output_tokens": { + "type": "integer" + }, + "protocol": { + "enum": [ + "anthropic", + "responses", + "chat_completions" + ], + "type": "string" + } + }, + "required": [ + "api_key", + "base_url", + "protocol" + ], + "type": "object" + }, + "v1.ModelProviderView": { + "properties": { + "api_key_configured": { + "type": "boolean" + }, + "base_url": { + "type": "string" + }, + "context_window": { + "type": "integer" + }, + "max_output_tokens": { + "type": "integer" + }, + "protocol": { + "enum": [ + "anthropic", + "responses", + "chat_completions" + ], + "type": "string" + } + }, + "required": [ + "api_key_configured", + "base_url", + "protocol" + ], + "type": "object" + }, + "v1.SavedAgentCore": { + "properties": { + "harness": { + "enum": [ + "claude_sdk", + "codex", + "mcode" + ], + "type": "string" + }, + "harness_config": { + "type": "object" + }, + "model_provider": { + "$ref": "#/components/schemas/v1.ModelProviderView" + } + }, + "type": "object" + }, + "v1.SavedAgentCoreInput": { + "properties": { + "harness": { + "enum": [ + "claude_sdk", + "codex", + "mcode" + ], + "type": "string" + }, + "harness_config": { + "type": "object" + }, + "model_provider": { + "anyOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/v1.ModelProviderInput" + } + ] + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "v1.SessionCore": { + "properties": { + "installation": { + "$ref": "#/components/schemas/v1.EnvironmentInstallation" + } + }, + "type": "object" + }, + "v1.SessionExecutionInput": { + "properties": { + "environment": { + "description": "Environment supplies placement-independent preparation through the Core extension.", + "type": "object" + }, + "harness_config": { + "type": "object" + }, + "model_provider": { + "$ref": "#/components/schemas/v1.ModelProviderInput" + } + }, + "type": "object" + } + }, + "responses": { + "TooManyRequests": { + "description": "The request was rejected because a rate limit was exceeded.", + "headers": { + "Retry-After": { + "description": "The minimum number of seconds to wait before retrying. This header is returned when the server has computed a retry delay and may be omitted for 429 responses that require user action.", + "schema": { + "type": "integer", + "minimum": 1 + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "securitySchemes": { + "ProjectKey": { + "type": "http", + "scheme": "bearer", + "description": "OpenAgentCore Project API key." + } + } + }, + "x-oaiMeta": { + "navigationGroups": [ + { + "id": "responses", + "title": "Responses API" + }, + { + "id": "webhooks", + "title": "Webhooks" + }, + { + "id": "endpoints", + "title": "Platform APIs" + }, + { + "id": "vector_stores", + "title": "Vector stores" + }, + { + "id": "chatkit", + "title": "ChatKit", + "beta": true + }, + { + "id": "containers", + "title": "Containers" + }, + { + "id": "live", + "title": "Live (alpha)" + }, + { + "id": "realtime", + "title": "Realtime" + }, + { + "id": "chat", + "title": "Chat Completions" + }, + { + "id": "assistants", + "title": "Assistants", + "deprecated": true + }, + { + "id": "administration", + "title": "Administration" + }, + { + "id": "legacy", + "title": "Legacy" + } + ], + "groups": [ + { + "id": "responses-streaming", + "title": "Streaming events", + "description": "When you [create a Response](https://developers.openai.com/api/reference/resources/responses/methods/create) with\n`stream` set to `true`, the server will emit server-sent events to the\nclient as the Response is generated. This section contains the events that\nare emitted by the server.\n\n[Learn more about streaming responses](https://developers.openai.com/api/docs/guides/streaming-responses).\n", + "navigationGroup": "responses", + "sections": [ + { + "type": "object", + "key": "ResponseCreatedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseFailedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseIncompleteEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseOutputItemAddedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseOutputItemDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseContentPartAddedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseContentPartDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseTextDeltaEvent", + "path": "response/output_text/delta" + }, + { + "type": "object", + "key": "ResponseTextDoneEvent", + "path": "response/output_text/done" + }, + { + "type": "object", + "key": "ResponseRefusalDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseRefusalDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseFunctionCallArgumentsDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseFunctionCallArgumentsDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseFileSearchCallInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseFileSearchCallSearchingEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseFileSearchCallCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseWebSearchCallInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseWebSearchCallSearchingEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseWebSearchCallCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseReasoningSummaryPartAddedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseReasoningSummaryPartDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseReasoningSummaryTextDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseReasoningSummaryTextDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseReasoningTextDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseReasoningTextDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseImageGenCallCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseImageGenCallGeneratingEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseImageGenCallInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseImageGenCallPartialImageEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPCallArgumentsDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPCallArgumentsDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPCallCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPCallFailedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPCallInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPListToolsCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPListToolsFailedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPListToolsInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCodeInterpreterCallInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCodeInterpreterCallInterpretingEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCodeInterpreterCallCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCodeInterpreterCallCodeDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCodeInterpreterCallCodeDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseOutputTextAnnotationAddedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseQueuedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCustomToolCallInputDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCustomToolCallInputDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseErrorEvent", + "path": "" + } + ] + }, + { + "id": "responses-websocket-client-events", + "title": "Client events", + "description": "Events sent by the client over a Responses API WebSocket connection.\n", + "navigationGroup": "responses", + "sections": [ + { + "type": "object", + "key": "ResponsesClientEventResponseCreate", + "path": "" + }, + { + "type": "object", + "key": "ResponseSteerEvent", + "path": "" + } + ] + }, + { + "id": "responses-websocket-server-events", + "title": "Server events (WebSocket only)", + "description": "Events emitted only over a Responses API WebSocket connection.\n", + "navigationGroup": "responses", + "sections": [ + { + "type": "object", + "key": "ResponseSteerAcceptedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseSteerPendingEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseSteerFailedEvent", + "path": "" + } + ] + }, + { + "id": "responses-websocket-shared-events", + "title": "Server events", + "description": "These events use the same payloads over WebSocket and\n[HTTP streaming](https://developers.openai.com/api/reference/resources/responses/streaming-events).\n", + "navigationGroup": "responses", + "sections": [ + { + "type": "object", + "key": "ResponseCreatedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseFailedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseIncompleteEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseOutputItemAddedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseOutputItemDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseContentPartAddedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseContentPartDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseTextDeltaEvent", + "path": "response/output_text/delta" + }, + { + "type": "object", + "key": "ResponseTextDoneEvent", + "path": "response/output_text/done" + }, + { + "type": "object", + "key": "ResponseRefusalDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseRefusalDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseFunctionCallArgumentsDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseFunctionCallArgumentsDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseFileSearchCallInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseFileSearchCallSearchingEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseFileSearchCallCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseWebSearchCallInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseWebSearchCallSearchingEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseWebSearchCallCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseReasoningSummaryPartAddedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseReasoningSummaryPartDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseReasoningSummaryTextDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseReasoningSummaryTextDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseReasoningTextDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseReasoningTextDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseImageGenCallCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseImageGenCallGeneratingEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseImageGenCallInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseImageGenCallPartialImageEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPCallArgumentsDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPCallArgumentsDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPCallCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPCallFailedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPCallInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPListToolsCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPListToolsFailedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseMCPListToolsInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCodeInterpreterCallInProgressEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCodeInterpreterCallInterpretingEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCodeInterpreterCallCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCodeInterpreterCallCodeDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCodeInterpreterCallCodeDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseOutputTextAnnotationAddedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseQueuedEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCustomToolCallInputDeltaEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseCustomToolCallInputDoneEvent", + "path": "" + }, + { + "type": "object", + "key": "ResponseErrorEvent", + "path": "" + } + ] + }, + { + "id": "safety-alerts", + "title": "Safety Alerts", + "description": "Retrieve approved safety alerts with an API key. Project keys require\n`api.safety.alerts.read` and can read alerts from their project.\n", + "navigationGroup": "endpoints", + "sections": [ + { + "type": "endpoint", + "key": "Getprojectsafetyalert", + "path": "retrieve" + }, + { + "type": "object", + "key": "SafetyAlertResource", + "path": "object" + } + ] + }, + { + "id": "webhook-events", + "title": "Webhook Events", + "description": "Webhooks are HTTP requests sent by OpenAI to a URL you specify when certain\nevents happen during the course of API usage.\n\n[Learn more about webhooks](https://developers.openai.com/api/docs/guides/webhooks).\n", + "navigationGroup": "webhooks", + "sections": [ + { + "type": "object", + "key": "WebhookResponseCompleted", + "path": "" + }, + { + "type": "object", + "key": "WebhookResponseCancelled", + "path": "" + }, + { + "type": "object", + "key": "WebhookResponseFailed", + "path": "" + }, + { + "type": "object", + "key": "WebhookResponseIncomplete", + "path": "" + }, + { + "type": "object", + "key": "WebhookBatchCompleted", + "path": "" + }, + { + "type": "object", + "key": "WebhookBatchCancelled", + "path": "" + }, + { + "type": "object", + "key": "WebhookBatchExpired", + "path": "" + }, + { + "type": "object", + "key": "WebhookBatchFailed", + "path": "" + }, + { + "type": "object", + "key": "WebhookFineTuningJobSucceeded", + "path": "" + }, + { + "type": "object", + "key": "WebhookFineTuningJobFailed", + "path": "" + }, + { + "type": "object", + "key": "WebhookFineTuningJobCancelled", + "path": "" + }, + { + "type": "object", + "key": "WebhookEvalRunSucceeded", + "path": "" + }, + { + "type": "object", + "key": "WebhookEvalRunFailed", + "path": "" + }, + { + "type": "object", + "key": "WebhookEvalRunCanceled", + "path": "" + }, + { + "type": "object", + "key": "WebhookRealtimeCallIncoming", + "path": "" + }, + { + "type": "object", + "key": "WebhookLiveCallIncoming", + "path": "" + }, + { + "type": "object", + "key": "WebhookLiveTransportIncoming", + "path": "" + }, + { + "type": "object", + "key": "WebhookSafetyAlertCreated", + "path": "" + }, + { + "type": "object", + "key": "WebhookSafetyOrgAlertCreated", + "path": "" + } + ] + }, + { + "id": "images-streaming", + "title": "Image Streaming", + "description": "Stream image generation and editing in real time with server-sent events.\n[Learn more about image streaming](https://developers.openai.com/api/docs/guides/image-generation).\n", + "navigationGroup": "endpoints", + "sections": [ + { + "type": "object", + "key": "ImageGenPartialImageEvent", + "path": "" + }, + { + "type": "object", + "key": "ImageGenCompletedEvent", + "path": "" + }, + { + "type": "object", + "key": "ImageEditPartialImageEvent", + "path": "" + }, + { + "type": "object", + "key": "ImageEditCompletedEvent", + "path": "" + } + ] + }, + { + "id": "realtime-client-events", + "title": "Client events", + "description": "These are events that the OpenAI Realtime WebSocket server will accept from the client.\n", + "navigationGroup": "realtime", + "sections": [ + { + "type": "object", + "key": "RealtimeClientEventSessionUpdate", + "path": "" + }, + { + "type": "object", + "key": "RealtimeClientEventInputAudioBufferAppend", + "path": "" + }, + { + "type": "object", + "key": "RealtimeClientEventInputAudioBufferCommit", + "path": "" + }, + { + "type": "object", + "key": "RealtimeClientEventInputAudioBufferClear", + "path": "" + }, + { + "type": "object", + "key": "RealtimeClientEventConversationItemCreate", + "path": "" + }, + { + "type": "object", + "key": "RealtimeClientEventConversationItemRetrieve", + "path": "" + }, + { + "type": "object", + "key": "RealtimeClientEventConversationItemTruncate", + "path": "" + }, + { + "type": "object", + "key": "RealtimeClientEventConversationItemDelete", + "path": "" + }, + { + "type": "object", + "key": "RealtimeClientEventResponseCreate", + "path": "" + }, + { + "type": "object", + "key": "RealtimeClientEventResponseCancel", + "path": "" + }, + { + "type": "object", + "key": "RealtimeClientEventOutputAudioBufferClear", + "path": "" + } + ] + }, + { + "id": "realtime-server-events", + "title": "Server events", + "description": "These are events emitted from the OpenAI Realtime WebSocket server to the client.\n", + "navigationGroup": "realtime", + "sections": [ + { + "type": "object", + "key": "RealtimeServerEventError", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventSessionCreated", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventSessionUpdated", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventConversationItemAdded", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventConversationItemDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventConversationItemRetrieved", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventConversationItemInputAudioTranscriptionCompleted", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventConversationItemInputAudioTranscriptionDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventConversationItemInputAudioTranscriptionSegment", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventConversationItemInputAudioTranscriptionFailed", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventConversationItemTruncated", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventConversationItemDeleted", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventInputAudioBufferCommitted", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventInputAudioBufferDtmfEventReceived", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventInputAudioBufferCleared", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventInputAudioBufferSpeechStarted", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventInputAudioBufferSpeechStopped", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventInputAudioBufferTimeoutTriggered", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventOutputAudioBufferStarted", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventOutputAudioBufferStopped", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventOutputAudioBufferCleared", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseCreated", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseOutputItemAdded", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseOutputItemDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseContentPartAdded", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseContentPartDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseTextDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseTextDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseAudioTranscriptDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseAudioTranscriptDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseAudioDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseAudioDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseFunctionCallArgumentsDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseFunctionCallArgumentsDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseMCPCallArgumentsDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseMCPCallArgumentsDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseMCPCallInProgress", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseMCPCallCompleted", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventResponseMCPCallFailed", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventMCPListToolsInProgress", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventMCPListToolsCompleted", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventMCPListToolsFailed", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventRateLimitsUpdated", + "path": "" + } + ] + }, + { + "id": "realtime-translation-client-events", + "title": "Translation client events", + "description": "These are events that the OpenAI Realtime Translation WebSocket server will accept from the client.\n", + "navigationGroup": "realtime", + "sections": [ + { + "type": "object", + "key": "RealtimeTranslationClientEventSessionUpdate", + "path": "" + }, + { + "type": "object", + "key": "RealtimeTranslationClientEventInputAudioBufferAppend", + "path": "" + }, + { + "type": "object", + "key": "RealtimeTranslationClientEventSessionClose", + "path": "" + } + ] + }, + { + "id": "realtime-translation-server-events", + "title": "Translation server events", + "description": "These are events emitted from the OpenAI Realtime Translation WebSocket server to the client.\n", + "navigationGroup": "realtime", + "sections": [ + { + "type": "object", + "key": "RealtimeServerEventError", + "path": "" + }, + { + "type": "object", + "key": "RealtimeTranslationServerEventSessionCreated", + "path": "" + }, + { + "type": "object", + "key": "RealtimeTranslationServerEventSessionUpdated", + "path": "" + }, + { + "type": "object", + "key": "RealtimeTranslationServerEventSessionClosed", + "path": "" + }, + { + "type": "object", + "key": "RealtimeTranslationServerEventSessionInputTranscriptDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeTranslationServerEventSessionOutputTranscriptDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeTranslationServerEventSessionOutputAudioDelta", + "path": "" + } + ] + }, + { + "id": "chat-streaming", + "title": "Streaming", + "description": "Stream Chat Completions in real time. Receive chunks of completions\nreturned from the model using server-sent events.\n[Learn more](https://developers.openai.com/api/docs/guides/streaming-responses).\n", + "navigationGroup": "chat", + "sections": [ + { + "type": "object", + "key": "CreateChatCompletionStreamResponse", + "path": "streaming" + } + ] + }, + { + "id": "assistants-streaming", + "title": "Streaming", + "beta": true, + "description": "Stream the result of executing a Run or resuming a Run after submitting tool outputs.\nYou can stream events from the [Create Thread and Run](https://developers.openai.com/api/docs/assistants/migration),\n[Create Run](https://developers.openai.com/api/docs/assistants/migration), and [Submit Tool Outputs](https://developers.openai.com/api/docs/assistants/migration)\nendpoints by passing `\"stream\": true`. The response will be a [Server-Sent events](https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events) stream.\nOur Node and Python SDKs provide helpful utilities to make streaming easy. Reference the\n[Assistants API quickstart](https://developers.openai.com/api/docs/assistants/migration) to learn more.\n", + "navigationGroup": "assistants", + "sections": [ + { + "type": "object", + "key": "AssistantStreamEvent", + "path": "events" + } + ] + }, + { + "id": "realtime-beta-client-events", + "title": "Realtime Beta client events", + "description": "These are events that the OpenAI Realtime WebSocket server will accept from the client.\n", + "navigationGroup": "legacy", + "sections": [ + { + "type": "object", + "key": "RealtimeBetaClientEventSessionUpdate", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaClientEventInputAudioBufferAppend", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaClientEventInputAudioBufferCommit", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaClientEventInputAudioBufferClear", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaClientEventConversationItemCreate", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaClientEventConversationItemRetrieve", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaClientEventConversationItemTruncate", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaClientEventConversationItemDelete", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaClientEventResponseCreate", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaClientEventResponseCancel", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaClientEventTranscriptionSessionUpdate", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaClientEventOutputAudioBufferClear", + "path": "" + } + ] + }, + { + "id": "realtime-beta-server-events", + "title": "Realtime Beta server events", + "description": "These are events emitted from the OpenAI Realtime WebSocket server to the client.\n", + "navigationGroup": "legacy", + "sections": [ + { + "type": "object", + "key": "RealtimeBetaServerEventError", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventSessionCreated", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventSessionUpdated", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventTranscriptionSessionCreated", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventTranscriptionSessionUpdated", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventConversationItemCreated", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventConversationItemRetrieved", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventConversationItemInputAudioTranscriptionCompleted", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventConversationItemInputAudioTranscriptionDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventConversationItemInputAudioTranscriptionSegment", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventConversationItemInputAudioTranscriptionFailed", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventConversationItemTruncated", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventConversationItemDeleted", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventInputAudioBufferCommitted", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventInputAudioBufferCleared", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventInputAudioBufferSpeechStarted", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventInputAudioBufferSpeechStopped", + "path": "" + }, + { + "type": "object", + "key": "RealtimeServerEventInputAudioBufferTimeoutTriggered", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseCreated", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseOutputItemAdded", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseOutputItemDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseContentPartAdded", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseContentPartDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseTextDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseTextDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseAudioTranscriptDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseAudioTranscriptDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseAudioDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseAudioDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseFunctionCallArgumentsDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseFunctionCallArgumentsDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseMCPCallArgumentsDelta", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseMCPCallArgumentsDone", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseMCPCallInProgress", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseMCPCallCompleted", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventResponseMCPCallFailed", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventMCPListToolsInProgress", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventMCPListToolsCompleted", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventMCPListToolsFailed", + "path": "" + }, + { + "type": "object", + "key": "RealtimeBetaServerEventRateLimitsUpdated", + "path": "" + } + ] + }, + { + "id": "live-client-events", + "title": "Client events", + "description": "Initialize a primary WebSocket with session.start and wait for session.started before sending other events. WebRTC creation starts the session for you. Start with the [Live prompting guide](https://developers.openai.com/api/docs/guides/live-prompting) to design frontend instructions and delegation policy; see [backend prompting](https://developers.openai.com/api/docs/guides/live-delegation#start-with-your-existing-backend-prompt) for tools and business rules.", + "navigationGroup": "live", + "sections": [ + { + "type": "object", + "key": "LiveSessionStartEvent", + "path": "" + }, + { + "type": "object", + "key": "LiveForkSessionStartEvent", + "path": "" + }, + { + "type": "object", + "key": "LiveSessionUpdateParam", + "path": "" + }, + { + "type": "object", + "key": "LiveInputAudioAppendEvent", + "path": "" + }, + { + "type": "object", + "key": "LiveInputAudioMuteParam", + "path": "" + }, + { + "type": "object", + "key": "LiveInputAudioUnmuteParam", + "path": "" + }, + { + "type": "object", + "key": "LiveInstructionsAppendParam", + "path": "" + }, + { + "type": "object", + "key": "LiveThinkingAppendParam", + "path": "" + }, + { + "type": "object", + "key": "LiveCommentaryAppendParam", + "path": "" + }, + { + "type": "object", + "key": "LiveResponseItemCreateParam", + "path": "" + }, + { + "type": "object", + "key": "LiveResponseCreateParam", + "path": "" + }, + { + "type": "object", + "key": "LiveSessionCloseParam", + "path": "" + } + ] + }, + { + "id": "live-server-events", + "title": "Server events", + "description": "Live server events. Responses delegation lifecycle events arrive inside response.event, not as top-level response events. Start with the [Live prompting guide](https://developers.openai.com/api/docs/guides/live-prompting) to design frontend instructions and delegation policy; see [backend prompting](https://developers.openai.com/api/docs/guides/live-delegation#start-with-your-existing-backend-prompt) for tools and business rules.", + "navigationGroup": "live", + "sections": [ + { + "type": "object", + "key": "LiveSessionStarted", + "path": "" + }, + { + "type": "object", + "key": "LiveSessionUpdated", + "path": "" + }, + { + "type": "object", + "key": "LiveInputAudioMuted", + "path": "" + }, + { + "type": "object", + "key": "LiveInputAudioUnmuted", + "path": "" + }, + { + "type": "object", + "key": "LiveInstructionsAppended", + "path": "" + }, + { + "type": "object", + "key": "LiveThinkingAppended", + "path": "" + }, + { + "type": "object", + "key": "LiveCommentaryAppended", + "path": "" + }, + { + "type": "object", + "key": "LiveOutputAudioDelta", + "path": "" + }, + { + "type": "object", + "key": "LiveInputTranscriptDelta", + "path": "" + }, + { + "type": "object", + "key": "LiveOutputTranscriptDelta", + "path": "" + }, + { + "type": "object", + "key": "LiveDelegationCreated", + "path": "" + }, + { + "type": "object", + "key": "LiveResponseEvent", + "path": "" + }, + { + "type": "object", + "key": "LiveSessionUsageUpdated", + "path": "" + }, + { + "type": "object", + "key": "LiveSessionClosed", + "path": "" + }, + { + "type": "object", + "key": "LiveErrorEvent", + "path": "" + }, + { + "type": "object", + "key": "LiveInfoEvent", + "path": "" + } + ] + } + ] + } +} diff --git a/contracts/agents-api/runtime.openapi.yaml b/contracts/agents-api/runtime.openapi.yaml index fc109bc6f..8e1ac37a7 100644 --- a/contracts/agents-api/runtime.openapi.yaml +++ b/contracts/agents-api/runtime.openapi.yaml @@ -151,13 +151,17 @@ definitions: x-nullable: true message: type: string + misalignment: + type: object param: type: string x-nullable: true type: type: string required: + - code - message + - param - type type: object v1.ErrorResponse: diff --git a/contracts/agents-api/upstream-fields.json b/contracts/agents-api/upstream-fields.json deleted file mode 100644 index f1668c995..000000000 --- a/contracts/agents-api/upstream-fields.json +++ /dev/null @@ -1,2301 +0,0 @@ -{ - "sdk_version": "3.13.0", - "commit": "d7c41efee1b0802b79f3f88a678ef2052b06e9ce", - "generator": "scripts/extract-agents-api-upstream.py", - "operations": { - "DELETE /agents/environments/templates/{}": { - "request": [], - "query": [], - "response": [ - "beta.agents.environments.environment_template_deleted.EnvironmentTemplateDeleted" - ] - }, - "DELETE /agents/sessions/{}": { - "request": [], - "query": [], - "response": [ - "beta.agent_session_deleted.AgentSessionDeleted" - ] - }, - "DELETE /agents/sessions/{}/artifacts/{}": { - "request": [], - "query": [], - "response": [ - "beta.agents.sessions.session_artifact_deleted.SessionArtifactDeleted" - ] - }, - "DELETE /agents/{}": { - "request": [], - "query": [], - "response": [ - "beta.agent_deleted.AgentDeleted" - ] - }, - "DELETE /files/{}": { - "request": [], - "query": [], - "response": [ - "file_deleted.FileDeleted" - ] - }, - "DELETE /skills/{}": { - "request": [], - "query": [], - "response": [ - "deleted_skill.DeletedSkill" - ] - }, - "DELETE /skills/{}/versions/{}": { - "request": [], - "query": [], - "response": [ - "skills.deleted_skill_version.DeletedSkillVersion" - ] - }, - "DELETE /vaults/{}": { - "request": [], - "query": [], - "response": [ - "beta.agents.vault_deleted.VaultDeleted" - ] - }, - "DELETE /vaults/{}/credentials/{}": { - "request": [], - "query": [], - "response": [ - "beta.agents.vaults.credential_deleted.CredentialDeleted" - ] - }, - "GET /agents": { - "request": [], - "query": [ - "beta.agent_list_params.AgentListParams" - ], - "response": [ - "pagination.SyncCursorPage[beta.agent.Agent]" - ] - }, - "GET /agents/environments/templates": { - "request": [], - "query": [ - "beta.agents.environments.template_list_params.TemplateListParams" - ], - "response": [ - "pagination.SyncCursorPage[beta.agents.environments.environment_template.EnvironmentTemplate]" - ] - }, - "GET /agents/environments/templates/{}": { - "request": [], - "query": [], - "response": [ - "beta.agents.environments.environment_template.EnvironmentTemplate" - ] - }, - "GET /agents/environments/{}": { - "request": [], - "query": [], - "response": [ - "beta.agents.environment_info.EnvironmentInfo" - ] - }, - "GET /agents/environments/{}/files": { - "request": [], - "query": [ - "beta.agents.environments.file_list_params.FileListParams" - ], - "response": [ - "pagination.SyncTokenPage[beta.agents.environments.environment_file.EnvironmentFile]" - ] - }, - "GET /agents/sessions": { - "request": [], - "query": [ - "beta.agents.session_list_params.SessionListParams" - ], - "response": [ - "pagination.SyncCursorPage[beta.agent_session.AgentSession]" - ] - }, - "GET /agents/sessions/{}": { - "request": [], - "query": [], - "response": [ - "beta.agent_session.AgentSession" - ] - }, - "GET /agents/sessions/{}/artifacts": { - "request": [], - "query": [ - "beta.agents.sessions.artifact_list_params.ArtifactListParams" - ], - "response": [ - "pagination.SyncCursorPage[beta.agents.sessions.session_artifact.SessionArtifact]" - ] - }, - "GET /agents/sessions/{}/artifacts/{}": { - "request": [], - "query": [], - "response": [ - "beta.agents.sessions.session_artifact.SessionArtifact" - ] - }, - "GET /agents/sessions/{}/artifacts/{}/content": { - "request": [], - "query": [], - "response": [] - }, - "GET /agents/sessions/{}/events": { - "request": [], - "query": [], - "response": [ - "beta.agent_output_command_execution_output_delta_event.AgentOutputCommandExecutionOutputDeltaEvent", - "beta.agent_session_created_event.AgentSessionCreatedEvent", - "beta.agent_session_environment_connected_event.AgentSessionEnvironmentConnectedEvent", - "beta.agent_session_environment_disconnected_event.AgentSessionEnvironmentDisconnectedEvent", - "beta.agent_session_environment_failed_event.AgentSessionEnvironmentFailedEvent", - "beta.agent_session_environment_pending_event.AgentSessionEnvironmentPendingEvent", - "beta.agent_session_environment_ready_event.AgentSessionEnvironmentReadyEvent", - "beta.agent_session_error_event.AgentSessionErrorEvent", - "beta.agent_session_failed_event.AgentSessionFailedEvent", - "beta.agent_session_idle_event.AgentSessionIdleEvent", - "beta.agent_session_in_progress_event.AgentSessionInProgressEvent", - "beta.agent_session_requires_action_event.AgentSessionRequiresActionEvent", - "beta.agent_session_subagent_active_event.AgentSessionSubagentActiveEvent", - "beta.agent_session_subagent_closed_event.AgentSessionSubagentClosedEvent", - "beta.agent_session_subagent_created_event.AgentSessionSubagentCreatedEvent", - "beta.agent_session_turn_cancelled_event.AgentSessionTurnCancelledEvent", - "beta.agent_session_turn_completed_event.AgentSessionTurnCompletedEvent", - "beta.agent_session_turn_content_part_added_event.AgentSessionTurnContentPartAddedEvent", - "beta.agent_session_turn_content_part_done_event.AgentSessionTurnContentPartDoneEvent", - "beta.agent_session_turn_created_event.AgentSessionTurnCreatedEvent", - "beta.agent_session_turn_failed_event.AgentSessionTurnFailedEvent", - "beta.agent_session_turn_in_progress_event.AgentSessionTurnInProgressEvent", - "beta.agent_session_turn_item_added_event.AgentSessionTurnItemAddedEvent", - "beta.agent_session_turn_item_done_event.AgentSessionTurnItemDoneEvent", - "beta.agent_session_turn_output_text_delta_event.AgentSessionTurnOutputTextDeltaEvent", - "beta.agent_session_turn_output_text_done_event.AgentSessionTurnOutputTextDoneEvent", - "beta.agent_session_turn_reasoning_summary_part_added_event.AgentSessionTurnReasoningSummaryPartAddedEvent", - "beta.agent_session_turn_reasoning_summary_part_done_event.AgentSessionTurnReasoningSummaryPartDoneEvent", - "beta.agent_session_turn_reasoning_summary_text_delta_event.AgentSessionTurnReasoningSummaryTextDeltaEvent", - "beta.agent_session_turn_reasoning_summary_text_done_event.AgentSessionTurnReasoningSummaryTextDoneEvent" - ] - }, - "GET /agents/sessions/{}/items": { - "request": [], - "query": [ - "beta.agents.sessions.item_list_params.ItemListParams" - ], - "response": [ - "pagination.SyncCursorPage[beta.agent_close_subagent_call_item.AgentCloseSubagentCallItem | beta.agent_command_execution_item.AgentCommandExecutionItem | beta.agent_create_subagent_call_item.AgentCreateSubagentCallItem | beta.agent_function_call_item.AgentFunctionCallItem | beta.agent_interrupt_subagent_call_item.AgentInterruptSubagentCallItem | beta.agent_mcp_call_item.AgentMcpCallItem | beta.agent_reasoning_item.AgentReasoningItem | beta.agent_resume_subagent_call_item.AgentResumeSubagentCallItem | beta.agent_send_subagent_input_call_item.AgentSendSubagentInputCallItem | beta.agent_session_item.AgentMessageItemResource | beta.agent_session_item.FunctionCallOutputItemResource | beta.agent_session_message.AgentSessionMessage | beta.agent_wait_for_subagents_call_item.AgentWaitForSubagentsCallItem | beta.agent_web_search_call_item.AgentWebSearchCallItem]" - ] - }, - "GET /agents/sessions/{}/subagents": { - "request": [], - "query": [ - "beta.agents.sessions.subagent_list_params.SubagentListParams" - ], - "response": [ - "pagination.SyncCursorPage[beta.subagent.Subagent]" - ] - }, - "GET /agents/sessions/{}/subagents/{}": { - "request": [], - "query": [], - "response": [ - "beta.subagent.Subagent" - ] - }, - "GET /agents/sessions/{}/subagents/{}/items": { - "request": [], - "query": [ - "beta.agents.sessions.subagents.item_list_params.ItemListParams" - ], - "response": [ - "pagination.SyncCursorPage[beta.agent_close_subagent_call_item.AgentCloseSubagentCallItem | beta.agent_command_execution_item.AgentCommandExecutionItem | beta.agent_create_subagent_call_item.AgentCreateSubagentCallItem | beta.agent_function_call_item.AgentFunctionCallItem | beta.agent_interrupt_subagent_call_item.AgentInterruptSubagentCallItem | beta.agent_mcp_call_item.AgentMcpCallItem | beta.agent_reasoning_item.AgentReasoningItem | beta.agent_resume_subagent_call_item.AgentResumeSubagentCallItem | beta.agent_send_subagent_input_call_item.AgentSendSubagentInputCallItem | beta.agent_session_item.AgentMessageItemResource | beta.agent_session_item.FunctionCallOutputItemResource | beta.agent_session_message.AgentSessionMessage | beta.agent_wait_for_subagents_call_item.AgentWaitForSubagentsCallItem | beta.agent_web_search_call_item.AgentWebSearchCallItem]" - ] - }, - "GET /agents/sessions/{}/subagents/{}/turns": { - "request": [], - "query": [ - "beta.agents.sessions.subagents.turn_list_params.TurnListParams" - ], - "response": [ - "pagination.SyncCursorPage[beta.agents.sessions.turn.Turn]" - ] - }, - "GET /agents/sessions/{}/subagents/{}/turns/{}": { - "request": [], - "query": [], - "response": [ - "beta.agents.sessions.turn.Turn" - ] - }, - "GET /agents/sessions/{}/subagents/{}/turns/{}/items": { - "request": [], - "query": [ - "beta.agents.sessions.subagents.turns.item_list_params.ItemListParams" - ], - "response": [ - "pagination.SyncCursorPage[beta.agent_close_subagent_call_item.AgentCloseSubagentCallItem | beta.agent_command_execution_item.AgentCommandExecutionItem | beta.agent_create_subagent_call_item.AgentCreateSubagentCallItem | beta.agent_function_call_item.AgentFunctionCallItem | beta.agent_interrupt_subagent_call_item.AgentInterruptSubagentCallItem | beta.agent_mcp_call_item.AgentMcpCallItem | beta.agent_reasoning_item.AgentReasoningItem | beta.agent_resume_subagent_call_item.AgentResumeSubagentCallItem | beta.agent_send_subagent_input_call_item.AgentSendSubagentInputCallItem | beta.agent_session_item.AgentMessageItemResource | beta.agent_session_item.FunctionCallOutputItemResource | beta.agent_session_message.AgentSessionMessage | beta.agent_wait_for_subagents_call_item.AgentWaitForSubagentsCallItem | beta.agent_web_search_call_item.AgentWebSearchCallItem]" - ] - }, - "GET /agents/sessions/{}/turns": { - "request": [], - "query": [ - "beta.agents.sessions.turn_list_params.TurnListParams" - ], - "response": [ - "pagination.SyncCursorPage[beta.agents.sessions.turn.Turn]" - ] - }, - "GET /agents/sessions/{}/turns/{}": { - "request": [], - "query": [], - "response": [ - "beta.agents.sessions.turn.Turn" - ] - }, - "GET /agents/{}": { - "request": [], - "query": [], - "response": [ - "beta.agent.Agent" - ] - }, - "GET /files": { - "request": [], - "query": [ - "file_list_params.FileListParams" - ], - "response": [ - "pagination.SyncCursorPage[file_object.FileObject]" - ] - }, - "GET /files/{}": { - "request": [], - "query": [], - "response": [ - "file_object.FileObject" - ] - }, - "GET /files/{}/content": { - "request": [], - "query": [], - "response": [] - }, - "GET /skills": { - "request": [], - "query": [ - "skill_list_params.SkillListParams" - ], - "response": [ - "pagination.SyncCursorPage[skill.Skill]" - ] - }, - "GET /skills/{}": { - "request": [], - "query": [], - "response": [ - "skill.Skill" - ] - }, - "GET /skills/{}/content": { - "request": [], - "query": [], - "response": [] - }, - "GET /skills/{}/versions": { - "request": [], - "query": [ - "skills.version_list_params.VersionListParams" - ], - "response": [ - "pagination.SyncCursorPage[skills.skill_version.SkillVersion]" - ] - }, - "GET /skills/{}/versions/{}": { - "request": [], - "query": [], - "response": [ - "skills.skill_version.SkillVersion" - ] - }, - "GET /skills/{}/versions/{}/content": { - "request": [], - "query": [], - "response": [] - }, - "GET /vaults": { - "request": [], - "query": [ - "beta.agents.vault_list_params.VaultListParams" - ], - "response": [ - "pagination.SyncCursorPage[beta.agents.vault.Vault]" - ] - }, - "GET /vaults/{}": { - "request": [], - "query": [], - "response": [ - "beta.agents.vault.Vault" - ] - }, - "GET /vaults/{}/credentials": { - "request": [], - "query": [ - "beta.agents.vaults.credential_list_params.CredentialListParams" - ], - "response": [ - "pagination.SyncCursorPage[beta.agents.vaults.credential.Credential]" - ] - }, - "GET /vaults/{}/credentials/{}": { - "request": [], - "query": [], - "response": [ - "beta.agents.vaults.credential.Credential" - ] - }, - "POST /agents": { - "request": [ - "beta.agent_create_params.AgentCreateParams" - ], - "query": [], - "response": [ - "beta.agent.Agent" - ] - }, - "POST /agents/environments/templates": { - "request": [ - "beta.agents.environments.template_create_params.TemplateCreateParams" - ], - "query": [], - "response": [ - "beta.agents.environments.environment_template.EnvironmentTemplate" - ] - }, - "POST /agents/environments/templates/{}": { - "request": [ - "beta.agents.environments.template_update_params.TemplateUpdateParams" - ], - "query": [], - "response": [ - "beta.agents.environments.environment_template.EnvironmentTemplate" - ] - }, - "POST /agents/environments/{}/files": { - "request": [ - "beta.agents.environments.file_create_params.HostedEnvironmentFileParamFileID", - "beta.agents.environments.file_create_params.HostedEnvironmentFileParamInline" - ], - "query": [], - "response": [ - "beta.agents.environments.environment_file.EnvironmentFile" - ] - }, - "POST /agents/sessions": { - "request": [ - "beta.agents.session_create_params.SessionCreateParamsNonStreaming", - "beta.agents.session_create_params.SessionCreateParamsStreaming" - ], - "query": [], - "response": [ - "beta.agent_session.AgentSession" - ] - }, - "POST /agents/sessions/{}": { - "request": [ - "beta.agents.session_update_params.SessionUpdateParams" - ], - "query": [], - "response": [ - "beta.agent_session.AgentSession" - ] - }, - "POST /agents/sessions/{}/events": { - "request": [ - "beta.agents.sessions.event_create_params.EventCreateParams" - ], - "query": [], - "response": [] - }, - "POST /agents/{}": { - "request": [ - "beta.agent_update_params.AgentUpdateParams" - ], - "query": [], - "response": [ - "beta.agent.Agent" - ] - }, - "POST /files": { - "request": [ - "file_create_params.FileCreateParams" - ], - "query": [], - "response": [ - "file_object.FileObject" - ] - }, - "POST /skills": { - "request": [ - "skill_create_params.SkillCreateParams" - ], - "query": [], - "response": [ - "skill.Skill" - ] - }, - "POST /skills/{}": { - "request": [ - "skill_update_params.SkillUpdateParams" - ], - "query": [], - "response": [ - "skill.Skill" - ] - }, - "POST /skills/{}/versions": { - "request": [ - "skills.version_create_params.VersionCreateParams" - ], - "query": [], - "response": [ - "skills.skill_version.SkillVersion" - ] - }, - "POST /vaults": { - "request": [ - "beta.agents.vault_create_params.VaultCreateParams" - ], - "query": [], - "response": [ - "beta.agents.vault.Vault" - ] - }, - "POST /vaults/{}/credentials": { - "request": [ - "beta.agents.vaults.credential_create_params.CredentialCreateParams" - ], - "query": [], - "response": [ - "beta.agents.vaults.credential.Credential" - ] - }, - "POST /vaults/{}/credentials/{}": { - "request": [ - "beta.agents.vaults.credential_update_params.CredentialUpdateParams" - ], - "query": [], - "response": [ - "beta.agents.vaults.credential.Credential" - ] - } - }, - "types": { - "beta.agent.Agent": { - "created_at": [], - "id": [], - "instructions": [], - "metadata": [], - "model": [], - "multi_agent": [ - "beta.multi_agent_config.MultiAgentConfig" - ], - "name": [], - "object": [], - "reasoning": [ - "beta.agent_reasoning.AgentReasoning" - ], - "service_tier": [], - "text": [ - "beta.agent_text.AgentText" - ], - "tools": [ - "beta.persisted_agent_tool.PersistedAgentToolResourceFunction", - "beta.persisted_agent_tool.PersistedAgentToolResourceMcp", - "beta.persisted_agent_tool.PersistedAgentToolResourceProgrammaticToolCalling", - "beta.persisted_agent_tool.PersistedAgentToolResourceToolSearch", - "beta.persisted_agent_tool.PersistedAgentToolResourceWebSearch" - ], - "updated_at": [] - }, - "beta.agent_close_subagent_call_item.AgentCloseSubagentCallItem": { - "id": [], - "recipient_agent_id": [], - "sender_agent_id": [], - "status": [], - "turn_id": [], - "type": [] - }, - "beta.agent_command_execution_item.AgentCommandExecutionItem": { - "command": [], - "cwd": [], - "duration_ms": [], - "exit_code": [], - "id": [], - "output": [], - "status": [], - "turn_id": [], - "type": [] - }, - "beta.agent_content.EncryptedContentResource": { - "encrypted_content": [], - "type": [] - }, - "beta.agent_create_params.AgentCreateParams": { - "instructions": [], - "metadata": [], - "model": [], - "multi_agent": [ - "beta.multi_agent_config_param.MultiAgentConfigParam" - ], - "name": [], - "reasoning": [ - "beta.agent_reasoning_param.AgentReasoningParam" - ], - "service_tier": [], - "text": [ - "beta.agent_text_param.AgentTextParam" - ], - "tools": [ - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamFunction", - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamMcp", - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamProgrammaticToolCalling", - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamToolSearch", - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamWebSearch" - ] - }, - "beta.agent_create_subagent_call_item.AgentCreateSubagentCallItem": { - "agent_id": [], - "content": [ - "beta.agent_content.EncryptedContentResource", - "beta.output_text.OutputText" - ], - "id": [], - "model": [], - "reasoning_effort": [], - "status": [], - "turn_id": [], - "type": [] - }, - "beta.agent_deleted.AgentDeleted": { - "deleted": [], - "id": [], - "object": [] - }, - "beta.agent_function_call_item.AgentFunctionCallItem": { - "arguments": [], - "call_id": [], - "id": [], - "name": [], - "status": [], - "turn_id": [], - "type": [] - }, - "beta.agent_interrupt_subagent_call_item.AgentInterruptSubagentCallItem": { - "id": [], - "recipient_agent_id": [], - "sender_agent_id": [], - "status": [], - "turn_id": [], - "type": [] - }, - "beta.agent_list_params.AgentListParams": { - "after": [], - "limit": [], - "order": [] - }, - "beta.agent_mcp_call_item.AgentMcpCallItem": { - "arguments": [], - "error": [], - "id": [], - "name": [], - "output": [], - "server_label": [], - "status": [], - "turn_id": [], - "type": [] - }, - "beta.agent_output_command_execution_output_delta_event.AgentOutputCommandExecutionOutputDeltaEvent": { - "delta": [], - "event_id": [], - "item_id": [], - "output_index": [], - "session_id": [], - "turn_id": [], - "type": [] - }, - "beta.agent_reasoning.AgentReasoning": { - "effort": [], - "summary": [] - }, - "beta.agent_reasoning_item.AgentReasoningItem": { - "id": [], - "status": [], - "summary": [ - "beta.summary_text.SummaryText" - ], - "turn_id": [], - "type": [] - }, - "beta.agent_reasoning_param.AgentReasoningParam": { - "effort": [], - "summary": [] - }, - "beta.agent_resume_subagent_call_item.AgentResumeSubagentCallItem": { - "id": [], - "recipient_agent_id": [], - "sender_agent_id": [], - "status": [], - "turn_id": [], - "type": [] - }, - "beta.agent_send_subagent_input_call_item.AgentSendSubagentInputCallItem": { - "content": [ - "beta.agent_content.EncryptedContentResource", - "beta.output_text.OutputText" - ], - "id": [], - "recipient_agent_id": [], - "sender_agent_id": [], - "status": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session.Agent": { - "id": [], - "instructions": [], - "model": [], - "multi_agent": [ - "beta.multi_agent_config.MultiAgentConfig" - ], - "name": [], - "reasoning": [ - "beta.agent_reasoning.AgentReasoning" - ], - "service_tier": [], - "text": [ - "beta.agent_text.AgentText" - ], - "tools": [ - "beta.agent_tool.AgentToolResourceFunction", - "beta.agent_tool.AgentToolResourceMcp", - "beta.agent_tool.AgentToolResourceProgrammaticToolCalling", - "beta.agent_tool.AgentToolResourceWebSearch" - ] - }, - "beta.agent_session.AgentSession": { - "agent": [ - "beta.agent_session.Agent" - ], - "created_at": [], - "environment": [ - "beta.environment.EnvironmentResourceNone", - "beta.environment.EnvironmentResourceOpenAIHosted", - "beta.environment.EnvironmentResourceSelfHosted" - ], - "error": [], - "id": [], - "last_active_at": [], - "metadata": [], - "object": [], - "required_actions": [ - "beta.agent_session.RequiredActionSessionRequiredActionResourceEnvironmentConnection", - "beta.agent_session.RequiredActionSessionRequiredActionResourceFunctionCall" - ], - "status": [], - "usage": [ - "beta.token_usage.TokenUsage" - ], - "vault_ids": [] - }, - "beta.agent_session.RequiredActionSessionRequiredActionResourceEnvironmentConnection": { - "environment_id": [], - "type": [] - }, - "beta.agent_session.RequiredActionSessionRequiredActionResourceFunctionCall": { - "arguments": [], - "call_id": [], - "name": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_assistant_message.AgentSessionAssistantMessage": { - "content": [ - "beta.output_text.OutputText" - ], - "id": [], - "phase": [], - "role": [], - "status": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_created_event.AgentSessionCreatedEvent": { - "event_id": [], - "session": [ - "beta.agent_session.AgentSession" - ], - "type": [] - }, - "beta.agent_session_deleted.AgentSessionDeleted": { - "deleted": [], - "id": [], - "object": [] - }, - "beta.agent_session_environment_connected_event.AgentSessionEnvironmentConnectedEvent": { - "environment": [ - "beta.agent_session_environment_state.AgentSessionEnvironmentState" - ], - "event_id": [], - "session_id": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_environment_disconnected_event.AgentSessionEnvironmentDisconnectedEvent": { - "environment": [ - "beta.agent_session_environment_state.AgentSessionEnvironmentState" - ], - "event_id": [], - "session_id": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_environment_failed_event.AgentSessionEnvironmentFailedEvent": { - "environment": [ - "beta.agent_session_environment_state.AgentSessionEnvironmentState" - ], - "event_id": [], - "session_id": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_environment_pending_event.AgentSessionEnvironmentPendingEvent": { - "environment": [ - "beta.agent_session_environment_state.AgentSessionEnvironmentState" - ], - "event_id": [], - "session_id": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_environment_ready_event.AgentSessionEnvironmentReadyEvent": { - "environment": [ - "beta.agent_session_environment_state.AgentSessionEnvironmentState" - ], - "event_id": [], - "session_id": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_environment_state.AgentSessionEnvironmentState": { - "error": [ - "beta.agent_session_environment_state.Error" - ], - "id": [], - "status": [], - "type": [] - }, - "beta.agent_session_environment_state.Error": { - "code": [], - "message": [], - "type": [] - }, - "beta.agent_session_error_event.AgentSessionErrorEvent": { - "error": [ - "beta.session_error.SessionError" - ], - "event_id": [], - "session_id": [], - "type": [] - }, - "beta.agent_session_failed_event.AgentSessionFailedEvent": { - "event_id": [], - "session": [ - "beta.agent_session.AgentSession" - ], - "type": [] - }, - "beta.agent_session_idle_event.AgentSessionIdleEvent": { - "event_id": [], - "session": [ - "beta.agent_session.AgentSession" - ], - "type": [] - }, - "beta.agent_session_in_progress_event.AgentSessionInProgressEvent": { - "event_id": [], - "session": [ - "beta.agent_session.AgentSession" - ], - "type": [] - }, - "beta.agent_session_input_message_param.AgentSessionInputMessageParam": { - "content": [ - "beta.input_content_param.InputContentParamInputImage", - "beta.input_content_param.InputContentParamInputText" - ], - "role": [], - "type": [] - }, - "beta.agent_session_input_param.SessionInputParamAgentSessionInputCancel": { - "type": [] - }, - "beta.agent_session_input_param.SessionInputParamAgentSessionInputMessage": { - "input": [ - "beta.agent_session_input_message_param.AgentSessionInputMessageParam" - ], - "type": [] - }, - "beta.agent_session_input_param.SessionInputParamAgentSessionInputToolResult": { - "call_id": [], - "error": [], - "output": [ - "beta.input_content_param.InputContentParamInputImage", - "beta.input_content_param.InputContentParamInputText" - ], - "success": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_item.AgentMessageItemResource": { - "content": [ - "beta.agent_content.EncryptedContentResource", - "beta.output_text.OutputText" - ], - "id": [], - "recipient_agent_id": [], - "sender_agent_id": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_item.FunctionCallOutputItemResource": { - "call_id": [], - "error": [], - "id": [], - "output": [ - "beta.input_content.InputContentResourceInputImage", - "beta.input_content.InputContentResourceInputText" - ], - "status": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_message.AgentSessionMessage": { - "content": [ - "beta.agent_session_message_content.MessageContentResourceInputImage", - "beta.agent_session_message_content.MessageContentResourceInputText", - "beta.agent_session_message_content.MessageContentResourceOutputText" - ], - "id": [], - "phase": [], - "role": [], - "status": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_message_content.MessageContentResourceInputImage": { - "image_url": [], - "type": [] - }, - "beta.agent_session_message_content.MessageContentResourceInputText": { - "text": [], - "type": [] - }, - "beta.agent_session_message_content.MessageContentResourceOutputText": { - "text": [], - "type": [] - }, - "beta.agent_session_requires_action_event.AgentSessionRequiresActionEvent": { - "event_id": [], - "session": [ - "beta.agent_session.AgentSession" - ], - "type": [] - }, - "beta.agent_session_subagent_active_event.AgentSessionSubagentActiveEvent": { - "event_id": [], - "subagent": [ - "beta.subagent.Subagent" - ], - "type": [] - }, - "beta.agent_session_subagent_closed_event.AgentSessionSubagentClosedEvent": { - "event_id": [], - "subagent": [ - "beta.subagent.Subagent" - ], - "type": [] - }, - "beta.agent_session_subagent_created_event.AgentSessionSubagentCreatedEvent": { - "event_id": [], - "subagent": [ - "beta.subagent.Subagent" - ], - "type": [] - }, - "beta.agent_session_turn_cancelled_event.AgentSessionTurnCancelledEvent": { - "event_id": [], - "session_id": [], - "turn": [ - "beta.agents.sessions.turn.Turn" - ], - "turn_id": [], - "type": [], - "usage": [ - "beta.token_usage.TokenUsage" - ] - }, - "beta.agent_session_turn_completed_event.AgentSessionTurnCompletedEvent": { - "event_id": [], - "session_id": [], - "turn": [ - "beta.agents.sessions.turn.Turn" - ], - "turn_id": [], - "type": [], - "usage": [ - "beta.token_usage.TokenUsage" - ] - }, - "beta.agent_session_turn_content_part_added_event.AgentSessionTurnContentPartAddedEvent": { - "content_index": [], - "event_id": [], - "item_id": [], - "output_index": [], - "part": [ - "beta.output_text.OutputText" - ], - "session_id": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_turn_content_part_done_event.AgentSessionTurnContentPartDoneEvent": { - "content_index": [], - "event_id": [], - "item_id": [], - "output_index": [], - "part": [ - "beta.output_text.OutputText" - ], - "session_id": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_turn_created_event.AgentSessionTurnCreatedEvent": { - "event_id": [], - "session_id": [], - "turn": [ - "beta.agents.sessions.turn.Turn" - ], - "turn_id": [], - "type": [] - }, - "beta.agent_session_turn_failed_event.AgentSessionTurnFailedEvent": { - "event_id": [], - "session_id": [], - "turn": [ - "beta.agents.sessions.turn.Turn" - ], - "turn_id": [], - "type": [], - "usage": [ - "beta.token_usage.TokenUsage" - ] - }, - "beta.agent_session_turn_in_progress_event.AgentSessionTurnInProgressEvent": { - "event_id": [], - "session_id": [], - "turn": [ - "beta.agents.sessions.turn.Turn" - ], - "turn_id": [], - "type": [] - }, - "beta.agent_session_turn_item_added_event.AgentSessionTurnItemAddedEvent": { - "event_id": [], - "item": [ - "beta.agent_close_subagent_call_item.AgentCloseSubagentCallItem", - "beta.agent_command_execution_item.AgentCommandExecutionItem", - "beta.agent_create_subagent_call_item.AgentCreateSubagentCallItem", - "beta.agent_function_call_item.AgentFunctionCallItem", - "beta.agent_interrupt_subagent_call_item.AgentInterruptSubagentCallItem", - "beta.agent_mcp_call_item.AgentMcpCallItem", - "beta.agent_reasoning_item.AgentReasoningItem", - "beta.agent_resume_subagent_call_item.AgentResumeSubagentCallItem", - "beta.agent_send_subagent_input_call_item.AgentSendSubagentInputCallItem", - "beta.agent_session_item.AgentMessageItemResource", - "beta.agent_session_item.FunctionCallOutputItemResource", - "beta.agent_session_message.AgentSessionMessage", - "beta.agent_wait_for_subagents_call_item.AgentWaitForSubagentsCallItem", - "beta.agent_web_search_call_item.AgentWebSearchCallItem" - ], - "output_index": [], - "session_id": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_turn_item_done_event.AgentSessionTurnItemDoneEvent": { - "event_id": [], - "item": [ - "beta.agent_close_subagent_call_item.AgentCloseSubagentCallItem", - "beta.agent_command_execution_item.AgentCommandExecutionItem", - "beta.agent_create_subagent_call_item.AgentCreateSubagentCallItem", - "beta.agent_function_call_item.AgentFunctionCallItem", - "beta.agent_interrupt_subagent_call_item.AgentInterruptSubagentCallItem", - "beta.agent_mcp_call_item.AgentMcpCallItem", - "beta.agent_reasoning_item.AgentReasoningItem", - "beta.agent_resume_subagent_call_item.AgentResumeSubagentCallItem", - "beta.agent_send_subagent_input_call_item.AgentSendSubagentInputCallItem", - "beta.agent_session_assistant_message.AgentSessionAssistantMessage", - "beta.agent_wait_for_subagents_call_item.AgentWaitForSubagentsCallItem", - "beta.agent_web_search_call_item.AgentWebSearchCallItem" - ], - "output_index": [], - "session_id": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_turn_output_text_delta_event.AgentSessionTurnOutputTextDeltaEvent": { - "content_index": [], - "delta": [], - "event_id": [], - "item_id": [], - "output_index": [], - "session_id": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_turn_output_text_done_event.AgentSessionTurnOutputTextDoneEvent": { - "content_index": [], - "event_id": [], - "item_id": [], - "output_index": [], - "session_id": [], - "text": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_turn_reasoning_summary_part_added_event.AgentSessionTurnReasoningSummaryPartAddedEvent": { - "event_id": [], - "item_id": [], - "output_index": [], - "part": [ - "beta.summary_text.SummaryText" - ], - "session_id": [], - "summary_index": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_turn_reasoning_summary_part_done_event.AgentSessionTurnReasoningSummaryPartDoneEvent": { - "event_id": [], - "item_id": [], - "output_index": [], - "part": [ - "beta.summary_text.SummaryText" - ], - "session_id": [], - "status": [], - "summary_index": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_turn_reasoning_summary_text_delta_event.AgentSessionTurnReasoningSummaryTextDeltaEvent": { - "delta": [], - "event_id": [], - "item_id": [], - "output_index": [], - "session_id": [], - "summary_index": [], - "turn_id": [], - "type": [] - }, - "beta.agent_session_turn_reasoning_summary_text_done_event.AgentSessionTurnReasoningSummaryTextDoneEvent": { - "event_id": [], - "item_id": [], - "output_index": [], - "session_id": [], - "summary_index": [], - "text": [], - "turn_id": [], - "type": [] - }, - "beta.agent_text.AgentText": { - "format": [ - "beta.text_format.TextFormatResourceJSONSchema", - "beta.text_format.TextFormatResourceText" - ], - "verbosity": [] - }, - "beta.agent_text_param.AgentTextParam": { - "format": [ - "beta.text_format_param.TextFormatParamJSONSchema", - "beta.text_format_param.TextFormatParamText" - ], - "verbosity": [] - }, - "beta.agent_tool.AgentToolResourceFunction": { - "defer_loading": [], - "description": [], - "name": [], - "parameters": [], - "type": [] - }, - "beta.agent_tool.AgentToolResourceMcp": { - "allowed_tools": [], - "connection_origin": [], - "credential_id": [], - "request_metadata": [], - "required": [], - "server_label": [], - "transport": [ - "beta.mcp_transport.McpTransportResourceHTTP", - "beta.mcp_transport.McpTransportResourceStdio" - ], - "type": [] - }, - "beta.agent_tool.AgentToolResourceProgrammaticToolCalling": { - "enabled": [], - "type": [] - }, - "beta.agent_tool.AgentToolResourceWebSearch": { - "allowed_domains": [], - "context_size": [], - "location": [ - "beta.agent_tool.AgentToolResourceWebSearchLocation" - ], - "mode": [], - "type": [] - }, - "beta.agent_tool.AgentToolResourceWebSearchLocation": { - "city": [], - "country": [], - "region": [], - "timezone": [] - }, - "beta.agent_tool_param.AgentToolConfigParamFunction": { - "defer_loading": [], - "description": [], - "name": [], - "parameters": [], - "type": [] - }, - "beta.agent_tool_param.AgentToolConfigParamMcp": { - "allowed_tools": [], - "connection_origin": [], - "credential_id": [], - "request_metadata": [], - "required": [], - "server_label": [], - "transport": [ - "beta.mcp_transport_param.McpTransportConfigParamHTTP", - "beta.mcp_transport_param.McpTransportConfigParamStdio" - ], - "type": [] - }, - "beta.agent_tool_param.AgentToolConfigParamProgrammaticToolCalling": { - "enabled": [], - "type": [] - }, - "beta.agent_tool_param.AgentToolConfigParamToolSearch": { - "type": [] - }, - "beta.agent_tool_param.AgentToolConfigParamWebSearch": { - "allowed_domains": [], - "context_size": [], - "location": [ - "beta.agent_tool_param.AgentToolConfigParamWebSearchLocation" - ], - "mode": [], - "type": [] - }, - "beta.agent_tool_param.AgentToolConfigParamWebSearchLocation": { - "city": [], - "country": [], - "region": [], - "timezone": [] - }, - "beta.agent_update_params.AgentUpdateParams": { - "instructions": [], - "metadata": [], - "model": [], - "multi_agent": [ - "beta.multi_agent_config_param.MultiAgentConfigParam" - ], - "name": [], - "reasoning": [ - "beta.agent_reasoning_param.AgentReasoningParam" - ], - "service_tier": [], - "text": [ - "beta.agent_text_param.AgentTextParam" - ], - "tools": [ - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamFunction", - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamMcp", - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamProgrammaticToolCalling", - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamToolSearch", - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamWebSearch" - ] - }, - "beta.agent_wait_for_subagents_call_item.AgentWaitForSubagentsCallItem": { - "id": [], - "recipient_agent_ids": [], - "sender_agent_id": [], - "status": [], - "turn_id": [], - "type": [] - }, - "beta.agent_web_search_call_item.AgentWebSearchCallItem": { - "action": [ - "beta.web_search_action.WebSearchActionResourceFindInPage", - "beta.web_search_action.WebSearchActionResourceOpenPage", - "beta.web_search_action.WebSearchActionResourceOther", - "beta.web_search_action.WebSearchActionResourceSearch" - ], - "id": [], - "status": [], - "turn_id": [], - "type": [] - }, - "beta.agents.environment_info.EnvironmentInfo": { - "files": [ - "beta.hosted_environment_file.HostedEnvironmentFileResourceInline", - "beta.hosted_environment_file_id.HostedEnvironmentFileID" - ], - "id": [], - "object": [], - "plugins": [ - "beta.hosted_plugin.HostedPlugin" - ], - "skills": [ - "beta.hosted_skill.HostedSkillResourceInline", - "beta.hosted_skill_reference.HostedSkillReference" - ], - "status": [], - "type": [] - }, - "beta.agents.environments.environment_file.EnvironmentFile": { - "environment_id": [], - "object": [], - "path": [], - "size_bytes": [] - }, - "beta.agents.environments.environment_template.EnvironmentTemplate": { - "capability_directories": [], - "created_at": [], - "files": [ - "beta.agents.environments.environment_template.FileHostedTemplateFileResourceFileID", - "beta.agents.environments.environment_template.FileHostedTemplateFileResourceInline" - ], - "id": [], - "name": [], - "network": [ - "beta.agents.environments.environment_template.Network" - ], - "object": [], - "packages": [ - "beta.agents.environments.environment_template.Packages" - ], - "plugins": [ - "beta.hosted_plugin.HostedPlugin" - ], - "skills": [ - "beta.agents.environments.environment_template.SkillHostedTemplateSkillResourceInline", - "beta.agents.environments.environment_template.SkillHostedTemplateSkillResourceSkillReference" - ], - "updated_at": [] - }, - "beta.agents.environments.environment_template.FileHostedTemplateFileResourceFileID": { - "file_id": [], - "path": [], - "type": [] - }, - "beta.agents.environments.environment_template.FileHostedTemplateFileResourceInline": { - "path": [], - "size_bytes": [], - "type": [] - }, - "beta.agents.environments.environment_template.Network": { - "access": [], - "allowed_domains": [] - }, - "beta.agents.environments.environment_template.Packages": { - "npm": [], - "python": [], - "system": [] - }, - "beta.agents.environments.environment_template.SkillHostedTemplateSkillResourceInline": { - "description": [], - "name": [], - "type": [] - }, - "beta.agents.environments.environment_template.SkillHostedTemplateSkillResourceSkillReference": { - "skill_id": [], - "type": [], - "version": [] - }, - "beta.agents.environments.environment_template_deleted.EnvironmentTemplateDeleted": { - "deleted": [], - "id": [], - "object": [] - }, - "beta.agents.environments.file_create_params.HostedEnvironmentFileParamFileID": { - "file_id": [], - "path": [], - "type": [] - }, - "beta.agents.environments.file_create_params.HostedEnvironmentFileParamInline": { - "data": [], - "path": [], - "type": [] - }, - "beta.agents.environments.file_list_params.FileListParams": { - "limit": [], - "order": [], - "page": [], - "path": [] - }, - "beta.agents.environments.template_create_params.Network": { - "access": [], - "allowed_domains": [] - }, - "beta.agents.environments.template_create_params.Packages": { - "npm": [], - "python": [], - "system": [] - }, - "beta.agents.environments.template_create_params.TemplateCreateParams": { - "capability_directories": [], - "env": [], - "files": [ - "beta.hosted_environment_file_param.HostedEnvironmentFileParamFileID", - "beta.hosted_environment_file_param.HostedEnvironmentFileParamInline" - ], - "name": [], - "network": [ - "beta.agents.environments.template_create_params.Network" - ], - "packages": [ - "beta.agents.environments.template_create_params.Packages" - ], - "plugins": [ - "beta.hosted_plugin_param.HostedPluginParam" - ], - "setup_commands": [ - "beta.setup_command_param.SetupCommandParam" - ], - "skills": [ - "beta.hosted_skill_param.HostedSkillParamInline", - "beta.hosted_skill_param.HostedSkillParamSkillReference" - ] - }, - "beta.agents.environments.template_list_params.TemplateListParams": { - "after": [], - "limit": [], - "order": [] - }, - "beta.agents.environments.template_update_params.Network": { - "access": [], - "allowed_domains": [] - }, - "beta.agents.environments.template_update_params.Packages": { - "npm": [], - "python": [], - "system": [] - }, - "beta.agents.environments.template_update_params.TemplateUpdateParams": { - "capability_directories": [], - "env": [], - "files": [ - "beta.hosted_environment_file_param.HostedEnvironmentFileParamFileID", - "beta.hosted_environment_file_param.HostedEnvironmentFileParamInline" - ], - "name": [], - "network": [ - "beta.agents.environments.template_update_params.Network" - ], - "packages": [ - "beta.agents.environments.template_update_params.Packages" - ], - "plugins": [ - "beta.hosted_plugin_param.HostedPluginParam" - ], - "setup_commands": [ - "beta.setup_command_param.SetupCommandParam" - ], - "skills": [ - "beta.hosted_skill_param.HostedSkillParamInline", - "beta.hosted_skill_param.HostedSkillParamSkillReference" - ] - }, - "beta.agents.session_create_params.Agent": { - "instructions": [], - "model": [], - "multi_agent": [ - "beta.multi_agent_config_param.MultiAgentConfigParam" - ], - "reasoning": [ - "beta.agent_reasoning_param.AgentReasoningParam" - ], - "service_tier": [], - "text": [ - "beta.agent_text_param.AgentTextParam" - ], - "tools": [ - "beta.agent_tool_param.AgentToolConfigParamFunction", - "beta.agent_tool_param.AgentToolConfigParamMcp", - "beta.agent_tool_param.AgentToolConfigParamProgrammaticToolCalling", - "beta.agent_tool_param.AgentToolConfigParamToolSearch", - "beta.agent_tool_param.AgentToolConfigParamWebSearch" - ] - }, - "beta.agents.session_create_params.SessionCreateParamsNonStreaming": { - "agent": [ - "beta.agents.session_create_params.Agent" - ], - "agent_id": [], - "environment": [ - "beta.environment_param.EnvironmentParamNone", - "beta.environment_param.EnvironmentParamOpenAIHosted", - "beta.environment_param.EnvironmentParamSelfHosted" - ], - "input": [ - "beta.agent_session_input_message_param.AgentSessionInputMessageParam" - ], - "metadata": [], - "stream": [], - "vault_ids": [] - }, - "beta.agents.session_create_params.SessionCreateParamsStreaming": { - "agent": [ - "beta.agents.session_create_params.Agent" - ], - "agent_id": [], - "environment": [ - "beta.environment_param.EnvironmentParamNone", - "beta.environment_param.EnvironmentParamOpenAIHosted", - "beta.environment_param.EnvironmentParamSelfHosted" - ], - "input": [ - "beta.agent_session_input_message_param.AgentSessionInputMessageParam" - ], - "metadata": [], - "stream": [], - "vault_ids": [] - }, - "beta.agents.session_list_params.SessionListParams": { - "after": [], - "agent_id": [], - "limit": [], - "order": [] - }, - "beta.agents.session_update_params.SessionUpdateParams": { - "metadata": [] - }, - "beta.agents.sessions.artifact_list_params.ArtifactListParams": { - "after": [], - "environment_id": [], - "limit": [], - "order": [] - }, - "beta.agents.sessions.event_create_params.EventCreateParams": { - "Idempotency-Key": [], - "events": [ - "beta.agent_session_input_param.SessionInputParamAgentSessionInputCancel", - "beta.agent_session_input_param.SessionInputParamAgentSessionInputMessage", - "beta.agent_session_input_param.SessionInputParamAgentSessionInputToolResult" - ] - }, - "beta.agents.sessions.item_list_params.ItemListParams": { - "after": [], - "limit": [], - "order": [] - }, - "beta.agents.sessions.session_artifact.SessionArtifact": { - "created_at": [], - "environment_id": [], - "id": [], - "object": [], - "path": [], - "session_id": [], - "size_bytes": [], - "turn_id": [] - }, - "beta.agents.sessions.session_artifact_deleted.SessionArtifactDeleted": { - "deleted": [], - "id": [], - "object": [] - }, - "beta.agents.sessions.subagent_list_params.SubagentListParams": { - "after": [], - "limit": [], - "order": [] - }, - "beta.agents.sessions.subagents.item_list_params.ItemListParams": { - "after": [], - "limit": [], - "order": [], - "session_id": [] - }, - "beta.agents.sessions.subagents.turn_list_params.TurnListParams": { - "after": [], - "limit": [], - "order": [], - "session_id": [] - }, - "beta.agents.sessions.subagents.turns.item_list_params.ItemListParams": { - "after": [], - "limit": [], - "order": [], - "session_id": [], - "subagent_id": [] - }, - "beta.agents.sessions.turn.Turn": { - "agent_id": [], - "completed_at": [], - "created_at": [], - "error": [ - "beta.session_turn_error.SessionTurnError" - ], - "id": [], - "object": [], - "session_id": [], - "started_at": [], - "status": [], - "subagent_id": [], - "usage": [ - "beta.token_usage.TokenUsage" - ] - }, - "beta.agents.sessions.turn_list_params.TurnListParams": { - "after": [], - "limit": [], - "order": [] - }, - "beta.agents.vault.Vault": { - "created_at": [], - "id": [], - "metadata": [], - "name": [], - "object": [] - }, - "beta.agents.vault_create_params.VaultCreateParams": { - "metadata": [], - "name": [] - }, - "beta.agents.vault_deleted.VaultDeleted": { - "deleted": [], - "id": [], - "object": [] - }, - "beta.agents.vault_list_params.VaultListParams": { - "after": [], - "limit": [], - "order": [], - "status": [] - }, - "beta.agents.vaults.credential.Credential": { - "auth": [ - "beta.agents.vaults.credential_auth.VaultCredentialAuthResourceMcpOauth", - "beta.agents.vaults.credential_auth.VaultCredentialAuthResourceStaticBearer" - ], - "created_at": [], - "id": [], - "name": [], - "object": [], - "updated_at": [], - "vault_id": [] - }, - "beta.agents.vaults.credential_auth.VaultCredentialAuthResourceMcpOauth": { - "expires_at": [], - "mcp_server_url": [], - "refresh": [ - "beta.agents.vaults.credential_auth.VaultCredentialAuthResourceMcpOauthRefresh" - ], - "type": [] - }, - "beta.agents.vaults.credential_auth.VaultCredentialAuthResourceMcpOauthRefresh": { - "client_id": [], - "resource": [], - "scope": [], - "token_endpoint": [], - "token_endpoint_auth": [ - "beta.agents.vaults.mcp_oauth_token_endpoint_auth.McpOauthTokenEndpointAuthResourceClientSecretBasic", - "beta.agents.vaults.mcp_oauth_token_endpoint_auth.McpOauthTokenEndpointAuthResourceClientSecretPost", - "beta.agents.vaults.mcp_oauth_token_endpoint_auth.McpOauthTokenEndpointAuthResourceNone" - ] - }, - "beta.agents.vaults.credential_auth.VaultCredentialAuthResourceStaticBearer": { - "mcp_server_url": [], - "type": [] - }, - "beta.agents.vaults.credential_auth_create_param.CreateVaultCredentialAuthParamMcpOauth": { - "access_token": [], - "expires_at": [], - "mcp_server_url": [], - "refresh": [ - "beta.agents.vaults.credential_auth_create_param.CreateVaultCredentialAuthParamMcpOauthRefresh" - ], - "type": [] - }, - "beta.agents.vaults.credential_auth_create_param.CreateVaultCredentialAuthParamMcpOauthRefresh": { - "client_id": [], - "refresh_token": [], - "resource": [], - "scope": [], - "token_endpoint": [], - "token_endpoint_auth": [ - "beta.agents.vaults.mcp_oauth_token_endpoint_auth_create_param.CreateMcpOauthTokenEndpointAuthParamClientSecretBasic", - "beta.agents.vaults.mcp_oauth_token_endpoint_auth_create_param.CreateMcpOauthTokenEndpointAuthParamClientSecretPost", - "beta.agents.vaults.mcp_oauth_token_endpoint_auth_create_param.CreateMcpOauthTokenEndpointAuthParamNone" - ] - }, - "beta.agents.vaults.credential_auth_create_param.CreateVaultCredentialAuthParamStaticBearer": { - "mcp_server_url": [], - "token": [], - "type": [] - }, - "beta.agents.vaults.credential_auth_rotate_param.RotateVaultCredentialAuthParamMcpOauth": { - "access_token": [], - "expires_at": [], - "refresh": [ - "beta.agents.vaults.credential_auth_rotate_param.RotateVaultCredentialAuthParamMcpOauthRefresh" - ], - "type": [] - }, - "beta.agents.vaults.credential_auth_rotate_param.RotateVaultCredentialAuthParamMcpOauthRefresh": { - "refresh_token": [], - "scope": [], - "token_endpoint_auth": [ - "beta.agents.vaults.mcp_oauth_token_endpoint_auth_rotate_param.RotateMcpOauthTokenEndpointAuthParamClientSecretBasic", - "beta.agents.vaults.mcp_oauth_token_endpoint_auth_rotate_param.RotateMcpOauthTokenEndpointAuthParamClientSecretPost" - ] - }, - "beta.agents.vaults.credential_auth_rotate_param.RotateVaultCredentialAuthParamStaticBearer": { - "token": [], - "type": [] - }, - "beta.agents.vaults.credential_create_params.CredentialCreateParams": { - "auth": [ - "beta.agents.vaults.credential_auth_create_param.CreateVaultCredentialAuthParamMcpOauth", - "beta.agents.vaults.credential_auth_create_param.CreateVaultCredentialAuthParamStaticBearer" - ], - "name": [] - }, - "beta.agents.vaults.credential_deleted.CredentialDeleted": { - "deleted": [], - "id": [], - "object": [] - }, - "beta.agents.vaults.credential_list_params.CredentialListParams": { - "after": [], - "limit": [], - "order": [], - "status": [] - }, - "beta.agents.vaults.credential_update_params.CredentialUpdateParams": { - "auth": [ - "beta.agents.vaults.credential_auth_rotate_param.RotateVaultCredentialAuthParamMcpOauth", - "beta.agents.vaults.credential_auth_rotate_param.RotateVaultCredentialAuthParamStaticBearer" - ], - "vault_id": [] - }, - "beta.agents.vaults.mcp_oauth_token_endpoint_auth.McpOauthTokenEndpointAuthResourceClientSecretBasic": { - "type": [] - }, - "beta.agents.vaults.mcp_oauth_token_endpoint_auth.McpOauthTokenEndpointAuthResourceClientSecretPost": { - "type": [] - }, - "beta.agents.vaults.mcp_oauth_token_endpoint_auth.McpOauthTokenEndpointAuthResourceNone": { - "type": [] - }, - "beta.agents.vaults.mcp_oauth_token_endpoint_auth_create_param.CreateMcpOauthTokenEndpointAuthParamClientSecretBasic": { - "client_secret": [], - "type": [] - }, - "beta.agents.vaults.mcp_oauth_token_endpoint_auth_create_param.CreateMcpOauthTokenEndpointAuthParamClientSecretPost": { - "client_secret": [], - "type": [] - }, - "beta.agents.vaults.mcp_oauth_token_endpoint_auth_create_param.CreateMcpOauthTokenEndpointAuthParamNone": { - "type": [] - }, - "beta.agents.vaults.mcp_oauth_token_endpoint_auth_rotate_param.RotateMcpOauthTokenEndpointAuthParamClientSecretBasic": { - "client_secret": [], - "type": [] - }, - "beta.agents.vaults.mcp_oauth_token_endpoint_auth_rotate_param.RotateMcpOauthTokenEndpointAuthParamClientSecretPost": { - "client_secret": [], - "type": [] - }, - "beta.environment.EnvironmentResourceNone": { - "type": [] - }, - "beta.environment.EnvironmentResourceOpenAIHosted": { - "capability_directories": [], - "files": [ - "beta.hosted_environment_file.HostedEnvironmentFileResourceInline", - "beta.hosted_environment_file_id.HostedEnvironmentFileID" - ], - "id": [], - "network": [ - "beta.environment.EnvironmentResourceOpenAIHostedNetwork" - ], - "packages": [ - "beta.environment.EnvironmentResourceOpenAIHostedPackages" - ], - "plugins": [ - "beta.hosted_plugin.HostedPlugin" - ], - "skills": [ - "beta.hosted_skill.HostedSkillResourceInline", - "beta.hosted_skill_reference.HostedSkillReference" - ], - "type": [] - }, - "beta.environment.EnvironmentResourceOpenAIHostedNetwork": { - "access": [], - "allowed_domains": [] - }, - "beta.environment.EnvironmentResourceOpenAIHostedPackages": { - "npm": [], - "python": [], - "system": [] - }, - "beta.environment.EnvironmentResourceSelfHosted": { - "capability_directories": [], - "id": [], - "remote_url": [], - "type": [], - "workspace_directory": [] - }, - "beta.environment_param.EnvironmentParamNone": { - "type": [] - }, - "beta.environment_param.EnvironmentParamOpenAIHosted": { - "capability_directories": [], - "env": [], - "environment_template_id": [], - "files": [ - "beta.hosted_environment_file_param.HostedEnvironmentFileParamFileID", - "beta.hosted_environment_file_param.HostedEnvironmentFileParamInline" - ], - "network": [ - "beta.environment_param.EnvironmentParamOpenAIHostedNetwork" - ], - "packages": [ - "beta.environment_param.EnvironmentParamOpenAIHostedPackages" - ], - "plugins": [ - "beta.hosted_plugin_param.HostedPluginParam" - ], - "setup_commands": [ - "beta.setup_command_param.SetupCommandParam" - ], - "skills": [ - "beta.hosted_skill_param.HostedSkillParamInline", - "beta.hosted_skill_param.HostedSkillParamSkillReference" - ], - "type": [] - }, - "beta.environment_param.EnvironmentParamOpenAIHostedNetwork": { - "access": [], - "allowed_domains": [] - }, - "beta.environment_param.EnvironmentParamOpenAIHostedPackages": { - "npm": [], - "python": [], - "system": [] - }, - "beta.environment_param.EnvironmentParamSelfHosted": { - "capability_directories": [], - "type": [], - "workspace_directory": [] - }, - "beta.hosted_environment_file.HostedEnvironmentFileResourceInline": { - "id": [], - "path": [], - "size_bytes": [], - "type": [] - }, - "beta.hosted_environment_file_id.HostedEnvironmentFileID": { - "file_id": [], - "id": [], - "path": [], - "size_bytes": [], - "type": [] - }, - "beta.hosted_environment_file_param.HostedEnvironmentFileParamFileID": { - "file_id": [], - "path": [], - "type": [] - }, - "beta.hosted_environment_file_param.HostedEnvironmentFileParamInline": { - "data": [], - "path": [], - "type": [] - }, - "beta.hosted_plugin.HostedPlugin": { - "description": [], - "name": [], - "type": [] - }, - "beta.hosted_plugin_param.HostedPluginParam": { - "description": [], - "name": [], - "source": [ - "beta.inline_capability_source_param.InlineCapabilitySourceParam" - ], - "type": [] - }, - "beta.hosted_skill.HostedSkillResourceInline": { - "description": [], - "name": [], - "type": [] - }, - "beta.hosted_skill_param.HostedSkillParamInline": { - "description": [], - "name": [], - "source": [ - "beta.inline_capability_source_param.InlineCapabilitySourceParam" - ], - "type": [] - }, - "beta.hosted_skill_param.HostedSkillParamSkillReference": { - "skill_id": [], - "type": [], - "version": [] - }, - "beta.hosted_skill_reference.HostedSkillReference": { - "description": [], - "name": [], - "skill_id": [], - "type": [], - "version": [] - }, - "beta.inline_capability_source_param.InlineCapabilitySourceParam": { - "data": [], - "media_type": [], - "type": [] - }, - "beta.input_content.InputContentResourceInputImage": { - "image_url": [], - "type": [] - }, - "beta.input_content.InputContentResourceInputText": { - "text": [], - "type": [] - }, - "beta.input_content_param.InputContentParamInputImage": { - "image_url": [], - "type": [] - }, - "beta.input_content_param.InputContentParamInputText": { - "text": [], - "type": [] - }, - "beta.mcp_transport.McpTransportResourceHTTP": { - "server_url": [], - "type": [] - }, - "beta.mcp_transport.McpTransportResourceStdio": { - "args": [], - "command": [], - "cwd": [], - "env_vars": [], - "type": [] - }, - "beta.mcp_transport_param.McpTransportConfigParamHTTP": { - "authorization": [], - "headers": [], - "server_url": [], - "type": [] - }, - "beta.mcp_transport_param.McpTransportConfigParamStdio": { - "args": [], - "command": [], - "cwd": [], - "env": [], - "env_vars": [], - "type": [] - }, - "beta.multi_agent_config.MultiAgentConfig": { - "enabled": [], - "max_concurrent_subagents": [] - }, - "beta.multi_agent_config_param.MultiAgentConfigParam": { - "enabled": [], - "max_concurrent_subagents": [] - }, - "beta.output_text.OutputText": { - "text": [], - "type": [] - }, - "beta.persisted_agent_tool.PersistedAgentToolResourceFunction": { - "defer_loading": [], - "description": [], - "name": [], - "parameters": [], - "type": [] - }, - "beta.persisted_agent_tool.PersistedAgentToolResourceMcp": { - "allowed_tools": [], - "connection_origin": [], - "credential_id": [], - "request_metadata": [], - "required": [], - "server_label": [], - "transport": [ - "beta.persisted_mcp_transport.PersistedMcpTransportResourceHTTP", - "beta.persisted_mcp_transport.PersistedMcpTransportResourceStdio" - ], - "type": [] - }, - "beta.persisted_agent_tool.PersistedAgentToolResourceProgrammaticToolCalling": { - "enabled": [], - "type": [] - }, - "beta.persisted_agent_tool.PersistedAgentToolResourceToolSearch": { - "type": [] - }, - "beta.persisted_agent_tool.PersistedAgentToolResourceWebSearch": { - "allowed_domains": [], - "context_size": [], - "location": [ - "beta.persisted_agent_tool.PersistedAgentToolResourceWebSearchLocation" - ], - "mode": [], - "type": [] - }, - "beta.persisted_agent_tool.PersistedAgentToolResourceWebSearchLocation": { - "city": [], - "country": [], - "region": [], - "timezone": [] - }, - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamFunction": { - "defer_loading": [], - "description": [], - "name": [], - "parameters": [], - "type": [] - }, - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamMcp": { - "allowed_tools": [], - "connection_origin": [], - "credential_id": [], - "request_metadata": [], - "required": [], - "server_label": [], - "transport": [ - "beta.persisted_mcp_transport_param.PersistedMcpTransportConfigParamHTTP", - "beta.persisted_mcp_transport_param.PersistedMcpTransportConfigParamStdio" - ], - "type": [] - }, - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamProgrammaticToolCalling": { - "enabled": [], - "type": [] - }, - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamToolSearch": { - "type": [] - }, - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamWebSearch": { - "allowed_domains": [], - "context_size": [], - "location": [ - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamWebSearchLocation" - ], - "mode": [], - "type": [] - }, - "beta.persisted_agent_tool_param.PersistedAgentToolConfigParamWebSearchLocation": { - "city": [], - "country": [], - "region": [], - "timezone": [] - }, - "beta.persisted_mcp_transport.PersistedMcpTransportResourceHTTP": { - "headers": [], - "server_url": [], - "type": [] - }, - "beta.persisted_mcp_transport.PersistedMcpTransportResourceStdio": { - "args": [], - "command": [], - "cwd": [], - "env_vars": [], - "type": [] - }, - "beta.persisted_mcp_transport_param.PersistedMcpTransportConfigParamHTTP": { - "headers": [], - "server_url": [], - "type": [] - }, - "beta.persisted_mcp_transport_param.PersistedMcpTransportConfigParamStdio": { - "args": [], - "command": [], - "cwd": [], - "env_vars": [], - "type": [] - }, - "beta.session_error.SessionError": { - "code": [], - "message": [], - "param": [], - "type": [] - }, - "beta.session_turn_error.SessionTurnError": { - "code": [], - "message": [] - }, - "beta.setup_command_param.SetupCommandParam": { - "command": [], - "cwd": [] - }, - "beta.subagent.Subagent": { - "closed_at": [], - "id": [], - "instructions": [ - "beta.agent_content.EncryptedContentResource", - "beta.output_text.OutputText" - ], - "name": [], - "object": [], - "opened_at": [], - "parent_agent_id": [], - "session_id": [], - "status": [] - }, - "beta.summary_text.SummaryText": { - "text": [], - "type": [] - }, - "beta.text_format.TextFormatResourceJSONSchema": { - "schema": [], - "type": [] - }, - "beta.text_format.TextFormatResourceText": { - "type": [] - }, - "beta.text_format_param.TextFormatParamJSONSchema": { - "schema": [], - "type": [] - }, - "beta.text_format_param.TextFormatParamText": { - "type": [] - }, - "beta.token_usage.InputTokensDetails": { - "cached_tokens": [] - }, - "beta.token_usage.OutputTokensDetails": { - "reasoning_tokens": [] - }, - "beta.token_usage.TokenUsage": { - "input_tokens": [], - "input_tokens_details": [ - "beta.token_usage.InputTokensDetails" - ], - "output_tokens": [], - "output_tokens_details": [ - "beta.token_usage.OutputTokensDetails" - ], - "total_tokens": [] - }, - "beta.web_search_action.WebSearchActionResourceFindInPage": { - "pattern": [], - "type": [], - "url": [] - }, - "beta.web_search_action.WebSearchActionResourceOpenPage": { - "type": [], - "url": [] - }, - "beta.web_search_action.WebSearchActionResourceOther": { - "type": [] - }, - "beta.web_search_action.WebSearchActionResourceSearch": { - "queries": [], - "query": [], - "type": [] - }, - "deleted_skill.DeletedSkill": { - "deleted": [], - "id": [], - "object": [] - }, - "file_create_params.ExpiresAfter": { - "anchor": [], - "seconds": [] - }, - "file_create_params.FileCreateParams": { - "expires_after": [ - "file_create_params.ExpiresAfter" - ], - "file": [], - "purpose": [] - }, - "file_deleted.FileDeleted": { - "deleted": [], - "id": [], - "object": [] - }, - "file_list_params.FileListParams": { - "after": [], - "limit": [], - "order": [], - "purpose": [] - }, - "file_object.FileObject": { - "bytes": [], - "created_at": [], - "expires_at": [], - "filename": [], - "id": [], - "object": [], - "purpose": [], - "status": [], - "status_details": [] - }, - "pagination.SyncCursorPage[beta.agent.Agent]": { - "data": [ - "beta.agent.Agent" - ], - "has_more": [] - }, - "pagination.SyncCursorPage[beta.agent_close_subagent_call_item.AgentCloseSubagentCallItem | beta.agent_command_execution_item.AgentCommandExecutionItem | beta.agent_create_subagent_call_item.AgentCreateSubagentCallItem | beta.agent_function_call_item.AgentFunctionCallItem | beta.agent_interrupt_subagent_call_item.AgentInterruptSubagentCallItem | beta.agent_mcp_call_item.AgentMcpCallItem | beta.agent_reasoning_item.AgentReasoningItem | beta.agent_resume_subagent_call_item.AgentResumeSubagentCallItem | beta.agent_send_subagent_input_call_item.AgentSendSubagentInputCallItem | beta.agent_session_item.AgentMessageItemResource | beta.agent_session_item.FunctionCallOutputItemResource | beta.agent_session_message.AgentSessionMessage | beta.agent_wait_for_subagents_call_item.AgentWaitForSubagentsCallItem | beta.agent_web_search_call_item.AgentWebSearchCallItem]": { - "data": [ - "beta.agent_close_subagent_call_item.AgentCloseSubagentCallItem", - "beta.agent_command_execution_item.AgentCommandExecutionItem", - "beta.agent_create_subagent_call_item.AgentCreateSubagentCallItem", - "beta.agent_function_call_item.AgentFunctionCallItem", - "beta.agent_interrupt_subagent_call_item.AgentInterruptSubagentCallItem", - "beta.agent_mcp_call_item.AgentMcpCallItem", - "beta.agent_reasoning_item.AgentReasoningItem", - "beta.agent_resume_subagent_call_item.AgentResumeSubagentCallItem", - "beta.agent_send_subagent_input_call_item.AgentSendSubagentInputCallItem", - "beta.agent_session_item.AgentMessageItemResource", - "beta.agent_session_item.FunctionCallOutputItemResource", - "beta.agent_session_message.AgentSessionMessage", - "beta.agent_wait_for_subagents_call_item.AgentWaitForSubagentsCallItem", - "beta.agent_web_search_call_item.AgentWebSearchCallItem" - ], - "has_more": [] - }, - "pagination.SyncCursorPage[beta.agent_session.AgentSession]": { - "data": [ - "beta.agent_session.AgentSession" - ], - "has_more": [] - }, - "pagination.SyncCursorPage[beta.agents.environments.environment_template.EnvironmentTemplate]": { - "data": [ - "beta.agents.environments.environment_template.EnvironmentTemplate" - ], - "has_more": [] - }, - "pagination.SyncCursorPage[beta.agents.sessions.session_artifact.SessionArtifact]": { - "data": [ - "beta.agents.sessions.session_artifact.SessionArtifact" - ], - "has_more": [] - }, - "pagination.SyncCursorPage[beta.agents.sessions.turn.Turn]": { - "data": [ - "beta.agents.sessions.turn.Turn" - ], - "has_more": [] - }, - "pagination.SyncCursorPage[beta.agents.vault.Vault]": { - "data": [ - "beta.agents.vault.Vault" - ], - "has_more": [] - }, - "pagination.SyncCursorPage[beta.agents.vaults.credential.Credential]": { - "data": [ - "beta.agents.vaults.credential.Credential" - ], - "has_more": [] - }, - "pagination.SyncCursorPage[beta.subagent.Subagent]": { - "data": [ - "beta.subagent.Subagent" - ], - "has_more": [] - }, - "pagination.SyncCursorPage[file_object.FileObject]": { - "data": [ - "file_object.FileObject" - ], - "has_more": [] - }, - "pagination.SyncCursorPage[skill.Skill]": { - "data": [ - "skill.Skill" - ], - "has_more": [] - }, - "pagination.SyncCursorPage[skills.skill_version.SkillVersion]": { - "data": [ - "skills.skill_version.SkillVersion" - ], - "has_more": [] - }, - "pagination.SyncTokenPage[beta.agents.environments.environment_file.EnvironmentFile]": { - "data": [ - "beta.agents.environments.environment_file.EnvironmentFile" - ], - "has_more": [], - "next": [] - }, - "skill.Skill": { - "created_at": [], - "default_version": [], - "description": [], - "id": [], - "latest_version": [], - "name": [], - "object": [] - }, - "skill_create_params.SkillCreateParams": { - "files": [] - }, - "skill_list_params.SkillListParams": { - "after": [], - "limit": [], - "order": [] - }, - "skill_update_params.SkillUpdateParams": { - "default_version": [] - }, - "skills.deleted_skill_version.DeletedSkillVersion": { - "deleted": [], - "id": [], - "object": [], - "version": [] - }, - "skills.skill_version.SkillVersion": { - "created_at": [], - "description": [], - "id": [], - "name": [], - "object": [], - "skill_id": [], - "version": [] - }, - "skills.version_create_params.VersionCreateParams": { - "default": [], - "files": [] - }, - "skills.version_list_params.VersionListParams": { - "after": [], - "limit": [], - "order": [] - } - } -} diff --git a/contracts/agents-api/upstream-routes.json b/contracts/agents-api/upstream-routes.json index 3326834f2..1f305b93a 100644 --- a/contracts/agents-api/upstream-routes.json +++ b/contracts/agents-api/upstream-routes.json @@ -1,12 +1,6 @@ { - "sdk_version": "3.13.0", - "commit": "d7c41efee1b0802b79f3f88a678ef2052b06e9ce", - "generator": "scripts/extract-agents-api-upstream.py", - "resources": [ - "openai.resources.beta.agents", - "openai.resources.files", - "openai.resources.skills" - ], + "openapi_commit": "046a2a0f325bf11f97966f2729219f27281ba71e", + "generator": "scripts/generate-public-api.py", "routes": [ "DELETE /agents/environments/templates/{}", "DELETE /agents/sessions/{}", diff --git a/contracts/agents-api/upstream.json b/contracts/agents-api/upstream.json index adc71d0bb..253e00627 100644 --- a/contracts/agents-api/upstream.json +++ b/contracts/agents-api/upstream.json @@ -4,5 +4,11 @@ "sdk_version": "3.13.0", "resource_path": "src/openai/resources/beta/agents", "type_path": "src/openai/types/beta", - "beta_header": "agents=v1" + "beta_header": "agents=v1", + "openapi": { + "repository": "https://github.com/openai/openai-openapi", + "commit": "046a2a0f325bf11f97966f2729219f27281ba71e", + "path": "openapi.json", + "sha256": "c96d974b164ff4750fa5ea5a1727f7ce6691540e257c177db02b660b0db58f26" + } } diff --git a/contracts/agents-api/upstream/LICENSE b/contracts/agents-api/upstream/LICENSE new file mode 100644 index 000000000..4f14854c3 --- /dev/null +++ b/contracts/agents-api/upstream/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) OpenAI (https://openai.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/contracts/agents-api/upstream/openapi.json b/contracts/agents-api/upstream/openapi.json new file mode 100644 index 000000000..f966c2530 --- /dev/null +++ b/contracts/agents-api/upstream/openapi.json @@ -0,0 +1,107618 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "OpenAI API", + "description": "The OpenAI REST API. Please see https://platform.openai.com/docs/api-reference for more details.", + "version": "2.3.0", + "termsOfService": "https://openai.com/policies/terms-of-use", + "contact": { + "name": "OpenAI Support", + "url": "https://help.openai.com/" + }, + "license": { + "name": "MIT", + "identifier": "MIT" + } + }, + "servers": [ + { + "url": "https://api.openai.com/v1" + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + { + "name": "Assistants", + "description": "Build Assistants that can call models and use tools." + }, + { + "name": "Audio", + "description": "Turn audio into text or text into audio." + }, + { + "name": "Chat", + "description": "Given a list of messages comprising a conversation, the model will return a response." + }, + { + "name": "Conversations", + "description": "Manage conversations and conversation items." + }, + { + "name": "Completions", + "description": "Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position." + }, + { + "name": "Embeddings", + "description": "Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms." + }, + { + "name": "Evals", + "description": "Manage and run evals in the OpenAI platform." + }, + { + "name": "Fine-tuning", + "description": "Manage fine-tuning jobs to tailor a model to your specific training data." + }, + { + "name": "Graders", + "description": "Manage and run graders in the OpenAI platform." + }, + { + "name": "Batch", + "description": "Create large batches of API requests to run asynchronously." + }, + { + "name": "Files", + "description": "Files are used to upload documents that can be used with features like Assistants and Fine-tuning." + }, + { + "name": "Uploads", + "description": "Use Uploads to upload large files in multiple parts." + }, + { + "name": "Images", + "description": "Given a prompt and/or an input image, the model will generate a new image." + }, + { + "name": "Models", + "description": "List and describe the various models available in the API." + }, + { + "name": "Moderations", + "description": "Given text and/or image inputs, classifies if those inputs are potentially harmful." + }, + { + "name": "Audit Logs", + "description": "List user actions and configuration changes within this organization." + } + ], + "paths": { + "/assistants": { + "get": { + "operationId": "listAssistants", + "tags": [ + "Assistants" + ], + "summary": "Returns a list of assistants.", + "deprecated": true, + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "schema": { + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "schema": { + "type": "string" + } + }, + { + "name": "before", + "in": "query", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListAssistantsResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List assistants", + "group": "assistants", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/assistants?order=desc&limit=20\" \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"OpenAI-Beta: assistants=v2\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nmy_assistants = client.beta.assistants.list(\n order=\"desc\",\n limit=\"20\",\n)\nprint(my_assistants.data)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const myAssistants = await openai.beta.assistants.list({\n order: \"desc\",\n limit: \"20\",\n });\n\n console.log(myAssistants.data);\n}\n\nmain();" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"asst_abc123\",\n \"object\": \"assistant\",\n \"created_at\": 1698982736,\n \"name\": \"Coding Tutor\",\n \"description\": null,\n \"model\": \"gpt-5\",\n \"instructions\": \"You are a helpful assistant designed to make me better at coding!\",\n \"tools\": [],\n \"tool_resources\": {},\n \"metadata\": {},\n \"top_p\": 1.0,\n \"temperature\": 1.0,\n \"response_format\": \"auto\"\n },\n {\n \"id\": \"asst_abc456\",\n \"object\": \"assistant\",\n \"created_at\": 1698982718,\n \"name\": \"My Assistant\",\n \"description\": null,\n \"model\": \"gpt-5\",\n \"instructions\": \"You are a helpful assistant designed to make me better at coding!\",\n \"tools\": [],\n \"tool_resources\": {},\n \"metadata\": {},\n \"top_p\": 1.0,\n \"temperature\": 1.0,\n \"response_format\": \"auto\"\n },\n {\n \"id\": \"asst_abc789\",\n \"object\": \"assistant\",\n \"created_at\": 1698982643,\n \"name\": null,\n \"description\": null,\n \"model\": \"gpt-5\",\n \"instructions\": null,\n \"tools\": [],\n \"tool_resources\": {},\n \"metadata\": {},\n \"top_p\": 1.0,\n \"temperature\": 1.0,\n \"response_format\": \"auto\"\n }\n ],\n \"first_id\": \"asst_abc123\",\n \"last_id\": \"asst_abc789\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "operationId": "createAssistant", + "tags": [ + "Assistants" + ], + "summary": "Create an assistant with a model and instructions.", + "deprecated": true, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAssistantRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssistantObject" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Create assistant", + "group": "assistants", + "examples": [ + { + "title": "Code Interpreter", + "request": { + "curl": "curl \"https://api.openai.com/v1/assistants\" \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"OpenAI-Beta: assistants=v2\" \\\n -d '{\n \"instructions\": \"You are a personal math tutor. When asked a question, write and run Python code to answer the question.\",\n \"name\": \"Math Tutor\",\n \"tools\": [{\"type\": \"code_interpreter\"}],\n \"model\": \"gpt-5\"\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nmy_assistant = client.beta.assistants.create(\n instructions=\"You are a personal math tutor. When asked a question, write and run Python code to answer the question.\",\n name=\"Math Tutor\",\n tools=[{\"type\": \"code_interpreter\"}],\n model=\"gpt-5\",\n)\nprint(my_assistant)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const myAssistant = await openai.beta.assistants.create({\n instructions:\n \"You are a personal math tutor. When asked a question, write and run Python code to answer the question.\",\n name: \"Math Tutor\",\n tools: [{ type: \"code_interpreter\" }],\n model: \"gpt-5\",\n });\n\n console.log(myAssistant);\n}\n\nmain();" + }, + "response": "{\n \"id\": \"asst_abc123\",\n \"object\": \"assistant\",\n \"created_at\": 1698984975,\n \"name\": \"Math Tutor\",\n \"description\": null,\n \"model\": \"gpt-5\",\n \"instructions\": \"You are a personal math tutor. When asked a question, write and run Python code to answer the question.\",\n \"tools\": [\n {\n \"type\": \"code_interpreter\"\n }\n ],\n \"metadata\": {},\n \"top_p\": 1.0,\n \"temperature\": 1.0,\n \"response_format\": \"auto\"\n}\n" + }, + { + "title": "Files", + "request": { + "curl": "curl https://api.openai.com/v1/assistants \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"OpenAI-Beta: assistants=v2\" \\\n -d '{\n \"instructions\": \"You are an HR bot, and you have access to files to answer employee questions about company policies.\",\n \"tools\": [{\"type\": \"file_search\"}],\n \"tool_resources\": {\"file_search\": {\"vector_store_ids\": [\"vs_123\"]}},\n \"model\": \"gpt-5\"\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nmy_assistant = client.beta.assistants.create(\n instructions=\"You are an HR bot, and you have access to files to answer employee questions about company policies.\",\n name=\"HR Helper\",\n tools=[{\"type\": \"file_search\"}],\n tool_resources={\"file_search\": {\"vector_store_ids\": [\"vs_123\"]}},\n model=\"gpt-5\"\n)\nprint(my_assistant)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const myAssistant = await openai.beta.assistants.create({\n instructions:\n \"You are an HR bot, and you have access to files to answer employee questions about company policies.\",\n name: \"HR Helper\",\n tools: [{ type: \"file_search\" }],\n tool_resources: {\n file_search: {\n vector_store_ids: [\"vs_123\"]\n }\n },\n model: \"gpt-5\"\n });\n\n console.log(myAssistant);\n}\n\nmain();" + }, + "response": "{\n \"id\": \"asst_abc123\",\n \"object\": \"assistant\",\n \"created_at\": 1699009403,\n \"name\": \"HR Helper\",\n \"description\": null,\n \"model\": \"gpt-5\",\n \"instructions\": \"You are an HR bot, and you have access to files to answer employee questions about company policies.\",\n \"tools\": [\n {\n \"type\": \"file_search\"\n }\n ],\n \"tool_resources\": {\n \"file_search\": {\n \"vector_store_ids\": [\"vs_123\"]\n }\n },\n \"metadata\": {},\n \"top_p\": 1.0,\n \"temperature\": 1.0,\n \"response_format\": \"auto\"\n}\n" + } + ] + } + } + }, + "/assistants/{assistant_id}": { + "get": { + "operationId": "getAssistant", + "tags": [ + "Assistants" + ], + "summary": "Retrieves an assistant.", + "deprecated": true, + "parameters": [ + { + "in": "path", + "name": "assistant_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the assistant to retrieve." + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssistantObject" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve assistant", + "group": "assistants", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/assistants/asst_abc123 \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"OpenAI-Beta: assistants=v2\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nmy_assistant = client.beta.assistants.retrieve(\"asst_abc123\")\nprint(my_assistant)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const myAssistant = await openai.beta.assistants.retrieve(\n \"asst_abc123\"\n );\n\n console.log(myAssistant);\n}\n\nmain();" + }, + "response": "{\n \"id\": \"asst_abc123\",\n \"object\": \"assistant\",\n \"created_at\": 1699009709,\n \"name\": \"HR Helper\",\n \"description\": null,\n \"model\": \"gpt-5\",\n \"instructions\": \"You are an HR bot, and you have access to files to answer employee questions about company policies.\",\n \"tools\": [\n {\n \"type\": \"file_search\"\n }\n ],\n \"metadata\": {},\n \"top_p\": 1.0,\n \"temperature\": 1.0,\n \"response_format\": \"auto\"\n}\n" + } + } + }, + "post": { + "operationId": "modifyAssistant", + "tags": [ + "Assistants" + ], + "summary": "Modifies an assistant.", + "deprecated": true, + "parameters": [ + { + "in": "path", + "name": "assistant_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the assistant to modify." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModifyAssistantRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssistantObject" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Modify assistant", + "group": "assistants", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/assistants/asst_abc123 \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"OpenAI-Beta: assistants=v2\" \\\n -d '{\n \"instructions\": \"You are an HR bot, and you have access to files to answer employee questions about company policies. Always response with info from either of the files.\",\n \"tools\": [{\"type\": \"file_search\"}],\n \"model\": \"gpt-5\"\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nmy_updated_assistant = client.beta.assistants.update(\n \"asst_abc123\",\n instructions=\"You are an HR bot, and you have access to files to answer employee questions about company policies. Always response with info from either of the files.\",\n name=\"HR Helper\",\n tools=[{\"type\": \"file_search\"}],\n model=\"gpt-5\"\n)\n\nprint(my_updated_assistant)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const myUpdatedAssistant = await openai.beta.assistants.update(\n \"asst_abc123\",\n {\n instructions:\n \"You are an HR bot, and you have access to files to answer employee questions about company policies. Always response with info from either of the files.\",\n name: \"HR Helper\",\n tools: [{ type: \"file_search\" }],\n model: \"gpt-5\"\n }\n );\n\n console.log(myUpdatedAssistant);\n}\n\nmain();" + }, + "response": "{\n \"id\": \"asst_123\",\n \"object\": \"assistant\",\n \"created_at\": 1699009709,\n \"name\": \"HR Helper\",\n \"description\": null,\n \"model\": \"gpt-5\",\n \"instructions\": \"You are an HR bot, and you have access to files to answer employee questions about company policies. Always response with info from either of the files.\",\n \"tools\": [\n {\n \"type\": \"file_search\"\n }\n ],\n \"tool_resources\": {\n \"file_search\": {\n \"vector_store_ids\": []\n }\n },\n \"metadata\": {},\n \"top_p\": 1.0,\n \"temperature\": 1.0,\n \"response_format\": \"auto\"\n}\n" + } + } + }, + "delete": { + "operationId": "deleteAssistant", + "tags": [ + "Assistants" + ], + "summary": "Delete an assistant.", + "deprecated": true, + "parameters": [ + { + "in": "path", + "name": "assistant_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the assistant to delete." + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteAssistantResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Delete assistant", + "group": "assistants", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/assistants/asst_abc123 \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"OpenAI-Beta: assistants=v2\" \\\n -X DELETE\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nresponse = client.beta.assistants.delete(\"asst_abc123\")\nprint(response)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const response = await openai.beta.assistants.delete(\"asst_abc123\");\n\n console.log(response);\n}\nmain();" + }, + "response": "{\n \"id\": \"asst_abc123\",\n \"object\": \"assistant.deleted\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/audio/speech": { + "post": { + "operationId": "createSpeech", + "tags": [ + "Audio" + ], + "summary": "Generates audio from the input text.\n\nReturns the audio file content, or a stream of audio events.\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSpeechRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "headers": { + "Transfer-Encoding": { + "schema": { + "type": "string" + }, + "description": "chunked" + } + }, + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/CreateSpeechResponseStreamEvent" + } + } + } + }, + "429": { + "$ref": "#/components/responses/InferenceRateLimited" + }, + "503": { + "$ref": "#/components/responses/InferenceServiceUnavailable" + } + }, + "x-oaiMeta": { + "name": "Create speech", + "group": "audio", + "examples": [ + { + "title": "Default", + "request": { + "curl": "curl https://api.openai.com/v1/audio/speech \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"model\": \"gpt-4o-mini-tts\",\n \"input\": \"The quick brown fox jumped over the lazy dog.\",\n \"voice\": \"alloy\"\n }' \\\n --output speech.mp3\n", + "python": "from pathlib import Path\nimport openai\n\nspeech_file_path = Path(__file__).parent / \"speech.mp3\"\nwith openai.audio.speech.with_streaming_response.create(\n model=\"gpt-4o-mini-tts\",\n voice=\"alloy\",\n input=\"The quick brown fox jumped over the lazy dog.\"\n) as response:\n response.stream_to_file(speech_file_path)\n", + "javascript": "import fs from \"fs\";\nimport path from \"path\";\nimport OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst speechFile = path.resolve(\"./speech.mp3\");\n\nasync function main() {\n const mp3 = await openai.audio.speech.create({\n model: \"gpt-4o-mini-tts\",\n voice: \"alloy\",\n input: \"Today is a wonderful day to build something people love!\",\n });\n console.log(speechFile);\n const buffer = Buffer.from(await mp3.arrayBuffer());\n await fs.promises.writeFile(speechFile, buffer);\n}\nmain();\n", + "csharp": "using System;\nusing System.IO;\n\nusing OpenAI.Audio;\n\nAudioClient client = new(\n model: \"gpt-4o-mini-tts\",\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nBinaryData speech = client.GenerateSpeech(\n text: \"The quick brown fox jumped over the lazy dog.\",\n voice: GeneratedSpeechVoice.Alloy\n);\n\nusing FileStream stream = File.OpenWrite(\"speech.mp3\");\nspeech.ToStream().CopyTo(stream);\n" + } + }, + { + "title": "SSE Stream Format", + "request": { + "curl": "curl https://api.openai.com/v1/audio/speech \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"model\": \"gpt-4o-mini-tts\",\n \"input\": \"The quick brown fox jumped over the lazy dog.\",\n \"voice\": \"alloy\",\n \"stream_format\": \"sse\"\n }'\n" + } + } + ] + } + } + }, + "/audio/transcriptions": { + "post": { + "operationId": "createTranscription", + "tags": [ + "Audio" + ], + "summary": "Transcribes audio into the input language.\n\nReturns a transcription object in `json`, `diarized_json`, or `verbose_json`\nformat, or a stream of transcript events.\n", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateTranscriptionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateTranscriptionResponseJson" + }, + { + "$ref": "#/components/schemas/CreateTranscriptionResponseDiarizedJson" + }, + { + "$ref": "#/components/schemas/CreateTranscriptionResponseVerboseJson" + } + ] + } + }, + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/CreateTranscriptionResponseStreamEvent" + } + } + } + }, + "429": { + "$ref": "#/components/responses/InferenceRateLimited" + }, + "503": { + "$ref": "#/components/responses/InferenceServiceUnavailable" + } + }, + "x-oaiMeta": { + "name": "Create transcription", + "group": "audio", + "examples": [ + { + "title": "Default", + "request": { + "curl": "curl https://api.openai.com/v1/audio/transcriptions \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: multipart/form-data\" \\\n -F file=\"@/path/to/file/audio.mp3\" \\\n -F model=\"gpt-4o-transcribe\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\naudio_file = open(\"speech.mp3\", \"rb\")\ntranscript = client.audio.transcriptions.create(\n model=\"gpt-4o-transcribe\",\n file=audio_file\n)\n", + "javascript": "import fs from \"fs\";\nimport OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const transcription = await openai.audio.transcriptions.create({\n file: fs.createReadStream(\"audio.mp3\"),\n model: \"gpt-4o-transcribe\",\n });\n\n console.log(transcription.text);\n}\nmain();\n", + "csharp": "using System;\n\nusing OpenAI.Audio;\nstring audioFilePath = \"audio.mp3\";\n\nAudioClient client = new(\n model: \"gpt-4o-transcribe\",\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nAudioTranscription transcription = client.TranscribeAudio(audioFilePath);\n\nConsole.WriteLine($\"{transcription.Text}\");\n" + }, + "response": "{\n \"text\": \"Imagine the wildest idea that you've ever had, and you're curious about how it might scale to something that's a 100, a 1,000 times bigger. This is a place where you can get to do that.\",\n \"usage\": {\n \"type\": \"tokens\",\n \"input_tokens\": 14,\n \"input_token_details\": {\n \"text_tokens\": 0,\n \"audio_tokens\": 14\n },\n \"output_tokens\": 45,\n \"total_tokens\": 59\n }\n}\n" + }, + { + "title": "Diarization", + "request": { + "curl": "curl https://api.openai.com/v1/audio/transcriptions \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: multipart/form-data\" \\\n -F file=\"@/path/to/file/meeting.wav\" \\\n -F model=\"gpt-4o-transcribe-diarize\" \\\n -F response_format=\"diarized_json\" \\\n -F chunking_strategy=auto \\\n -F 'known_speaker_names[]=agent' \\\n -F 'known_speaker_references[]=data:audio/wav;base64,AAA...'\n", + "python": "import base64\nfrom openai import OpenAI\n\nclient = OpenAI()\n\ndef to_data_url(path: str) -> str:\n with open(path, \"rb\") as fh:\n return \"data:audio/wav;base64,\" + base64.b64encode(fh.read()).decode(\"utf-8\")\n\nwith open(\"meeting.wav\", \"rb\") as audio_file:\n transcript = client.audio.transcriptions.create(\n model=\"gpt-4o-transcribe-diarize\",\n file=audio_file,\n response_format=\"diarized_json\",\n chunking_strategy=\"auto\",\n extra_body={\n \"known_speaker_names\": [\"agent\"],\n \"known_speaker_references\": [to_data_url(\"agent.wav\")],\n },\n )\n\nprint(transcript.segments)\n", + "javascript": "import fs from \"fs\";\nimport OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst speakerRef = fs.readFileSync(\"agent.wav\").toString(\"base64\");\n\nconst transcript = await openai.audio.transcriptions.create({\n file: fs.createReadStream(\"meeting.wav\"),\n model: \"gpt-4o-transcribe-diarize\",\n response_format: \"diarized_json\",\n chunking_strategy: \"auto\",\n extra_body: {\n known_speaker_names: [\"agent\"],\n known_speaker_references: [`data:audio/wav;base64,${speakerRef}`],\n },\n});\n\nconsole.log(transcript.segments);\n" + }, + "response": "{\n \"task\": \"transcribe\",\n \"duration\": 27.4,\n \"text\": \"Agent: Thanks for calling OpenAI support.\\nA: Hi, I'm trying to enable diarization.\\nAgent: Happy to walk you through the steps.\",\n \"segments\": [\n {\n \"type\": \"transcript.text.segment\",\n \"id\": \"seg_001\",\n \"start\": 0.0,\n \"end\": 4.7,\n \"text\": \"Thanks for calling OpenAI support.\",\n \"speaker\": \"agent\"\n },\n {\n \"type\": \"transcript.text.segment\",\n \"id\": \"seg_002\",\n \"start\": 4.7,\n \"end\": 11.8,\n \"text\": \"Hi, I'm trying to enable diarization.\",\n \"speaker\": \"A\"\n },\n {\n \"type\": \"transcript.text.segment\",\n \"id\": \"seg_003\",\n \"start\": 12.1,\n \"end\": 18.5,\n \"text\": \"Happy to walk you through the steps.\",\n \"speaker\": \"agent\"\n }\n ],\n \"usage\": {\n \"type\": \"duration\",\n \"seconds\": 27\n }\n}\n" + }, + { + "title": "Streaming", + "request": { + "curl": "curl https://api.openai.com/v1/audio/transcriptions \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: multipart/form-data\" \\\n -F file=\"@/path/to/file/audio.mp3\" \\\n -F model=\"gpt-4o-mini-transcribe\" \\\n -F stream=true\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\naudio_file = open(\"speech.mp3\", \"rb\")\nstream = client.audio.transcriptions.create(\n file=audio_file,\n model=\"gpt-4o-mini-transcribe\",\n stream=True\n)\n\nfor event in stream:\n print(event)\n", + "javascript": "import fs from \"fs\";\nimport OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst stream = await openai.audio.transcriptions.create({\n file: fs.createReadStream(\"audio.mp3\"),\n model: \"gpt-4o-mini-transcribe\",\n stream: true,\n});\n\nfor await (const event of stream) {\n console.log(event);\n}\n" + }, + "response": "data: {\"type\":\"transcript.text.delta\",\"delta\":\"I\",\"logprobs\":[{\"token\":\"I\",\"logprob\":-0.00007588794,\"bytes\":[73]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" see\",\"logprobs\":[{\"token\":\" see\",\"logprob\":-3.1281633e-7,\"bytes\":[32,115,101,101]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" skies\",\"logprobs\":[{\"token\":\" skies\",\"logprob\":-2.3392786e-6,\"bytes\":[32,115,107,105,101,115]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" of\",\"logprobs\":[{\"token\":\" of\",\"logprob\":-3.1281633e-7,\"bytes\":[32,111,102]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" blue\",\"logprobs\":[{\"token\":\" blue\",\"logprob\":-1.0280384e-6,\"bytes\":[32,98,108,117,101]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" and\",\"logprobs\":[{\"token\":\" and\",\"logprob\":-0.0005108566,\"bytes\":[32,97,110,100]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" clouds\",\"logprobs\":[{\"token\":\" clouds\",\"logprob\":-1.9361265e-7,\"bytes\":[32,99,108,111,117,100,115]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" of\",\"logprobs\":[{\"token\":\" of\",\"logprob\":-1.9361265e-7,\"bytes\":[32,111,102]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" white\",\"logprobs\":[{\"token\":\" white\",\"logprob\":-7.89631e-7,\"bytes\":[32,119,104,105,116,101]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\",\",\"logprobs\":[{\"token\":\",\",\"logprob\":-0.0014890312,\"bytes\":[44]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" the\",\"logprobs\":[{\"token\":\" the\",\"logprob\":-0.0110956915,\"bytes\":[32,116,104,101]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" bright\",\"logprobs\":[{\"token\":\" bright\",\"logprob\":0.0,\"bytes\":[32,98,114,105,103,104,116]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" blessed\",\"logprobs\":[{\"token\":\" blessed\",\"logprob\":-0.000045848617,\"bytes\":[32,98,108,101,115,115,101,100]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" days\",\"logprobs\":[{\"token\":\" days\",\"logprob\":-0.000010802739,\"bytes\":[32,100,97,121,115]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\",\",\"logprobs\":[{\"token\":\",\",\"logprob\":-0.00001700133,\"bytes\":[44]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" the\",\"logprobs\":[{\"token\":\" the\",\"logprob\":-0.0000118755715,\"bytes\":[32,116,104,101]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" dark\",\"logprobs\":[{\"token\":\" dark\",\"logprob\":-5.5122365e-7,\"bytes\":[32,100,97,114,107]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" sacred\",\"logprobs\":[{\"token\":\" sacred\",\"logprob\":-5.4385737e-6,\"bytes\":[32,115,97,99,114,101,100]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" nights\",\"logprobs\":[{\"token\":\" nights\",\"logprob\":-4.00813e-6,\"bytes\":[32,110,105,103,104,116,115]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\",\",\"logprobs\":[{\"token\":\",\",\"logprob\":-0.0036910512,\"bytes\":[44]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" and\",\"logprobs\":[{\"token\":\" and\",\"logprob\":-0.0031903093,\"bytes\":[32,97,110,100]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" I\",\"logprobs\":[{\"token\":\" I\",\"logprob\":-1.504853e-6,\"bytes\":[32,73]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" think\",\"logprobs\":[{\"token\":\" think\",\"logprob\":-4.3202e-7,\"bytes\":[32,116,104,105,110,107]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" to\",\"logprobs\":[{\"token\":\" to\",\"logprob\":-1.9361265e-7,\"bytes\":[32,116,111]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" myself\",\"logprobs\":[{\"token\":\" myself\",\"logprob\":-1.7432603e-6,\"bytes\":[32,109,121,115,101,108,102]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\",\",\"logprobs\":[{\"token\":\",\",\"logprob\":-0.29254505,\"bytes\":[44]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" what\",\"logprobs\":[{\"token\":\" what\",\"logprob\":-0.016815351,\"bytes\":[32,119,104,97,116]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" a\",\"logprobs\":[{\"token\":\" a\",\"logprob\":-3.1281633e-7,\"bytes\":[32,97]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" wonderful\",\"logprobs\":[{\"token\":\" wonderful\",\"logprob\":-2.1008714e-6,\"bytes\":[32,119,111,110,100,101,114,102,117,108]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\" world\",\"logprobs\":[{\"token\":\" world\",\"logprob\":-8.180258e-6,\"bytes\":[32,119,111,114,108,100]}]}\n\ndata: {\"type\":\"transcript.text.delta\",\"delta\":\".\",\"logprobs\":[{\"token\":\".\",\"logprob\":-0.014231676,\"bytes\":[46]}]}\n\ndata: {\"type\":\"transcript.text.done\",\"text\":\"I see skies of blue and clouds of white, the bright blessed days, the dark sacred nights, and I think to myself, what a wonderful world.\",\"logprobs\":[{\"token\":\"I\",\"logprob\":-0.00007588794,\"bytes\":[73]},{\"token\":\" see\",\"logprob\":-3.1281633e-7,\"bytes\":[32,115,101,101]},{\"token\":\" skies\",\"logprob\":-2.3392786e-6,\"bytes\":[32,115,107,105,101,115]},{\"token\":\" of\",\"logprob\":-3.1281633e-7,\"bytes\":[32,111,102]},{\"token\":\" blue\",\"logprob\":-1.0280384e-6,\"bytes\":[32,98,108,117,101]},{\"token\":\" and\",\"logprob\":-0.0005108566,\"bytes\":[32,97,110,100]},{\"token\":\" clouds\",\"logprob\":-1.9361265e-7,\"bytes\":[32,99,108,111,117,100,115]},{\"token\":\" of\",\"logprob\":-1.9361265e-7,\"bytes\":[32,111,102]},{\"token\":\" white\",\"logprob\":-7.89631e-7,\"bytes\":[32,119,104,105,116,101]},{\"token\":\",\",\"logprob\":-0.0014890312,\"bytes\":[44]},{\"token\":\" the\",\"logprob\":-0.0110956915,\"bytes\":[32,116,104,101]},{\"token\":\" bright\",\"logprob\":0.0,\"bytes\":[32,98,114,105,103,104,116]},{\"token\":\" blessed\",\"logprob\":-0.000045848617,\"bytes\":[32,98,108,101,115,115,101,100]},{\"token\":\" days\",\"logprob\":-0.000010802739,\"bytes\":[32,100,97,121,115]},{\"token\":\",\",\"logprob\":-0.00001700133,\"bytes\":[44]},{\"token\":\" the\",\"logprob\":-0.0000118755715,\"bytes\":[32,116,104,101]},{\"token\":\" dark\",\"logprob\":-5.5122365e-7,\"bytes\":[32,100,97,114,107]},{\"token\":\" sacred\",\"logprob\":-5.4385737e-6,\"bytes\":[32,115,97,99,114,101,100]},{\"token\":\" nights\",\"logprob\":-4.00813e-6,\"bytes\":[32,110,105,103,104,116,115]},{\"token\":\",\",\"logprob\":-0.0036910512,\"bytes\":[44]},{\"token\":\" and\",\"logprob\":-0.0031903093,\"bytes\":[32,97,110,100]},{\"token\":\" I\",\"logprob\":-1.504853e-6,\"bytes\":[32,73]},{\"token\":\" think\",\"logprob\":-4.3202e-7,\"bytes\":[32,116,104,105,110,107]},{\"token\":\" to\",\"logprob\":-1.9361265e-7,\"bytes\":[32,116,111]},{\"token\":\" myself\",\"logprob\":-1.7432603e-6,\"bytes\":[32,109,121,115,101,108,102]},{\"token\":\",\",\"logprob\":-0.29254505,\"bytes\":[44]},{\"token\":\" what\",\"logprob\":-0.016815351,\"bytes\":[32,119,104,97,116]},{\"token\":\" a\",\"logprob\":-3.1281633e-7,\"bytes\":[32,97]},{\"token\":\" wonderful\",\"logprob\":-2.1008714e-6,\"bytes\":[32,119,111,110,100,101,114,102,117,108]},{\"token\":\" world\",\"logprob\":-8.180258e-6,\"bytes\":[32,119,111,114,108,100]},{\"token\":\".\",\"logprob\":-0.014231676,\"bytes\":[46]}],\"usage\":{\"input_tokens\":14,\"input_token_details\":{\"text_tokens\":0,\"audio_tokens\":14},\"output_tokens\":45,\"total_tokens\":59}}\n" + }, + { + "title": "Logprobs", + "request": { + "curl": "curl https://api.openai.com/v1/audio/transcriptions \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: multipart/form-data\" \\\n -F file=\"@/path/to/file/audio.mp3\" \\\n -F \"include[]=logprobs\" \\\n -F model=\"gpt-4o-transcribe\" \\\n -F response_format=\"json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\naudio_file = open(\"speech.mp3\", \"rb\")\ntranscript = client.audio.transcriptions.create(\n file=audio_file,\n model=\"gpt-4o-transcribe\",\n response_format=\"json\",\n include=[\"logprobs\"]\n)\n\nprint(transcript)\n", + "javascript": "import fs from \"fs\";\nimport OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const transcription = await openai.audio.transcriptions.create({\n file: fs.createReadStream(\"audio.mp3\"),\n model: \"gpt-4o-transcribe\",\n response_format: \"json\",\n include: [\"logprobs\"]\n });\n\n console.log(transcription);\n}\nmain();\n" + }, + "response": "{\n \"text\": \"Hey, my knee is hurting and I want to see the doctor tomorrow ideally.\",\n \"logprobs\": [\n { \"token\": \"Hey\", \"logprob\": -1.0415299, \"bytes\": [72, 101, 121] },\n { \"token\": \",\", \"logprob\": -9.805982e-5, \"bytes\": [44] },\n { \"token\": \" my\", \"logprob\": -0.00229799, \"bytes\": [32, 109, 121] },\n {\n \"token\": \" knee\",\n \"logprob\": -4.7159858e-5,\n \"bytes\": [32, 107, 110, 101, 101]\n },\n { \"token\": \" is\", \"logprob\": -0.043909557, \"bytes\": [32, 105, 115] },\n {\n \"token\": \" hurting\",\n \"logprob\": -1.1041146e-5,\n \"bytes\": [32, 104, 117, 114, 116, 105, 110, 103]\n },\n { \"token\": \" and\", \"logprob\": -0.011076359, \"bytes\": [32, 97, 110, 100] },\n { \"token\": \" I\", \"logprob\": -5.3193703e-6, \"bytes\": [32, 73] },\n {\n \"token\": \" want\",\n \"logprob\": -0.0017156356,\n \"bytes\": [32, 119, 97, 110, 116]\n },\n { \"token\": \" to\", \"logprob\": -7.89631e-7, \"bytes\": [32, 116, 111] },\n { \"token\": \" see\", \"logprob\": -5.5122365e-7, \"bytes\": [32, 115, 101, 101] },\n { \"token\": \" the\", \"logprob\": -0.0040786397, \"bytes\": [32, 116, 104, 101] },\n {\n \"token\": \" doctor\",\n \"logprob\": -2.3392786e-6,\n \"bytes\": [32, 100, 111, 99, 116, 111, 114]\n },\n {\n \"token\": \" tomorrow\",\n \"logprob\": -7.89631e-7,\n \"bytes\": [32, 116, 111, 109, 111, 114, 114, 111, 119]\n },\n {\n \"token\": \" ideally\",\n \"logprob\": -0.5800861,\n \"bytes\": [32, 105, 100, 101, 97, 108, 108, 121]\n },\n { \"token\": \".\", \"logprob\": -0.00011093382, \"bytes\": [46] }\n ],\n \"usage\": {\n \"type\": \"tokens\",\n \"input_tokens\": 14,\n \"input_token_details\": {\n \"text_tokens\": 0,\n \"audio_tokens\": 14\n },\n \"output_tokens\": 45,\n \"total_tokens\": 59\n }\n}\n" + }, + { + "title": "Word timestamps", + "request": { + "curl": "curl https://api.openai.com/v1/audio/transcriptions \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: multipart/form-data\" \\\n -F file=\"@/path/to/file/audio.mp3\" \\\n -F \"timestamp_granularities[]=word\" \\\n -F model=\"whisper-1\" \\\n -F response_format=\"verbose_json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\naudio_file = open(\"speech.mp3\", \"rb\")\ntranscript = client.audio.transcriptions.create(\n file=audio_file,\n model=\"whisper-1\",\n response_format=\"verbose_json\",\n timestamp_granularities=[\"word\"]\n)\n\nprint(transcript.words)\n", + "javascript": "import fs from \"fs\";\nimport OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const transcription = await openai.audio.transcriptions.create({\n file: fs.createReadStream(\"audio.mp3\"),\n model: \"whisper-1\",\n response_format: \"verbose_json\",\n timestamp_granularities: [\"word\"]\n });\n\n console.log(transcription.text);\n}\nmain();\n", + "csharp": "using System;\n\nusing OpenAI.Audio;\n\nstring audioFilePath = \"audio.mp3\";\n\nAudioClient client = new(\n model: \"whisper-1\",\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nAudioTranscriptionOptions options = new()\n{\n ResponseFormat = AudioTranscriptionFormat.Verbose,\n TimestampGranularities = AudioTimestampGranularities.Word,\n};\n\nAudioTranscription transcription = client.TranscribeAudio(audioFilePath, options);\n\nConsole.WriteLine($\"{transcription.Text}\");\n" + }, + "response": "{\n \"task\": \"transcribe\",\n \"language\": \"english\",\n \"duration\": 8.470000267028809,\n \"text\": \"The beach was a popular spot on a hot summer day. People were swimming in the ocean, building sandcastles, and playing beach volleyball.\",\n \"words\": [\n {\n \"word\": \"The\",\n \"start\": 0.0,\n \"end\": 0.23999999463558197\n },\n ...\n {\n \"word\": \"volleyball\",\n \"start\": 7.400000095367432,\n \"end\": 7.900000095367432\n }\n ],\n \"usage\": {\n \"type\": \"duration\",\n \"seconds\": 9\n }\n}\n" + }, + { + "title": "Segment timestamps", + "request": { + "curl": "curl https://api.openai.com/v1/audio/transcriptions \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: multipart/form-data\" \\\n -F file=\"@/path/to/file/audio.mp3\" \\\n -F \"timestamp_granularities[]=segment\" \\\n -F model=\"whisper-1\" \\\n -F response_format=\"verbose_json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\naudio_file = open(\"speech.mp3\", \"rb\")\ntranscript = client.audio.transcriptions.create(\n file=audio_file,\n model=\"whisper-1\",\n response_format=\"verbose_json\",\n timestamp_granularities=[\"segment\"]\n)\n\nprint(transcript.words)\n", + "javascript": "import fs from \"fs\";\nimport OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const transcription = await openai.audio.transcriptions.create({\n file: fs.createReadStream(\"audio.mp3\"),\n model: \"whisper-1\",\n response_format: \"verbose_json\",\n timestamp_granularities: [\"segment\"]\n });\n\n console.log(transcription.text);\n}\nmain();\n", + "csharp": "using System;\n\nusing OpenAI.Audio;\n\nstring audioFilePath = \"audio.mp3\";\n\nAudioClient client = new(\n model: \"whisper-1\",\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nAudioTranscriptionOptions options = new()\n{\n ResponseFormat = AudioTranscriptionFormat.Verbose,\n TimestampGranularities = AudioTimestampGranularities.Segment,\n};\n\nAudioTranscription transcription = client.TranscribeAudio(audioFilePath, options);\n\nConsole.WriteLine($\"{transcription.Text}\");\n" + }, + "response": "{\n \"task\": \"transcribe\",\n \"language\": \"english\",\n \"duration\": 8.470000267028809,\n \"text\": \"The beach was a popular spot on a hot summer day. People were swimming in the ocean, building sandcastles, and playing beach volleyball.\",\n \"segments\": [\n {\n \"id\": 0,\n \"seek\": 0,\n \"start\": 0.0,\n \"end\": 3.319999933242798,\n \"text\": \" The beach was a popular spot on a hot summer day.\",\n \"tokens\": [\n 50364, 440, 7534, 390, 257, 3743, 4008, 322, 257, 2368, 4266, 786, 13, 50530\n ],\n \"temperature\": 0.0,\n \"avg_logprob\": -0.2860786020755768,\n \"compression_ratio\": 1.2363636493682861,\n \"no_speech_prob\": 0.00985979475080967\n },\n ...\n ],\n \"usage\": {\n \"type\": \"duration\",\n \"seconds\": 9\n }\n}\n" + } + ] + } + } + }, + "/audio/translations": { + "post": { + "operationId": "createTranslation", + "tags": [ + "Audio" + ], + "summary": "Translates audio into English.", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateTranslationRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateTranslationResponseJson" + }, + { + "$ref": "#/components/schemas/CreateTranslationResponseVerboseJson" + } + ] + } + } + } + }, + "429": { + "$ref": "#/components/responses/InferenceRateLimited" + }, + "503": { + "$ref": "#/components/responses/InferenceServiceUnavailable" + } + }, + "x-oaiMeta": { + "name": "Create translation", + "group": "audio", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/audio/translations \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: multipart/form-data\" \\\n -F file=\"@/path/to/file/german.m4a\" \\\n -F model=\"whisper-1\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\naudio_file = open(\"speech.mp3\", \"rb\")\ntranscript = client.audio.translations.create(\n model=\"whisper-1\",\n file=audio_file\n)\n", + "javascript": "import fs from \"fs\";\nimport OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const translation = await openai.audio.translations.create({\n file: fs.createReadStream(\"speech.mp3\"),\n model: \"whisper-1\",\n });\n\n console.log(translation.text);\n}\nmain();\n", + "csharp": "using System;\n\nusing OpenAI.Audio;\n\nstring audioFilePath = \"audio.mp3\";\n\nAudioClient client = new(\n model: \"whisper-1\",\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nAudioTranscription transcription = client.TranscribeAudio(audioFilePath);\n\nConsole.WriteLine($\"{transcription.Text}\");\n" + }, + "response": "{\n \"text\": \"Hello, my name is Wolfgang and I come from Germany. Where are you heading today?\"\n}\n" + } + } + } + }, + "/audio/voice_consents": { + "post": { + "operationId": "createVoiceConsent", + "tags": [ + "Audio" + ], + "summary": "Upload a voice consent recording.", + "description": "Upload a consent recording that authorizes creation of a custom voice.\n\nSee the [custom voices guide](https://developers.openai.com/api/docs/guides/text-to-speech#custom-voices) for requirements and best practices. Custom voices are limited to eligible customers.\n", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateVoiceConsentRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VoiceConsentResource" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create voice consent", + "group": "audio", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/audio/voice_consents \\\n -X POST \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -F \"name=John Doe\" \\\n -F \"language=en-US\" \\\n -F \"recording=@$HOME/consent_recording.wav;type=audio/x-wav\"\n" + } + } + } + }, + "get": { + "operationId": "listVoiceConsents", + "tags": [ + "Audio" + ], + "summary": "Returns a list of voice consent recordings.", + "description": "List consent recordings available to your organization for creating custom voices.\n\nSee the [custom voices guide](https://developers.openai.com/api/docs/guides/text-to-speech#custom-voices). Custom voices are limited to eligible customers.\n", + "parameters": [ + { + "in": "query", + "name": "after", + "required": false, + "schema": { + "type": "string" + }, + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n" + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VoiceConsentListResource" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List voice consents", + "group": "audio", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/audio/voice_consents?limit=20 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n" + } + } + } + } + }, + "/audio/voice_consents/{consent_id}": { + "get": { + "operationId": "getVoiceConsent", + "tags": [ + "Audio" + ], + "summary": "Retrieves a voice consent recording.", + "description": "Retrieve consent recording metadata used for creating custom voices.\n\nSee the [custom voices guide](https://developers.openai.com/api/docs/guides/text-to-speech#custom-voices). Custom voices are limited to eligible customers.\n", + "parameters": [ + { + "in": "path", + "name": "consent_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the consent recording to retrieve." + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VoiceConsentResource" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve voice consent", + "group": "audio", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/audio/voice_consents/cons_1234 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n" + } + } + } + }, + "post": { + "operationId": "updateVoiceConsent", + "tags": [ + "Audio" + ], + "summary": "Updates a voice consent recording (metadata only).", + "description": "Update consent recording metadata used for creating custom voices. This endpoint updates metadata only and does not replace the underlying audio.\n\nSee the [custom voices guide](https://developers.openai.com/api/docs/guides/text-to-speech#custom-voices). Custom voices are limited to eligible customers.\n", + "parameters": [ + { + "in": "path", + "name": "consent_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the consent recording to update." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateVoiceConsentRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VoiceConsentResource" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Update voice consent", + "group": "audio", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/audio/voice_consents/cons_1234 \\\n -X POST \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"John Doe\"\n }'\n" + } + } + } + }, + "delete": { + "operationId": "deleteVoiceConsent", + "tags": [ + "Audio" + ], + "summary": "Deletes a voice consent recording.", + "description": "Delete a consent recording that was uploaded for creating custom voices.\n\nSee the [custom voices guide](https://developers.openai.com/api/docs/guides/text-to-speech#custom-voices). Custom voices are limited to eligible customers.\n", + "parameters": [ + { + "in": "path", + "name": "consent_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the consent recording to delete." + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VoiceConsentDeletedResource" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete voice consent", + "group": "audio", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/audio/voice_consents/cons_1234 \\\n -X DELETE \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n" + } + } + } + } + }, + "/audio/voices": { + "post": { + "operationId": "createVoice", + "tags": [ + "Audio" + ], + "summary": "Creates a custom voice.", + "description": "Create a custom voice you can use for audio output (for example, in Text-to-Speech and the Realtime API). This requires an audio sample and a previously uploaded consent recording.\n\nSee the [custom voices guide](https://developers.openai.com/api/docs/guides/text-to-speech#custom-voices) for requirements and best practices. Custom voices are limited to eligible customers.\n", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateVoiceRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VoiceResource" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create voice", + "group": "audio", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/audio/voices \\\n -X POST \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -F \"name=My new voice\" \\\n -F \"consent=cons_1234\" \\\n -F \"audio_sample=@$HOME/audio_sample.wav;type=audio/x-wav\"\n" + } + } + } + } + }, + "/batches": { + "post": { + "summary": "Creates and executes a batch from an uploaded file of requests", + "operationId": "createBatch", + "tags": [ + "Batch" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateBatchRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Batch created successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Batch" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create batch", + "group": "batch", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/batches \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"input_file_id\": \"file-abc123\",\n \"endpoint\": \"/v1/chat/completions\",\n \"completion_window\": \"24h\"\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.batches.create(\n input_file_id=\"file-abc123\",\n endpoint=\"/v1/chat/completions\",\n completion_window=\"24h\"\n)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const batch = await openai.batches.create({\n input_file_id: \"file-abc123\",\n endpoint: \"/v1/chat/completions\",\n completion_window: \"24h\"\n });\n\n console.log(batch);\n}\n\nmain();\n" + }, + "response": "{\n \"id\": \"batch_abc123\",\n \"object\": \"batch\",\n \"endpoint\": \"/v1/chat/completions\",\n \"errors\": null,\n \"input_file_id\": \"file-abc123\",\n \"completion_window\": \"24h\",\n \"status\": \"validating\",\n \"output_file_id\": null,\n \"error_file_id\": null,\n \"created_at\": 1711471533,\n \"in_progress_at\": null,\n \"expires_at\": null,\n \"finalizing_at\": null,\n \"completed_at\": null,\n \"failed_at\": null,\n \"expired_at\": null,\n \"cancelling_at\": null,\n \"cancelled_at\": null,\n \"request_counts\": {\n \"total\": 0,\n \"completed\": 0,\n \"failed\": 0\n },\n \"metadata\": {\n \"customer_id\": \"user_123456789\",\n \"batch_description\": \"Nightly eval job\",\n }\n}\n" + } + } + }, + "get": { + "operationId": "listBatches", + "tags": [ + "Batch" + ], + "summary": "List your organization's batches.", + "parameters": [ + { + "in": "query", + "name": "after", + "required": false, + "schema": { + "type": "string" + }, + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n" + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + } + ], + "responses": { + "200": { + "description": "Batch listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListBatchesResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List batches", + "group": "batch", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/batches?limit=2 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.batches.list()\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const list = await openai.batches.list();\n\n for await (const batch of list) {\n console.log(batch);\n }\n}\n\nmain();\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"batch_abc123\",\n \"object\": \"batch\",\n \"endpoint\": \"/v1/chat/completions\",\n \"errors\": null,\n \"input_file_id\": \"file-abc123\",\n \"completion_window\": \"24h\",\n \"status\": \"completed\",\n \"output_file_id\": \"file-cvaTdG\",\n \"error_file_id\": \"file-HOWS94\",\n \"created_at\": 1711471533,\n \"in_progress_at\": 1711471538,\n \"expires_at\": 1711557933,\n \"finalizing_at\": 1711493133,\n \"completed_at\": 1711493163,\n \"failed_at\": null,\n \"expired_at\": null,\n \"cancelling_at\": null,\n \"cancelled_at\": null,\n \"request_counts\": {\n \"total\": 100,\n \"completed\": 95,\n \"failed\": 5\n },\n \"metadata\": {\n \"customer_id\": \"user_123456789\",\n \"batch_description\": \"Nightly job\",\n }\n },\n { ... },\n ],\n \"first_id\": \"batch_abc123\",\n \"last_id\": \"batch_abc456\",\n \"has_more\": true\n}\n" + } + } + } + }, + "/batches/{batch_id}": { + "get": { + "operationId": "retrieveBatch", + "tags": [ + "Batch" + ], + "summary": "Retrieves a batch.", + "parameters": [ + { + "in": "path", + "name": "batch_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the batch to retrieve." + } + ], + "responses": { + "200": { + "description": "Batch retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Batch" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve batch", + "group": "batch", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/batches/batch_abc123 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.batches.retrieve(\"batch_abc123\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const batch = await openai.batches.retrieve(\"batch_abc123\");\n\n console.log(batch);\n}\n\nmain();\n" + }, + "response": "{\n \"id\": \"batch_abc123\",\n \"object\": \"batch\",\n \"endpoint\": \"/v1/completions\",\n \"errors\": null,\n \"input_file_id\": \"file-abc123\",\n \"completion_window\": \"24h\",\n \"status\": \"completed\",\n \"output_file_id\": \"file-cvaTdG\",\n \"error_file_id\": \"file-HOWS94\",\n \"created_at\": 1711471533,\n \"in_progress_at\": 1711471538,\n \"expires_at\": 1711557933,\n \"finalizing_at\": 1711493133,\n \"completed_at\": 1711493163,\n \"failed_at\": null,\n \"expired_at\": null,\n \"cancelling_at\": null,\n \"cancelled_at\": null,\n \"request_counts\": {\n \"total\": 100,\n \"completed\": 95,\n \"failed\": 5\n },\n \"metadata\": {\n \"customer_id\": \"user_123456789\",\n \"batch_description\": \"Nightly eval job\",\n }\n}\n" + } + } + } + }, + "/batches/{batch_id}/cancel": { + "post": { + "operationId": "cancelBatch", + "tags": [ + "Batch" + ], + "summary": "Cancels an in-progress batch. The batch will be in status `cancelling` for up to 10 minutes, before changing to `cancelled`, where it will have partial results (if any) available in the output file.", + "parameters": [ + { + "in": "path", + "name": "batch_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the batch to cancel." + } + ], + "responses": { + "200": { + "description": "Batch is cancelling. Returns the cancelling batch's details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Batch" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Cancel batch", + "group": "batch", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/batches/batch_abc123/cancel \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -X POST\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.batches.cancel(\"batch_abc123\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const batch = await openai.batches.cancel(\"batch_abc123\");\n\n console.log(batch);\n}\n\nmain();\n" + }, + "response": "{\n \"id\": \"batch_abc123\",\n \"object\": \"batch\",\n \"endpoint\": \"/v1/chat/completions\",\n \"errors\": null,\n \"input_file_id\": \"file-abc123\",\n \"completion_window\": \"24h\",\n \"status\": \"cancelling\",\n \"output_file_id\": null,\n \"error_file_id\": null,\n \"created_at\": 1711471533,\n \"in_progress_at\": 1711471538,\n \"expires_at\": 1711557933,\n \"finalizing_at\": null,\n \"completed_at\": null,\n \"failed_at\": null,\n \"expired_at\": null,\n \"cancelling_at\": 1711475133,\n \"cancelled_at\": null,\n \"request_counts\": {\n \"total\": 100,\n \"completed\": 23,\n \"failed\": 1\n },\n \"metadata\": {\n \"customer_id\": \"user_123456789\",\n \"batch_description\": \"Nightly eval job\",\n }\n}\n" + } + } + } + }, + "/chat/completions": { + "get": { + "operationId": "listChatCompletions", + "tags": [ + "Chat" + ], + "summary": "List stored Chat Completions. Only Chat Completions that have been stored\nwith the `store` parameter set to `true` will be returned.\n", + "parameters": [ + { + "name": "model", + "in": "query", + "description": "The model used to generate the Chat Completions.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "metadata", + "in": "query", + "description": "A list of metadata keys to filter the Chat Completions by. Example:\n\n`metadata[key1]=value1&metadata[key2]=value2`\n", + "required": false, + "schema": { + "$ref": "#/components/schemas/Metadata" + } + }, + { + "name": "after", + "in": "query", + "description": "Identifier for the last chat completion from the previous pagination request.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "Number of Chat Completions to retrieve.", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for Chat Completions by timestamp. Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + } + ], + "responses": { + "200": { + "description": "A list of Chat Completions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatCompletionList" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List Chat Completions", + "group": "chat", + "path": "list", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/chat/completions \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ncompletions = client.chat.completions.list()\nprint(completions)\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"chat.completion\",\n \"id\": \"chatcmpl-AyPNinnUqUDYo9SAdA52NobMflmj2\",\n \"model\": \"gpt-6-astra\",\n \"created\": 1738960610,\n \"request_id\": \"req_ded8ab984ec4bf840f37566c1011c417\",\n \"tool_choice\": null,\n \"usage\": {\n \"total_tokens\": 31,\n \"completion_tokens\": 18,\n \"prompt_tokens\": 13\n },\n \"seed\": 4944116822809979520,\n \"top_p\": 1.0,\n \"temperature\": 1.0,\n \"presence_penalty\": 0.0,\n \"frequency_penalty\": 0.0,\n \"system_fingerprint\": \"fp_50cad350e4\",\n \"input_user\": null,\n \"service_tier\": \"default\",\n \"tools\": null,\n \"metadata\": {},\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"content\": \"Mind of circuits hum, \\nLearning patterns in silence— \\nFuture's quiet spark.\",\n \"role\": \"assistant\",\n \"tool_calls\": null,\n \"function_call\": null\n },\n \"finish_reason\": \"stop\",\n \"logprobs\": null\n }\n ],\n \"response_format\": null\n }\n ],\n \"first_id\": \"chatcmpl-AyPNinnUqUDYo9SAdA52NobMflmj2\",\n \"last_id\": \"chatcmpl-AyPNinnUqUDYo9SAdA52NobMflmj2\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "operationId": "createChatCompletion", + "tags": [ + "Chat" + ], + "summary": "**Starting a new project?** We recommend trying [Responses](https://developers.openai.com/api/reference/resources/responses)\nto take advantage of the latest OpenAI platform features. Compare\n[Chat Completions with Responses](https://developers.openai.com/api/docs/guides/migrate-to-responses?api-mode=responses).\n\n---\n\nCreates a model response for the given chat conversation. Learn more in the\n[text generation](https://developers.openai.com/api/docs/guides/text), [vision](https://developers.openai.com/api/docs/guides/images-vision),\nand [audio](https://developers.openai.com/api/docs/guides/audio) guides.\n\nParameter support can differ depending on the model used to generate the\nresponse, particularly for newer reasoning models. Parameters that are only\nsupported for reasoning models are noted below. For the current state of\nunsupported parameters in reasoning models,\n[refer to the reasoning guide](https://developers.openai.com/api/docs/guides/reasoning).\n\nReturns a chat completion object, or a streamed sequence of chat completion\nchunk objects if the request is streamed.\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateChatCompletionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateChatCompletionResponse" + } + }, + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/CreateChatCompletionStreamResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/InferenceRateLimited" + }, + "503": { + "$ref": "#/components/responses/InferenceServiceUnavailable" + } + }, + "x-oaiMeta": { + "name": "Create chat completion", + "group": "chat", + "path": "create", + "examples": [ + { + "title": "Default", + "request": { + "curl": "curl https://api.openai.com/v1/chat/completions \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"model\": \"gpt-6-astra\",\n \"messages\": [\n {\n \"role\": \"developer\",\n \"content\": \"You are a helpful assistant.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"Hello!\"\n }\n ]\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ncompletion = client.chat.completions.create(\n model=\"gpt-6-astra\",\n messages=[\n {\"role\": \"developer\", \"content\": \"You are a helpful assistant.\"},\n {\"role\": \"user\", \"content\": \"Hello!\"}\n ]\n)\n\nprint(completion.choices[0].message)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const completion = await openai.chat.completions.create({\n messages: [{ role: \"developer\", content: \"You are a helpful assistant.\" }],\n model: \"gpt-6-astra\",\n store: true,\n });\n\n console.log(completion.choices[0]);\n}\n\nmain();\n", + "csharp": "using System;\nusing System.Collections.Generic;\n\nusing OpenAI.Chat;\n\nChatClient client = new(\n model: \"gpt-6-astra\",\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nList messages =\n[\n new SystemChatMessage(\"You are a helpful assistant.\"),\n new UserChatMessage(\"Hello!\")\n];\n\nChatCompletion completion = client.CompleteChat(messages);\n\nConsole.WriteLine(completion.Content[0].Text);\n" + }, + "response": "{\n \"id\": \"chatcmpl-B9MBs8CjcvOU2jLn4n570S5qMJKcT\",\n \"object\": \"chat.completion\",\n \"created\": 1741569952,\n \"model\": \"gpt-6-astra\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Hello! How can I assist you today?\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 19,\n \"completion_tokens\": 10,\n \"total_tokens\": 29,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\"\n}\n" + }, + { + "title": "Image input", + "request": { + "curl": "curl https://api.openai.com/v1/chat/completions \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"model\": \"gpt-6-astra\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"type\": \"text\",\n \"text\": \"What is in this image?\"\n },\n {\n \"type\": \"image_url\",\n \"image_url\": {\n \"url\": \"https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg\"\n }\n }\n ]\n }\n ],\n \"max_tokens\": 300\n }'\n", + "python": "from openai import OpenAI\n\nclient = OpenAI()\n\nresponse = client.chat.completions.create(\n model=\"gpt-6-astra\",\n messages=[\n {\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"text\", \"text\": \"What's in this image?\"},\n {\n \"type\": \"image_url\",\n \"image_url\": {\n \"url\": \"https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg\",\n }\n },\n ],\n }\n ],\n max_tokens=300,\n)\n\nprint(response.choices[0])\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const response = await openai.chat.completions.create({\n model: \"gpt-6-astra\",\n messages: [\n {\n role: \"user\",\n content: [\n { type: \"text\", text: \"What's in this image?\" },\n {\n type: \"image_url\",\n image_url: {\n \"url\": \"https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg\",\n },\n }\n ],\n },\n ],\n });\n console.log(response.choices[0]);\n}\nmain();\n", + "csharp": "using System;\nusing System.Collections.Generic;\n\nusing OpenAI.Chat;\n\nChatClient client = new(\n model: \"gpt-6-astra\",\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nList messages =\n[\n new UserChatMessage(\n [\n ChatMessageContentPart.CreateTextPart(\"What's in this image?\"),\n ChatMessageContentPart.CreateImagePart(new Uri(\"https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg\"))\n ])\n];\n\nChatCompletion completion = client.CompleteChat(messages);\n\nConsole.WriteLine(completion.Content[0].Text);\n" + }, + "response": "{\n \"id\": \"chatcmpl-B9MHDbslfkBeAs8l4bebGdFOJ6PeG\",\n \"object\": \"chat.completion\",\n \"created\": 1741570283,\n \"model\": \"gpt-6-astra\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"The image shows a wooden boardwalk path running through a lush green field or meadow. The sky is bright blue with some scattered clouds, giving the scene a serene and peaceful atmosphere. Trees and shrubs are visible in the background.\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 1117,\n \"completion_tokens\": 46,\n \"total_tokens\": 1163,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\"\n}\n" + }, + { + "title": "Streaming", + "request": { + "curl": "curl https://api.openai.com/v1/chat/completions \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"model\": \"gpt-6-astra\",\n \"messages\": [\n {\n \"role\": \"developer\",\n \"content\": \"You are a helpful assistant.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"Hello!\"\n }\n ],\n \"stream\": true\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ncompletion = client.chat.completions.create(\n model=\"gpt-6-astra\",\n messages=[\n {\"role\": \"developer\", \"content\": \"You are a helpful assistant.\"},\n {\"role\": \"user\", \"content\": \"Hello!\"}\n ],\n stream=True\n)\n\nfor chunk in completion:\n print(chunk.choices[0].delta)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const completion = await openai.chat.completions.create({\n model: \"gpt-6-astra\",\n messages: [\n {\"role\": \"developer\", \"content\": \"You are a helpful assistant.\"},\n {\"role\": \"user\", \"content\": \"Hello!\"}\n ],\n stream: true,\n });\n\n for await (const chunk of completion) {\n console.log(chunk.choices[0].delta.content);\n }\n}\n\nmain();\n", + "csharp": "using System;\nusing System.ClientModel;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\n\nusing OpenAI.Chat;\n\nChatClient client = new(\n model: \"gpt-6-astra\",\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nList messages =\n[\n new SystemChatMessage(\"You are a helpful assistant.\"),\n new UserChatMessage(\"Hello!\")\n];\n\nAsyncCollectionResult completionUpdates = client.CompleteChatStreamingAsync(messages);\n\nawait foreach (StreamingChatCompletionUpdate completionUpdate in completionUpdates)\n{\n if (completionUpdate.ContentUpdate.Count > 0)\n {\n Console.Write(completionUpdate.ContentUpdate[0].Text);\n }\n}\n" + }, + "response": "{\"id\":\"chatcmpl-123\",\"object\":\"chat.completion.chunk\",\"created\":1694268190,\"model\":\"gpt-6-astra\", \"system_fingerprint\": \"fp_44709d6fcb\", \"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\"},\"logprobs\":null,\"finish_reason\":null}]}\n\n{\"id\":\"chatcmpl-123\",\"object\":\"chat.completion.chunk\",\"created\":1694268190,\"model\":\"gpt-6-astra\", \"system_fingerprint\": \"fp_44709d6fcb\", \"choices\":[{\"index\":0,\"delta\":{\"content\":\"Hello\"},\"logprobs\":null,\"finish_reason\":null}]}\n\n....\n\n{\"id\":\"chatcmpl-123\",\"object\":\"chat.completion.chunk\",\"created\":1694268190,\"model\":\"gpt-6-astra\", \"system_fingerprint\": \"fp_44709d6fcb\", \"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}]}\n" + }, + { + "title": "Functions", + "request": { + "curl": "curl https://api.openai.com/v1/chat/completions \\\n-H \"Content-Type: application/json\" \\\n-H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n-d '{\n \"model\": \"gpt-6-astra\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"What is the weather like in Boston today?\"\n }\n ],\n \"tools\": [\n {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"get_current_weather\",\n \"description\": \"Get the current weather in a given location\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"location\": {\n \"type\": \"string\",\n \"description\": \"The city and state, e.g. San Francisco, CA\"\n },\n \"unit\": {\n \"type\": \"string\",\n \"enum\": [\"celsius\", \"fahrenheit\"]\n }\n },\n \"required\": [\"location\"]\n }\n }\n }\n ],\n \"tool_choice\": \"auto\"\n}'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ntools = [\n {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"get_current_weather\",\n \"description\": \"Get the current weather in a given location\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"location\": {\n \"type\": \"string\",\n \"description\": \"The city and state, e.g. San Francisco, CA\",\n },\n \"unit\": {\"type\": \"string\", \"enum\": [\"celsius\", \"fahrenheit\"]},\n },\n \"required\": [\"location\"],\n },\n }\n }\n]\nmessages = [{\"role\": \"user\", \"content\": \"What's the weather like in Boston today?\"}]\ncompletion = client.chat.completions.create(\n model=\"gpt-6-astra\",\n messages=messages,\n tools=tools,\n tool_choice=\"auto\"\n)\n\nprint(completion)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const messages = [{\"role\": \"user\", \"content\": \"What's the weather like in Boston today?\"}];\n const tools = [\n {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"get_current_weather\",\n \"description\": \"Get the current weather in a given location\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"location\": {\n \"type\": \"string\",\n \"description\": \"The city and state, e.g. San Francisco, CA\",\n },\n \"unit\": {\"type\": \"string\", \"enum\": [\"celsius\", \"fahrenheit\"]},\n },\n \"required\": [\"location\"],\n },\n }\n }\n ];\n\n const response = await openai.chat.completions.create({\n model: \"gpt-6-astra\",\n messages: messages,\n tools: tools,\n tool_choice: \"auto\",\n });\n\n console.log(response);\n}\n\nmain();\n", + "csharp": "using System;\nusing System.Collections.Generic;\n\nusing OpenAI.Chat;\n\nChatClient client = new(\n model: \"gpt-6-astra\",\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nChatTool getCurrentWeatherTool = ChatTool.CreateFunctionTool(\n functionName: \"get_current_weather\",\n functionDescription: \"Get the current weather in a given location\",\n functionParameters: BinaryData.FromString(\"\"\"\n {\n \"type\": \"object\",\n \"properties\": {\n \"location\": {\n \"type\": \"string\",\n \"description\": \"The city and state, e.g. San Francisco, CA\"\n },\n \"unit\": {\n \"type\": \"string\",\n \"enum\": [ \"celsius\", \"fahrenheit\" ]\n }\n },\n \"required\": [ \"location\" ]\n }\n \"\"\")\n);\n\nList messages =\n[\n new UserChatMessage(\"What's the weather like in Boston today?\"),\n];\n\nChatCompletionOptions options = new()\n{\n Tools =\n {\n getCurrentWeatherTool\n },\n ToolChoice = ChatToolChoice.CreateAutoChoice(),\n};\n\nChatCompletion completion = client.CompleteChat(messages, options);\n" + }, + "response": "{\n \"id\": \"chatcmpl-abc123\",\n \"object\": \"chat.completion\",\n \"created\": 1699896916,\n \"model\": \"gpt-6-astra\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n \"id\": \"call_abc123\",\n \"type\": \"function\",\n \"function\": {\n \"name\": \"get_current_weather\",\n \"arguments\": \"{\\n\\\"location\\\": \\\"Boston, MA\\\"\\n}\"\n }\n }\n ]\n },\n \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 82,\n \"completion_tokens\": 17,\n \"total_tokens\": 99,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n }\n}\n" + }, + { + "title": "Logprobs", + "request": { + "curl": "curl https://api.openai.com/v1/chat/completions \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"model\": \"gpt-6-astra\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Hello!\"\n }\n ],\n \"reasoning_effort\": \"none\",\n \"logprobs\": true,\n \"top_logprobs\": 2\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ncompletion = client.chat.completions.create(\n model=\"gpt-6-astra\",\n messages=[\n {\"role\": \"user\", \"content\": \"Hello!\"}\n ],\n reasoning_effort=\"none\",\n logprobs=True,\n top_logprobs=2\n)\n\nprint(completion.choices[0].message)\nprint(completion.choices[0].logprobs)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const completion = await openai.chat.completions.create({\n messages: [{ role: \"user\", content: \"Hello!\" }],\n model: \"gpt-6-astra\",\n reasoning_effort: \"none\",\n logprobs: true,\n top_logprobs: 2,\n });\n\n console.log(completion.choices[0]);\n}\n\nmain();\n", + "csharp": "using System;\nusing System.Collections.Generic;\n\nusing OpenAI.Chat;\n\nChatClient client = new(\n model: \"gpt-6-astra\",\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nList messages =\n[\n new UserChatMessage(\"Hello!\")\n];\n\nChatCompletionOptions options = new()\n{\n ReasoningEffortLevel = ChatReasoningEffortLevel.None,\n IncludeLogProbabilities = true,\n TopLogProbabilityCount = 2\n};\n\nChatCompletion completion = client.CompleteChat(messages, options);\n\nConsole.WriteLine(completion.Content[0].Text);\n" + }, + "response": "{\n \"id\": \"chatcmpl-123\",\n \"object\": \"chat.completion\",\n \"created\": 1702685778,\n \"model\": \"gpt-6-astra\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Hello! How can I assist you today?\"\n },\n \"logprobs\": {\n \"content\": [\n {\n \"token\": \"Hello\",\n \"logprob\": -0.31725305,\n \"bytes\": [72, 101, 108, 108, 111],\n \"top_logprobs\": [\n {\n \"token\": \"Hello\",\n \"logprob\": -0.31725305,\n \"bytes\": [72, 101, 108, 108, 111]\n },\n {\n \"token\": \"Hi\",\n \"logprob\": -1.3190403,\n \"bytes\": [72, 105]\n }\n ]\n },\n {\n \"token\": \"!\",\n \"logprob\": -0.02380986,\n \"bytes\": [\n 33\n ],\n \"top_logprobs\": [\n {\n \"token\": \"!\",\n \"logprob\": -0.02380986,\n \"bytes\": [33]\n },\n {\n \"token\": \" there\",\n \"logprob\": -3.787621,\n \"bytes\": [32, 116, 104, 101, 114, 101]\n }\n ]\n },\n {\n \"token\": \" How\",\n \"logprob\": -0.000054669687,\n \"bytes\": [32, 72, 111, 119],\n \"top_logprobs\": [\n {\n \"token\": \" How\",\n \"logprob\": -0.000054669687,\n \"bytes\": [32, 72, 111, 119]\n },\n {\n \"token\": \"<|end|>\",\n \"logprob\": -10.953937,\n \"bytes\": null\n }\n ]\n },\n {\n \"token\": \" can\",\n \"logprob\": -0.015801601,\n \"bytes\": [32, 99, 97, 110],\n \"top_logprobs\": [\n {\n \"token\": \" can\",\n \"logprob\": -0.015801601,\n \"bytes\": [32, 99, 97, 110]\n },\n {\n \"token\": \" may\",\n \"logprob\": -4.161023,\n \"bytes\": [32, 109, 97, 121]\n }\n ]\n },\n {\n \"token\": \" I\",\n \"logprob\": -3.7697225e-6,\n \"bytes\": [\n 32,\n 73\n ],\n \"top_logprobs\": [\n {\n \"token\": \" I\",\n \"logprob\": -3.7697225e-6,\n \"bytes\": [32, 73]\n },\n {\n \"token\": \" assist\",\n \"logprob\": -13.596657,\n \"bytes\": [32, 97, 115, 115, 105, 115, 116]\n }\n ]\n },\n {\n \"token\": \" assist\",\n \"logprob\": -0.04571125,\n \"bytes\": [32, 97, 115, 115, 105, 115, 116],\n \"top_logprobs\": [\n {\n \"token\": \" assist\",\n \"logprob\": -0.04571125,\n \"bytes\": [32, 97, 115, 115, 105, 115, 116]\n },\n {\n \"token\": \" help\",\n \"logprob\": -3.1089056,\n \"bytes\": [32, 104, 101, 108, 112]\n }\n ]\n },\n {\n \"token\": \" you\",\n \"logprob\": -5.4385737e-6,\n \"bytes\": [32, 121, 111, 117],\n \"top_logprobs\": [\n {\n \"token\": \" you\",\n \"logprob\": -5.4385737e-6,\n \"bytes\": [32, 121, 111, 117]\n },\n {\n \"token\": \" today\",\n \"logprob\": -12.807695,\n \"bytes\": [32, 116, 111, 100, 97, 121]\n }\n ]\n },\n {\n \"token\": \" today\",\n \"logprob\": -0.0040071653,\n \"bytes\": [32, 116, 111, 100, 97, 121],\n \"top_logprobs\": [\n {\n \"token\": \" today\",\n \"logprob\": -0.0040071653,\n \"bytes\": [32, 116, 111, 100, 97, 121]\n },\n {\n \"token\": \"?\",\n \"logprob\": -5.5247097,\n \"bytes\": [63]\n }\n ]\n },\n {\n \"token\": \"?\",\n \"logprob\": -0.0008108172,\n \"bytes\": [63],\n \"top_logprobs\": [\n {\n \"token\": \"?\",\n \"logprob\": -0.0008108172,\n \"bytes\": [63]\n },\n {\n \"token\": \"?\\n\",\n \"logprob\": -7.184561,\n \"bytes\": [63, 10]\n }\n ]\n }\n ]\n },\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 9,\n \"completion_tokens\": 9,\n \"total_tokens\": 18,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\": null\n}\n" + } + ] + } + } + }, + "/chat/completions/{completion_id}": { + "get": { + "operationId": "getChatCompletion", + "tags": [ + "Chat" + ], + "summary": "Get a stored chat completion. Only Chat Completions that have been created\nwith the `store` parameter set to `true` will be returned.\n", + "parameters": [ + { + "in": "path", + "name": "completion_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the chat completion to retrieve." + } + ], + "responses": { + "200": { + "description": "A chat completion", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateChatCompletionResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Get chat completion", + "group": "chat", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/chat/completions/chatcmpl-abc123 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ncompletions = client.chat.completions.list()\nfirst_id = completions[0].id\nfirst_completion = client.chat.completions.retrieve(completion_id=first_id)\nprint(first_completion)\n" + }, + "response": "{\n \"object\": \"chat.completion\",\n \"id\": \"chatcmpl-abc123\",\n \"model\": \"gpt-6-astra\",\n \"created\": 1738960610,\n \"request_id\": \"req_ded8ab984ec4bf840f37566c1011c417\",\n \"tool_choice\": null,\n \"usage\": {\n \"total_tokens\": 31,\n \"completion_tokens\": 18,\n \"prompt_tokens\": 13\n },\n \"seed\": 4944116822809979520,\n \"top_p\": 1.0,\n \"temperature\": 1.0,\n \"presence_penalty\": 0.0,\n \"frequency_penalty\": 0.0,\n \"system_fingerprint\": \"fp_50cad350e4\",\n \"input_user\": null,\n \"service_tier\": \"default\",\n \"tools\": null,\n \"metadata\": {},\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"content\": \"Mind of circuits hum, \\nLearning patterns in silence— \\nFuture's quiet spark.\",\n \"role\": \"assistant\",\n \"tool_calls\": null,\n \"function_call\": null\n },\n \"finish_reason\": \"stop\",\n \"logprobs\": null\n }\n ],\n \"response_format\": null\n}\n" + } + } + }, + "post": { + "operationId": "updateChatCompletion", + "tags": [ + "Chat" + ], + "summary": "Modify a stored chat completion. Only Chat Completions that have been\ncreated with the `store` parameter set to `true` can be modified. Currently,\nthe only supported modification is to update the `metadata` field.\n", + "parameters": [ + { + "in": "path", + "name": "completion_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the chat completion to update." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "metadata" + ], + "properties": { + "metadata": { + "$ref": "#/components/schemas/Metadata" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "A chat completion", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateChatCompletionResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Update chat completion", + "group": "chat", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/chat/completions/chat_abc123 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"metadata\": {\"foo\": \"bar\"}}'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ncompletions = client.chat.completions.list()\nfirst_id = completions[0].id\nupdated_completion = client.chat.completions.update(completion_id=first_id, request_body={\"metadata\": {\"foo\": \"bar\"}})\nprint(updated_completion)\n" + }, + "response": "{\n \"object\": \"chat.completion\",\n \"id\": \"chatcmpl-AyPNinnUqUDYo9SAdA52NobMflmj2\",\n \"model\": \"gpt-6-astra\",\n \"created\": 1738960610,\n \"request_id\": \"req_ded8ab984ec4bf840f37566c1011c417\",\n \"tool_choice\": null,\n \"usage\": {\n \"total_tokens\": 31,\n \"completion_tokens\": 18,\n \"prompt_tokens\": 13\n },\n \"seed\": 4944116822809979520,\n \"top_p\": 1.0,\n \"temperature\": 1.0,\n \"presence_penalty\": 0.0,\n \"frequency_penalty\": 0.0,\n \"system_fingerprint\": \"fp_50cad350e4\",\n \"input_user\": null,\n \"service_tier\": \"default\",\n \"tools\": null,\n \"metadata\": {\n \"foo\": \"bar\"\n },\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"content\": \"Mind of circuits hum, \\nLearning patterns in silence— \\nFuture's quiet spark.\",\n \"role\": \"assistant\",\n \"tool_calls\": null,\n \"function_call\": null\n },\n \"finish_reason\": \"stop\",\n \"logprobs\": null\n }\n ],\n \"response_format\": null\n}\n" + } + } + }, + "delete": { + "operationId": "deleteChatCompletion", + "tags": [ + "Chat" + ], + "summary": "Delete a stored chat completion. Only Chat Completions that have been\ncreated with the `store` parameter set to `true` can be deleted.\n", + "parameters": [ + { + "in": "path", + "name": "completion_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the chat completion to delete." + } + ], + "responses": { + "200": { + "description": "The chat completion was deleted successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatCompletionDeleted" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete chat completion", + "group": "chat", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/chat/completions/chat_abc123 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ncompletions = client.chat.completions.list()\nfirst_id = completions[0].id\ndelete_response = client.chat.completions.delete(completion_id=first_id)\nprint(delete_response)\n" + }, + "response": "{\n \"object\": \"chat.completion.deleted\",\n \"id\": \"chatcmpl-AyPNinnUqUDYo9SAdA52NobMflmj2\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/chat/completions/{completion_id}/messages": { + "get": { + "operationId": "getChatCompletionMessages", + "tags": [ + "Chat" + ], + "summary": "Get the messages in a stored chat completion. Only Chat Completions that\nhave been created with the `store` parameter set to `true` will be\nreturned.\n", + "parameters": [ + { + "in": "path", + "name": "completion_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the chat completion to retrieve messages from." + }, + { + "name": "after", + "in": "query", + "description": "Identifier for the last message from the previous pagination request.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "Number of messages to retrieve.", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for messages by timestamp. Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + } + ], + "responses": { + "200": { + "description": "A list of messages", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatCompletionMessageList" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Get chat messages", + "group": "chat", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/chat/completions/chat_abc123/messages \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ncompletions = client.chat.completions.list()\nfirst_id = completions[0].id\nfirst_completion = client.chat.completions.retrieve(completion_id=first_id)\nmessages = client.chat.completions.messages.list(completion_id=first_id)\nprint(messages)\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"chatcmpl-AyPNinnUqUDYo9SAdA52NobMflmj2-0\",\n \"role\": \"user\",\n \"content\": \"write a haiku about ai\",\n \"name\": null,\n \"content_parts\": null\n }\n ],\n \"first_id\": \"chatcmpl-AyPNinnUqUDYo9SAdA52NobMflmj2-0\",\n \"last_id\": \"chatcmpl-AyPNinnUqUDYo9SAdA52NobMflmj2-0\",\n \"has_more\": false\n}\n" + } + } + } + }, + "/completions": { + "post": { + "operationId": "createCompletion", + "tags": [ + "Completions" + ], + "summary": "Creates a completion for the provided prompt and parameters.\n\nReturns a completion object, or a sequence of completion objects if the request is streamed.\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCompletionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCompletionResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/InferenceRateLimited" + }, + "503": { + "$ref": "#/components/responses/InferenceServiceUnavailable" + } + }, + "x-oaiMeta": { + "name": "Create completion", + "group": "completions", + "legacy": true, + "examples": [ + { + "title": "No streaming", + "request": { + "curl": "curl https://api.openai.com/v1/completions \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"model\": \"gpt-3.5-turbo-instruct\",\n \"prompt\": \"Say this is a test\",\n \"max_tokens\": 7,\n \"temperature\": 0\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.completions.create(\n model=\"gpt-3.5-turbo-instruct\",\n prompt=\"Say this is a test\",\n max_tokens=7,\n temperature=0\n)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const completion = await openai.completions.create({\n model: \"gpt-3.5-turbo-instruct\",\n prompt: \"Say this is a test.\",\n max_tokens: 7,\n temperature: 0,\n });\n\n console.log(completion);\n}\nmain();" + }, + "response": "{\n \"id\": \"cmpl-uqkvlQyYK7bGYrRHQ0eXlWi7\",\n \"object\": \"text_completion\",\n \"created\": 1589478378,\n \"model\": \"gpt-3.5-turbo-instruct\",\n \"system_fingerprint\": \"fp_44709d6fcb\",\n \"choices\": [\n {\n \"text\": \"\\n\\nThis is indeed a test\",\n \"index\": 0,\n \"logprobs\": null,\n \"finish_reason\": \"length\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 5,\n \"completion_tokens\": 7,\n \"total_tokens\": 12\n }\n}\n" + }, + { + "title": "Streaming", + "request": { + "curl": "curl https://api.openai.com/v1/completions \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"model\": \"gpt-3.5-turbo-instruct\",\n \"prompt\": \"Say this is a test\",\n \"max_tokens\": 7,\n \"temperature\": 0,\n \"stream\": true\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nfor chunk in client.completions.create(\n model=\"gpt-3.5-turbo-instruct\",\n prompt=\"Say this is a test\",\n max_tokens=7,\n temperature=0,\n stream=True\n):\n print(chunk.choices[0].text)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const stream = await openai.completions.create({\n model: \"gpt-3.5-turbo-instruct\",\n prompt: \"Say this is a test.\",\n stream: true,\n });\n\n for await (const chunk of stream) {\n console.log(chunk.choices[0].text)\n }\n}\nmain();" + }, + "response": "{\n \"id\": \"cmpl-7iA7iJjj8V2zOkCGvWF2hAkDWBQZe\",\n \"object\": \"text_completion\",\n \"created\": 1690759702,\n \"choices\": [\n {\n \"text\": \"This\",\n \"index\": 0,\n \"logprobs\": null,\n \"finish_reason\": null\n }\n ],\n \"model\": \"gpt-3.5-turbo-instruct\"\n \"system_fingerprint\": \"fp_44709d6fcb\",\n}\n" + } + ] + } + } + }, + "/containers": { + "get": { + "summary": "List Containers", + "description": "Lists containers.", + "operationId": "ListContainers", + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "schema": { + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "schema": { + "type": "string" + } + }, + { + "name": "name", + "in": "query", + "description": "Filter results by container name.", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContainerListResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List containers", + "group": "containers", + "path": "get", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/containers \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"cntr_682dfebaacac8198bbfe9c2474fb6f4a085685cbe3cb5863\",\n \"object\": \"container\",\n \"created_at\": 1747844794,\n \"status\": \"running\",\n \"expires_after\": {\n \"anchor\": \"last_active_at\",\n \"minutes\": 20\n },\n \"last_active_at\": 1747844794,\n \"memory_limit\": \"4g\",\n \"name\": \"My Container\"\n }\n ],\n \"first_id\": \"container_123\",\n \"last_id\": \"container_123\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "summary": "Create Container", + "description": "Creates a container.", + "operationId": "CreateContainer", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateContainerBody" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContainerResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Create container", + "group": "containers", + "path": "post", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/containers \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"My Container\",\n \"memory_limit\": \"4g\",\n \"skills\": [\n {\n \"type\": \"skill_reference\",\n \"skill_id\": \"skill_4db6f1a2c9e73508b41f9da06e2c7b5f\"\n },\n {\n \"type\": \"skill_reference\",\n \"skill_id\": \"openai-spreadsheets\",\n \"version\": \"latest\"\n }\n ],\n \"network_policy\": {\n \"type\": \"allowlist\",\n \"allowed_domains\": [\"api.buildkite.com\"]\n }\n }'\n" + }, + "response": "{\n \"id\": \"cntr_682e30645a488191b6363a0cbefc0f0a025ec61b66250591\",\n \"object\": \"container\",\n \"created_at\": 1747857508,\n \"status\": \"running\",\n \"expires_after\": {\n \"anchor\": \"last_active_at\",\n \"minutes\": 20\n },\n \"last_active_at\": 1747857508,\n \"network_policy\": {\n \"type\": \"allowlist\",\n \"allowed_domains\": [\"api.buildkite.com\"]\n },\n \"memory_limit\": \"4g\",\n \"name\": \"My Container\"\n}\n" + } + } + } + }, + "/containers/{container_id}": { + "get": { + "summary": "Retrieve Container", + "description": "Retrieves a container.", + "operationId": "RetrieveContainer", + "parameters": [ + { + "name": "container_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContainerResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve container", + "group": "containers", + "path": "get", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/containers/cntr_682dfebaacac8198bbfe9c2474fb6f4a085685cbe3cb5863 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n" + }, + "response": "{\n \"id\": \"cntr_682dfebaacac8198bbfe9c2474fb6f4a085685cbe3cb5863\",\n \"object\": \"container\",\n \"created_at\": 1747844794,\n \"status\": \"running\",\n \"expires_after\": {\n \"anchor\": \"last_active_at\",\n \"minutes\": 20\n },\n \"last_active_at\": 1747844794,\n \"memory_limit\": \"4g\",\n \"name\": \"My Container\"\n}\n" + } + } + }, + "delete": { + "operationId": "DeleteContainer", + "summary": "Delete Container", + "description": "Delete a container.", + "parameters": [ + { + "name": "container_id", + "in": "path", + "description": "The ID of the container to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Delete a container", + "group": "containers", + "path": "delete", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/containers/cntr_682dfebaacac8198bbfe9c2474fb6f4a085685cbe3cb5863 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n" + }, + "response": "{\n \"id\": \"cntr_682dfebaacac8198bbfe9c2474fb6f4a085685cbe3cb5863\",\n \"object\": \"container.deleted\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/containers/{container_id}/files": { + "post": { + "summary": "Create a Container File\n\nYou can send either a multipart/form-data request with the raw file content, or a JSON request with a file ID.\n", + "description": "Creates a container file.\n", + "operationId": "CreateContainerFile", + "parameters": [ + { + "name": "container_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateContainerFileBody" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateContainerFileBody" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContainerFileResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Create container file", + "group": "containers", + "path": "post", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/containers/cntr_682e0e7318108198aa783fd921ff305e08e78805b9fdbb04/files \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -F file=\"@example.txt\"\n" + }, + "response": "{\n \"id\": \"cfile_682e0e8a43c88191a7978f477a09bdf5\",\n \"object\": \"container.file\",\n \"created_at\": 1747848842,\n \"bytes\": 880,\n \"container_id\": \"cntr_682e0e7318108198aa783fd921ff305e08e78805b9fdbb04\",\n \"path\": \"/mnt/data/88e12fa445d32636f190a0b33daed6cb-tsconfig.json\",\n \"source\": \"user\"\n}\n" + } + } + }, + "get": { + "summary": "List Container files", + "description": "Lists container files.", + "operationId": "ListContainerFiles", + "parameters": [ + { + "name": "container_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "schema": { + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContainerFileListResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List container files", + "group": "containers", + "path": "get", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/containers/cntr_682e0e7318108198aa783fd921ff305e08e78805b9fdbb04/files \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"cfile_682e0e8a43c88191a7978f477a09bdf5\",\n \"object\": \"container.file\",\n \"created_at\": 1747848842,\n \"bytes\": 880,\n \"container_id\": \"cntr_682e0e7318108198aa783fd921ff305e08e78805b9fdbb04\",\n \"path\": \"/mnt/data/88e12fa445d32636f190a0b33daed6cb-tsconfig.json\",\n \"source\": \"user\"\n }\n ],\n \"first_id\": \"cfile_682e0e8a43c88191a7978f477a09bdf5\",\n \"has_more\": false,\n \"last_id\": \"cfile_682e0e8a43c88191a7978f477a09bdf5\"\n}\n" + } + } + } + }, + "/containers/{container_id}/files/{file_id}": { + "get": { + "summary": "Retrieve Container File", + "description": "Retrieves a container file.", + "operationId": "RetrieveContainerFile", + "parameters": [ + { + "name": "container_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "file_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContainerFileResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve container file", + "group": "containers", + "path": "get", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/containers/container_123/files/file_456 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n" + }, + "response": "{\n \"id\": \"cfile_682e0e8a43c88191a7978f477a09bdf5\",\n \"object\": \"container.file\",\n \"created_at\": 1747848842,\n \"bytes\": 880,\n \"container_id\": \"cntr_682e0e7318108198aa783fd921ff305e08e78805b9fdbb04\",\n \"path\": \"/mnt/data/88e12fa445d32636f190a0b33daed6cb-tsconfig.json\",\n \"source\": \"user\"\n}\n" + } + } + }, + "delete": { + "operationId": "DeleteContainerFile", + "summary": "Delete Container File", + "description": "Delete a container file.", + "parameters": [ + { + "name": "container_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "file_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Delete a container file", + "group": "containers", + "path": "delete", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/containers/cntr_682dfebaacac8198bbfe9c2474fb6f4a085685cbe3cb5863/files/cfile_682e0e8a43c88191a7978f477a09bdf5 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n" + }, + "response": "{\n \"id\": \"cfile_682e0e8a43c88191a7978f477a09bdf5\",\n \"object\": \"container.file.deleted\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/containers/{container_id}/files/{file_id}/content": { + "get": { + "summary": "Retrieve Container File Content", + "description": "Retrieves a container file content.", + "operationId": "RetrieveContainerFileContent", + "parameters": [ + { + "name": "container_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "file_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve container file content", + "group": "containers", + "path": "get", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/containers/container_123/files/cfile_456/content \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n" + }, + "response": "\n" + } + } + } + }, + "/conversations/{conversation_id}/items": { + "post": { + "operationId": "createConversationItems", + "tags": [ + "Conversations" + ], + "summary": "Create items in a conversation with the given ID.", + "parameters": [ + { + "in": "path", + "name": "conversation_id", + "required": true, + "schema": { + "type": "string", + "example": "conv_123" + }, + "description": "The ID of the conversation to add the item to." + }, + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IncludeEnum" + } + }, + "description": "Additional fields to include in the response. See the `include`\nparameter for [listing Conversation items above](https://developers.openai.com/api/reference/resources/conversations/subresources/items/methods/list#%28resource%29%20conversations.items%20%3E%20%28method%29%20list%20%3E%20%28params%29%20default%20%3E%20%28param%29%20include%20%3E%20%28schema%29) for more information.\n" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "items": { + "type": "array", + "description": "The items to add to the conversation. You may add up to 20 items at a time.\n", + "items": { + "$ref": "#/components/schemas/InputItem" + }, + "maxItems": 20 + } + }, + "required": [ + "items" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConversationItemList" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Create items", + "group": "conversations", + "path": "create-item", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/conversations/conv_123/items \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"items\": [\n {\n \"type\": \"message\",\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"input_text\", \"text\": \"Hello!\"}\n ]\n },\n {\n \"type\": \"message\",\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"input_text\", \"text\": \"How are you?\"}\n ]\n }\n ]\n }'\n", + "javascript": "import OpenAI from \"openai\";\nconst client = new OpenAI();\n\nconst items = await client.conversations.items.create(\n \"conv_123\",\n {\n items: [\n {\n type: \"message\",\n role: \"user\",\n content: [{ type: \"input_text\", text: \"Hello!\" }],\n },\n {\n type: \"message\",\n role: \"user\",\n content: [{ type: \"input_text\", text: \"How are you?\" }],\n },\n ],\n }\n);\nconsole.log(items.data);\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nitems = client.conversations.items.create(\n \"conv_123\",\n items=[\n {\n \"type\": \"message\",\n \"role\": \"user\",\n \"content\": [{\"type\": \"input_text\", \"text\": \"Hello!\"}],\n },\n {\n \"type\": \"message\",\n \"role\": \"user\",\n \"content\": [{\"type\": \"input_text\", \"text\": \"How are you?\"}],\n }\n ],\n)\nprint(items.data)\n", + "csharp": "using System;\nusing System.Collections.Generic;\nusing OpenAI.Conversations;\n\nOpenAIConversationClient client = new(\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nConversationItemList created = client.ConversationItems.Create(\n conversationId: \"conv_123\",\n new CreateConversationItemsOptions\n {\n Items = new List\n {\n new ConversationMessage\n {\n Role = \"user\",\n Content =\n {\n new ConversationInputText { Text = \"Hello!\" }\n }\n },\n new ConversationMessage\n {\n Role = \"user\",\n Content =\n {\n new ConversationInputText { Text = \"How are you?\" }\n }\n }\n }\n }\n);\nConsole.WriteLine(created.Data.Count);\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"type\": \"message\",\n \"id\": \"msg_abc\",\n \"status\": \"completed\",\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"input_text\", \"text\": \"Hello!\"}\n ]\n },\n {\n \"type\": \"message\",\n \"id\": \"msg_def\",\n \"status\": \"completed\",\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"input_text\", \"text\": \"How are you?\"}\n ]\n }\n ],\n \"first_id\": \"msg_abc\",\n \"last_id\": \"msg_def\",\n \"has_more\": false\n}\n" + } + } + }, + "get": { + "operationId": "listConversationItems", + "tags": [ + "Conversations" + ], + "summary": "List all items for a conversation with the given ID.", + "parameters": [ + { + "in": "path", + "name": "conversation_id", + "required": true, + "schema": { + "type": "string", + "example": "conv_123" + }, + "description": "The ID of the conversation to list items for." + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between\n1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "in": "query", + "name": "order", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "description": "The order to return the input items in. Default is `desc`.\n- `asc`: Return the input items in ascending order.\n- `desc`: Return the input items in descending order.\n" + }, + { + "in": "query", + "name": "after", + "schema": { + "type": "string" + }, + "description": "An item ID to list items after, used in pagination.\n" + }, + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IncludeEnum" + } + }, + "description": "Specify additional output data to include in the model response. Currently supported values are:\n- `web_search_call.action.sources`: Include the sources of the web search tool call.\n- `code_interpreter_call.outputs`: Includes the outputs of python code execution in code interpreter tool call items.\n- `computer_call_output.output.image_url`: Include image urls from the computer call output.\n- `file_search_call.results`: Include the search results of the file search tool call.\n- `message.input_image.image_url`: Include image urls from the input message.\n- `message.output_text.logprobs`: Include logprobs with assistant messages.\n- `reasoning.encrypted_content`: Includes an encrypted version of reasoning tokens in reasoning item outputs. This enables reasoning items to be used in multi-turn conversations when using the Responses API statelessly (like when the `store` parameter is set to `false`, or when an organization is enrolled in the zero data retention program)." + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConversationItemList" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List items", + "group": "conversations", + "path": "list-items", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/conversations/conv_123/items?limit=10\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "javascript": "import OpenAI from \"openai\";\nconst client = new OpenAI();\n\nconst items = await client.conversations.items.list(\"conv_123\", { limit: 10 });\nconsole.log(items.data);\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nitems = client.conversations.items.list(\"conv_123\", limit=10)\nprint(items.data)\n", + "csharp": "using System;\nusing OpenAI.Conversations;\n\nOpenAIConversationClient client = new(\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nConversationItemList items = client.ConversationItems.List(\n conversationId: \"conv_123\",\n new ListConversationItemsOptions { Limit = 10 }\n);\nConsole.WriteLine(items.Data.Count);\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"type\": \"message\",\n \"id\": \"msg_abc\",\n \"status\": \"completed\",\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"input_text\", \"text\": \"Hello!\"}\n ]\n }\n ],\n \"first_id\": \"msg_abc\",\n \"last_id\": \"msg_abc\",\n \"has_more\": false\n}\n" + } + } + } + }, + "/conversations/{conversation_id}/items/{item_id}": { + "get": { + "operationId": "getConversationItem", + "tags": [ + "Conversations" + ], + "summary": "Get a single item from a conversation with the given IDs.", + "parameters": [ + { + "in": "path", + "name": "conversation_id", + "required": true, + "schema": { + "type": "string", + "example": "conv_123" + }, + "description": "The ID of the conversation that contains the item." + }, + { + "in": "path", + "name": "item_id", + "required": true, + "schema": { + "type": "string", + "example": "msg_abc" + }, + "description": "The ID of the item to retrieve." + }, + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IncludeEnum" + } + }, + "description": "Additional fields to include in the response. See the `include`\nparameter for [listing Conversation items above](https://developers.openai.com/api/reference/resources/conversations/subresources/items/methods/list#%28resource%29%20conversations.items%20%3E%20%28method%29%20list%20%3E%20%28params%29%20default%20%3E%20%28param%29%20include%20%3E%20%28schema%29) for more information.\n" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConversationItem" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve an item", + "group": "conversations", + "path": "get-item", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/conversations/conv_123/items/msg_abc \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "javascript": "import OpenAI from \"openai\";\nconst client = new OpenAI();\n\nconst item = await client.conversations.items.retrieve(\n \"conv_123\",\n \"msg_abc\"\n);\nconsole.log(item);\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nitem = client.conversations.items.retrieve(\"conv_123\", \"msg_abc\")\nprint(item)\n", + "csharp": "using System;\nusing OpenAI.Conversations;\n\nOpenAIConversationClient client = new(\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nConversationItem item = client.ConversationItems.Get(\n conversationId: \"conv_123\",\n itemId: \"msg_abc\"\n);\nConsole.WriteLine(item.Id);\n" + }, + "response": "{\n \"type\": \"message\",\n \"id\": \"msg_abc\",\n \"status\": \"completed\",\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"input_text\", \"text\": \"Hello!\"}\n ]\n}\n" + } + } + }, + "delete": { + "operationId": "deleteConversationItem", + "tags": [ + "Conversations" + ], + "summary": "Delete an item from a conversation with the given IDs.", + "parameters": [ + { + "in": "path", + "name": "conversation_id", + "required": true, + "schema": { + "type": "string", + "example": "conv_123" + }, + "description": "The ID of the conversation that contains the item." + }, + { + "in": "path", + "name": "item_id", + "required": true, + "schema": { + "type": "string", + "example": "msg_abc" + }, + "description": "The ID of the item to delete." + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConversationResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Delete an item", + "group": "conversations", + "path": "delete-item", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/conversations/conv_123/items/msg_abc \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "javascript": "import OpenAI from \"openai\";\nconst client = new OpenAI();\n\nconst conversation = await client.conversations.items.delete(\n \"conv_123\",\n \"msg_abc\"\n);\nconsole.log(conversation);\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nconversation = client.conversations.items.delete(\"conv_123\", \"msg_abc\")\nprint(conversation)\n", + "csharp": "using System;\nusing OpenAI.Conversations;\n\nOpenAIConversationClient client = new(\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nConversation conversation = client.ConversationItems.Delete(\n conversationId: \"conv_123\",\n itemId: \"msg_abc\"\n);\nConsole.WriteLine(conversation.Id);\n" + }, + "response": "{\n \"id\": \"conv_123\",\n \"object\": \"conversation\",\n \"created_at\": 1741900000,\n \"metadata\": {\"topic\": \"demo\"}\n}\n" + } + } + } + }, + "/embeddings": { + "post": { + "operationId": "createEmbedding", + "tags": [ + "Embeddings" + ], + "summary": "Creates an embedding vector representing the input text.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateEmbeddingRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateEmbeddingResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/InferenceRateLimited" + }, + "503": { + "$ref": "#/components/responses/InferenceServiceUnavailable" + } + }, + "x-oaiMeta": { + "name": "Create embeddings", + "group": "embeddings", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/embeddings \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"input\": \"The food was delicious and the waiter...\",\n \"model\": \"text-embedding-ada-002\",\n \"encoding_format\": \"float\"\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.embeddings.create(\n model=\"text-embedding-ada-002\",\n input=\"The food was delicious and the waiter...\",\n encoding_format=\"float\"\n)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const embedding = await openai.embeddings.create({\n model: \"text-embedding-ada-002\",\n input: \"The quick brown fox jumped over the lazy dog\",\n encoding_format: \"float\",\n });\n\n console.log(embedding);\n}\n\nmain();\n", + "csharp": "using System;\n\nusing OpenAI.Embeddings;\n\nEmbeddingClient client = new(\n model: \"text-embedding-3-small\",\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nOpenAIEmbedding embedding = client.GenerateEmbedding(input: \"The quick brown fox jumped over the lazy dog\");\nReadOnlyMemory vector = embedding.ToFloats();\n\nfor (int i = 0; i < vector.Length; i++)\n{\n Console.WriteLine($\" [{i,4}] = {vector.Span[i]}\");\n}\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"embedding\",\n \"embedding\": [\n 0.0023064255,\n -0.009327292,\n .... (1536 floats total for ada-002)\n -0.0028842222,\n ],\n \"index\": 0\n }\n ],\n \"model\": \"text-embedding-ada-002\",\n \"usage\": {\n \"prompt_tokens\": 8,\n \"total_tokens\": 8\n }\n}\n" + } + } + } + }, + "/evals": { + "get": { + "operationId": "listEvals", + "tags": [ + "Evals" + ], + "summary": "List evaluations for a project.\n", + "parameters": [ + { + "name": "after", + "in": "query", + "description": "Identifier for the last eval from the previous pagination request.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "Number of evals to retrieve.", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for evals by timestamp. Use `asc` for ascending order or `desc` for descending order.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + }, + { + "name": "order_by", + "in": "query", + "description": "Evals can be ordered by creation time or last updated time. Use\n`created_at` for creation time or `updated_at` for last updated time.\n", + "required": false, + "schema": { + "type": "string", + "enum": [ + "created_at", + "updated_at" + ], + "default": "created_at" + } + } + ], + "responses": { + "200": { + "description": "A list of evals", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvalList" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List evals", + "group": "evals", + "path": "list", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/evals?limit=1 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nevals = client.evals.list(limit=1)\nprint(evals)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst evals = await openai.evals.list({ limit: 1 });\nconsole.log(evals);\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"eval_67abd54d9b0081909a86353f6fb9317a\",\n \"object\": \"eval\",\n \"data_source_config\": {\n \"type\": \"stored_completions\",\n \"metadata\": {\n \"usecase\": \"push_notifications_summarizer\"\n },\n \"schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"item\": {\n \"type\": \"object\"\n },\n \"sample\": {\n \"type\": \"object\"\n }\n },\n \"required\": [\n \"item\",\n \"sample\"\n ]\n }\n },\n \"testing_criteria\": [\n {\n \"name\": \"Push Notification Summary Grader\",\n \"id\": \"Push Notification Summary Grader-9b876f24-4762-4be9-aff4-db7a9b31c673\",\n \"type\": \"label_model\",\n \"model\": \"o3-mini\",\n \"input\": [\n {\n \"type\": \"message\",\n \"role\": \"developer\",\n \"content\": {\n \"type\": \"input_text\",\n \"text\": \"\\nLabel the following push notification summary as either correct or incorrect.\\nThe push notification and the summary will be provided below.\\nA good push notificiation summary is concise and snappy.\\nIf it is good, then label it as correct, if not, then incorrect.\\n\"\n }\n },\n {\n \"type\": \"message\",\n \"role\": \"user\",\n \"content\": {\n \"type\": \"input_text\",\n \"text\": \"\\nPush notifications: {{item.input}}\\nSummary: {{sample.output_text}}\\n\"\n }\n }\n ],\n \"passing_labels\": [\n \"correct\"\n ],\n \"labels\": [\n \"correct\",\n \"incorrect\"\n ],\n \"sampling_params\": null\n }\n ],\n \"name\": \"Push Notification Summary Grader\",\n \"created_at\": 1739314509,\n \"metadata\": {\n \"description\": \"A stored completions eval for push notification summaries\"\n }\n }\n ],\n \"first_id\": \"eval_67abd54d9b0081909a86353f6fb9317a\",\n \"last_id\": \"eval_67aa884cf6688190b58f657d4441c8b7\",\n \"has_more\": true\n}\n" + } + } + }, + "post": { + "operationId": "createEval", + "tags": [ + "Evals" + ], + "summary": "Create the structure of an evaluation that can be used to test a model's performance.\nAn evaluation is a set of testing criteria and the config for a data source, which dictates the schema of the data used in the evaluation. After creating an evaluation, you can run it on different models and model parameters. We support several types of graders and datasources.\nFor more information, see the [Evals guide](https://developers.openai.com/api/docs/guides/evals).\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateEvalRequest" + } + } + } + }, + "responses": { + "201": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Eval" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Create eval", + "group": "evals", + "path": "post", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/evals \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Sentiment\",\n \"data_source_config\": {\n \"type\": \"stored_completions\",\n \"metadata\": {\n \"usecase\": \"chatbot\"\n }\n },\n \"testing_criteria\": [\n {\n \"type\": \"label_model\",\n \"model\": \"o3-mini\",\n \"input\": [\n {\n \"role\": \"developer\",\n \"content\": \"Classify the sentiment of the following statement as one of 'positive', 'neutral', or 'negative'\"\n },\n {\n \"role\": \"user\",\n \"content\": \"Statement: {{item.input}}\"\n }\n ],\n \"passing_labels\": [\n \"positive\"\n ],\n \"labels\": [\n \"positive\",\n \"neutral\",\n \"negative\"\n ],\n \"name\": \"Example label grader\"\n }\n ]\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\neval_obj = client.evals.create(\n name=\"Sentiment\",\n data_source_config={\n \"type\": \"stored_completions\",\n \"metadata\": {\"usecase\": \"chatbot\"}\n },\n testing_criteria=[\n {\n \"type\": \"label_model\",\n \"model\": \"o3-mini\",\n \"input\": [\n {\"role\": \"developer\", \"content\": \"Classify the sentiment of the following statement as one of 'positive', 'neutral', or 'negative'\"},\n {\"role\": \"user\", \"content\": \"Statement: {{item.input}}\"}\n ],\n \"passing_labels\": [\"positive\"],\n \"labels\": [\"positive\", \"neutral\", \"negative\"],\n \"name\": \"Example label grader\"\n }\n ]\n)\nprint(eval_obj)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst evalObj = await openai.evals.create({\n name: \"Sentiment\",\n data_source_config: {\n type: \"stored_completions\",\n metadata: { usecase: \"chatbot\" }\n },\n testing_criteria: [\n {\n type: \"label_model\",\n model: \"o3-mini\",\n input: [\n { role: \"developer\", content: \"Classify the sentiment of the following statement as one of 'positive', 'neutral', or 'negative'\" },\n { role: \"user\", content: \"Statement: {{item.input}}\" }\n ],\n passing_labels: [\"positive\"],\n labels: [\"positive\", \"neutral\", \"negative\"],\n name: \"Example label grader\"\n }\n ]\n});\nconsole.log(evalObj);\n" + }, + "response": "{\n \"object\": \"eval\",\n \"id\": \"eval_67b7fa9a81a88190ab4aa417e397ea21\",\n \"data_source_config\": {\n \"type\": \"stored_completions\",\n \"metadata\": {\n \"usecase\": \"chatbot\"\n },\n \"schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"item\": {\n \"type\": \"object\"\n },\n \"sample\": {\n \"type\": \"object\"\n }\n },\n \"required\": [\n \"item\",\n \"sample\"\n ]\n },\n \"testing_criteria\": [\n {\n \"name\": \"Example label grader\",\n \"type\": \"label_model\",\n \"model\": \"o3-mini\",\n \"input\": [\n {\n \"type\": \"message\",\n \"role\": \"developer\",\n \"content\": {\n \"type\": \"input_text\",\n \"text\": \"Classify the sentiment of the following statement as one of positive, neutral, or negative\"\n }\n },\n {\n \"type\": \"message\",\n \"role\": \"user\",\n \"content\": {\n \"type\": \"input_text\",\n \"text\": \"Statement: {{item.input}}\"\n }\n }\n ],\n \"passing_labels\": [\n \"positive\"\n ],\n \"labels\": [\n \"positive\",\n \"neutral\",\n \"negative\"\n ]\n }\n ],\n \"name\": \"Sentiment\",\n \"created_at\": 1740110490,\n \"metadata\": {\n \"description\": \"An eval for sentiment analysis\"\n }\n}\n" + } + } + } + }, + "/evals/{eval_id}": { + "get": { + "operationId": "getEval", + "tags": [ + "Evals" + ], + "summary": "Get an evaluation by ID.\n", + "parameters": [ + { + "name": "eval_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the evaluation to retrieve." + } + ], + "responses": { + "200": { + "description": "The evaluation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Eval" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Get an eval", + "group": "evals", + "path": "get", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/evals/eval_67abd54d9b0081909a86353f6fb9317a \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\neval_obj = client.evals.retrieve(\"eval_67abd54d9b0081909a86353f6fb9317a\")\nprint(eval_obj)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst evalObj = await openai.evals.retrieve(\"eval_67abd54d9b0081909a86353f6fb9317a\");\nconsole.log(evalObj);\n" + }, + "response": "{\n \"object\": \"eval\",\n \"id\": \"eval_67abd54d9b0081909a86353f6fb9317a\",\n \"data_source_config\": {\n \"type\": \"custom\",\n \"schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"item\": {\n \"type\": \"object\",\n \"properties\": {\n \"input\": {\n \"type\": \"string\"\n },\n \"ground_truth\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"input\",\n \"ground_truth\"\n ]\n }\n },\n \"required\": [\n \"item\"\n ]\n }\n },\n \"testing_criteria\": [\n {\n \"name\": \"String check\",\n \"id\": \"String check-2eaf2d8d-d649-4335-8148-9535a7ca73c2\",\n \"type\": \"string_check\",\n \"input\": \"{{item.input}}\",\n \"reference\": \"{{item.ground_truth}}\",\n \"operation\": \"eq\"\n }\n ],\n \"name\": \"External Data Eval\",\n \"created_at\": 1739314509,\n \"metadata\": {},\n}\n" + } + } + }, + "post": { + "operationId": "updateEval", + "tags": [ + "Evals" + ], + "summary": "Update certain properties of an evaluation.\n", + "parameters": [ + { + "name": "eval_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the evaluation to update." + } + ], + "requestBody": { + "description": "Request to update an evaluation", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Rename the evaluation." + }, + "metadata": { + "$ref": "#/components/schemas/Metadata" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The updated evaluation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Eval" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Update an eval", + "group": "evals", + "path": "update", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/evals/eval_67abd54d9b0081909a86353f6fb9317a \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"name\": \"Updated Eval\", \"metadata\": {\"description\": \"Updated description\"}}'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nupdated_eval = client.evals.update(\n \"eval_67abd54d9b0081909a86353f6fb9317a\",\n name=\"Updated Eval\",\n metadata={\"description\": \"Updated description\"}\n)\nprint(updated_eval)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst updatedEval = await openai.evals.update(\n \"eval_67abd54d9b0081909a86353f6fb9317a\",\n {\n name: \"Updated Eval\",\n metadata: { description: \"Updated description\" }\n }\n);\nconsole.log(updatedEval);\n" + }, + "response": "{\n \"object\": \"eval\",\n \"id\": \"eval_67abd54d9b0081909a86353f6fb9317a\",\n \"data_source_config\": {\n \"type\": \"custom\",\n \"schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"item\": {\n \"type\": \"object\",\n \"properties\": {\n \"input\": {\n \"type\": \"string\"\n },\n \"ground_truth\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"input\",\n \"ground_truth\"\n ]\n }\n },\n \"required\": [\n \"item\"\n ]\n }\n },\n \"testing_criteria\": [\n {\n \"name\": \"String check\",\n \"id\": \"String check-2eaf2d8d-d649-4335-8148-9535a7ca73c2\",\n \"type\": \"string_check\",\n \"input\": \"{{item.input}}\",\n \"reference\": \"{{item.ground_truth}}\",\n \"operation\": \"eq\"\n }\n ],\n \"name\": \"Updated Eval\",\n \"created_at\": 1739314509,\n \"metadata\": {\"description\": \"Updated description\"},\n}\n" + } + } + }, + "delete": { + "operationId": "deleteEval", + "tags": [ + "Evals" + ], + "summary": "Delete an evaluation.\n", + "parameters": [ + { + "name": "eval_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the evaluation to delete." + } + ], + "responses": { + "200": { + "description": "Successfully deleted the evaluation.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "object": { + "type": "string", + "example": "eval.deleted" + }, + "deleted": { + "type": "boolean", + "example": true + }, + "eval_id": { + "type": "string", + "example": "eval_abc123" + } + }, + "required": [ + "object", + "deleted", + "eval_id" + ] + } + } + } + }, + "404": { + "description": "Evaluation not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Delete an eval", + "group": "evals", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/evals/eval_abc123 \\\n -X DELETE \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ndeleted = client.evals.delete(\"eval_abc123\")\nprint(deleted)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst deleted = await openai.evals.delete(\"eval_abc123\");\nconsole.log(deleted);\n" + }, + "response": "{\n \"object\": \"eval.deleted\",\n \"deleted\": true,\n \"eval_id\": \"eval_abc123\"\n}\n" + } + } + } + }, + "/evals/{eval_id}/runs": { + "get": { + "operationId": "getEvalRuns", + "tags": [ + "Evals" + ], + "summary": "Get a list of runs for an evaluation.\n", + "parameters": [ + { + "name": "eval_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the evaluation to retrieve runs for." + }, + { + "name": "after", + "in": "query", + "description": "Identifier for the last run from the previous pagination request.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "Number of runs to retrieve.", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for runs by timestamp. Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + }, + { + "name": "status", + "in": "query", + "description": "Filter runs by status. One of `queued` | `in_progress` | `failed` | `completed` | `canceled`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "queued", + "in_progress", + "completed", + "canceled", + "failed" + ] + } + } + ], + "responses": { + "200": { + "description": "A list of runs for the evaluation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvalRunList" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Get eval runs", + "group": "evals", + "path": "get-runs", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/evals/egroup_67abd54d9b0081909a86353f6fb9317a/runs \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nruns = client.evals.runs.list(\"egroup_67abd54d9b0081909a86353f6fb9317a\")\nprint(runs)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst runs = await openai.evals.runs.list(\"egroup_67abd54d9b0081909a86353f6fb9317a\");\nconsole.log(runs);\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"eval.run\",\n \"id\": \"evalrun_67e0c7d31560819090d60c0780591042\",\n \"eval_id\": \"eval_67e0c726d560819083f19a957c4c640b\",\n \"report_url\": \"https://platform.openai.com/evaluations/eval_67e0c726d560819083f19a957c4c640b\",\n \"status\": \"completed\",\n \"model\": \"o3-mini\",\n \"name\": \"bulk_with_negative_examples_o3-mini\",\n \"created_at\": 1742784467,\n \"result_counts\": {\n \"total\": 1,\n \"errored\": 0,\n \"failed\": 0,\n \"passed\": 1\n },\n \"per_model_usage\": [\n {\n \"model_name\": \"o3-mini\",\n \"invocation_count\": 1,\n \"prompt_tokens\": 563,\n \"completion_tokens\": 874,\n \"total_tokens\": 1437,\n \"cached_tokens\": 0\n }\n ],\n \"per_testing_criteria_results\": [\n {\n \"testing_criteria\": \"Push Notification Summary Grader-1808cd0b-eeec-4e0b-a519-337e79f4f5d1\",\n \"passed\": 1,\n \"failed\": 0\n }\n ],\n \"data_source\": {\n \"type\": \"completions\",\n \"source\": {\n \"type\": \"file_content\",\n \"content\": [\n {\n \"item\": {\n \"notifications\": \"\\n- New message from Sarah: \\\"Can you call me later?\\\"\\n- Your package has been delivered!\\n- Flash sale: 20% off electronics for the next 2 hours!\\n\"\n }\n }\n ]\n },\n \"input_messages\": {\n \"type\": \"template\",\n \"template\": [\n {\n \"type\": \"message\",\n \"role\": \"developer\",\n \"content\": {\n \"type\": \"input_text\",\n \"text\": \"\\n\\n\\n\\nYou are a helpful assistant that takes in an array of push notifications and returns a collapsed summary of them.\\nThe push notification will be provided as follows:\\n\\n...notificationlist...\\n\\n\\nYou should return just the summary and nothing else.\\n\\n\\nYou should return a summary that is concise and snappy.\\n\\n\\nHere is an example of a good summary:\\n\\n- Traffic alert: Accident reported on Main Street.- Package out for delivery: Expected by 5 PM.- New friend suggestion: Connect with Emma.\\n\\n\\nTraffic alert, package expected by 5pm, suggestion for new friend (Emily).\\n\\n\\n\\nHere is an example of a bad summary:\\n\\n- Traffic alert: Accident reported on Main Street.- Package out for delivery: Expected by 5 PM.- New friend suggestion: Connect with Emma.\\n\\n\\nTraffic alert reported on main street. You have a package that will arrive by 5pm, Emily is a new friend suggested for you.\\n\\n\"\n }\n },\n {\n \"type\": \"message\",\n \"role\": \"user\",\n \"content\": {\n \"type\": \"input_text\",\n \"text\": \"{{item.notifications}}\"\n }\n }\n ]\n },\n \"model\": \"o3-mini\",\n \"sampling_params\": null\n },\n \"error\": null,\n \"metadata\": {}\n }\n ],\n \"first_id\": \"evalrun_67e0c7d31560819090d60c0780591042\",\n \"last_id\": \"evalrun_67e0c7d31560819090d60c0780591042\",\n \"has_more\": true\n}\n" + } + } + }, + "post": { + "operationId": "createEvalRun", + "tags": [ + "Evals" + ], + "summary": "Kicks off a new run for a given evaluation, specifying the data source, and what model configuration to use to test. The datasource will be validated against the schema specified in the config of the evaluation.\n", + "parameters": [ + { + "in": "path", + "name": "eval_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the evaluation to create a run for." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateEvalRunRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Successfully created a run for the evaluation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvalRun" + } + } + } + }, + "400": { + "description": "Bad request (for example, missing eval object)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Create eval run", + "group": "evals", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/evals/eval_67e579652b548190aaa83ada4b125f47/runs \\\n -X POST \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"name\":\"gpt-6-astra\",\"data_source\":{\"type\":\"completions\",\"input_messages\":{\"type\":\"template\",\"template\":[{\"role\":\"developer\",\"content\":\"Categorize a given news headline into one of the following topics: Technology, Markets, World, Business, or Sports.\\n\\n# Steps\\n\\n1. Analyze the content of the news headline to understand its primary focus.\\n2. Extract the subject matter, identifying any key indicators or keywords.\\n3. Use the identified indicators to determine the most suitable category out of the five options: Technology, Markets, World, Business, or Sports.\\n4. Ensure only one category is selected per headline.\\n\\n# Output Format\\n\\nRespond with the chosen category as a single word. For instance: \\\"Technology\\\", \\\"Markets\\\", \\\"World\\\", \\\"Business\\\", or \\\"Sports\\\".\\n\\n# Examples\\n\\n**Input**: \\\"Apple Unveils New iPhone Model, Featuring Advanced AI Features\\\" \\n**Output**: \\\"Technology\\\"\\n\\n**Input**: \\\"Global Stocks Mixed as Investors Await Central Bank Decisions\\\" \\n**Output**: \\\"Markets\\\"\\n\\n**Input**: \\\"War in Ukraine: Latest Updates on Negotiation Status\\\" \\n**Output**: \\\"World\\\"\\n\\n**Input**: \\\"Microsoft in Talks to Acquire Gaming Company for $2 Billion\\\" \\n**Output**: \\\"Business\\\"\\n\\n**Input**: \\\"Manchester United Secures Win in Premier League Football Match\\\" \\n**Output**: \\\"Sports\\\" \\n\\n# Notes\\n\\n- If the headline appears to fit into more than one category, choose the most dominant theme.\\n- Keywords or phrases such as \\\"stocks\\\", \\\"company acquisition\\\", \\\"match\\\", or technological brands can be good indicators for classification.\\n\"} , {\"role\":\"user\",\"content\":\"{{item.input}}\"}]} ,\"sampling_params\":{\"max_completions_tokens\":2048},\"model\":\"gpt-6-astra\",\"source\":{\"type\":\"file_content\",\"content\":[{\"item\":{\"input\":\"Tech Company Launches Advanced Artificial Intelligence Platform\",\"ground_truth\":\"Technology\"}}]}}}'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nrun = client.evals.runs.create(\n \"eval_67e579652b548190aaa83ada4b125f47\",\n name=\"gpt-6-astra\",\n data_source={\n \"type\": \"completions\",\n \"input_messages\": {\n \"type\": \"template\",\n \"template\": [\n {\n \"role\": \"developer\",\n \"content\": \"Categorize a given news headline into one of the following topics: Technology, Markets, World, Business, or Sports.\\n\\n# Steps\\n\\n1. Analyze the content of the news headline to understand its primary focus.\\n2. Extract the subject matter, identifying any key indicators or keywords.\\n3. Use the identified indicators to determine the most suitable category out of the five options: Technology, Markets, World, Business, or Sports.\\n4. Ensure only one category is selected per headline.\\n\\n# Output Format\\n\\nRespond with the chosen category as a single word. For instance: \\\"Technology\\\", \\\"Markets\\\", \\\"World\\\", \\\"Business\\\", or \\\"Sports\\\".\\n\\n# Examples\\n\\n**Input**: \\\"Apple Unveils New iPhone Model, Featuring Advanced AI Features\\\" \\n**Output**: \\\"Technology\\\"\\n\\n**Input**: \\\"Global Stocks Mixed as Investors Await Central Bank Decisions\\\" \\n**Output**: \\\"Markets\\\"\\n\\n**Input**: \\\"War in Ukraine: Latest Updates on Negotiation Status\\\" \\n**Output**: \\\"World\\\"\\n\\n**Input**: \\\"Microsoft in Talks to Acquire Gaming Company for $2 Billion\\\" \\n**Output**: \\\"Business\\\"\\n\\n**Input**: \\\"Manchester United Secures Win in Premier League Football Match\\\" \\n**Output**: \\\"Sports\\\" \\n\\n# Notes\\n\\n- If the headline appears to fit into more than one category, choose the most dominant theme.\\n- Keywords or phrases such as \\\"stocks\\\", \\\"company acquisition\\\", \\\"match\\\", or technological brands can be good indicators for classification.\\n\"\n },\n {\n \"role\": \"user\",\n \"content\": \"{{item.input}}\"\n }\n ]\n },\n \"sampling_params\": {\n \"max_completions_tokens\": 2048\n },\n \"model\": \"gpt-6-astra\",\n \"source\": {\n \"type\": \"file_content\",\n \"content\": [\n {\n \"item\": {\n \"input\": \"Tech Company Launches Advanced Artificial Intelligence Platform\",\n \"ground_truth\": \"Technology\"\n }\n }\n ]\n }\n }\n)\nprint(run)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst run = await openai.evals.runs.create(\n \"eval_67e579652b548190aaa83ada4b125f47\",\n {\n name: \"gpt-6-astra\",\n data_source: {\n type: \"completions\",\n input_messages: {\n type: \"template\",\n template: [\n {\n role: \"developer\",\n content: \"Categorize a given news headline into one of the following topics: Technology, Markets, World, Business, or Sports.\\n\\n# Steps\\n\\n1. Analyze the content of the news headline to understand its primary focus.\\n2. Extract the subject matter, identifying any key indicators or keywords.\\n3. Use the identified indicators to determine the most suitable category out of the five options: Technology, Markets, World, Business, or Sports.\\n4. Ensure only one category is selected per headline.\\n\\n# Output Format\\n\\nRespond with the chosen category as a single word. For instance: \\\"Technology\\\", \\\"Markets\\\", \\\"World\\\", \\\"Business\\\", or \\\"Sports\\\".\\n\\n# Examples\\n\\n**Input**: \\\"Apple Unveils New iPhone Model, Featuring Advanced AI Features\\\" \\n**Output**: \\\"Technology\\\"\\n\\n**Input**: \\\"Global Stocks Mixed as Investors Await Central Bank Decisions\\\" \\n**Output**: \\\"Markets\\\"\\n\\n**Input**: \\\"War in Ukraine: Latest Updates on Negotiation Status\\\" \\n**Output**: \\\"World\\\"\\n\\n**Input**: \\\"Microsoft in Talks to Acquire Gaming Company for $2 Billion\\\" \\n**Output**: \\\"Business\\\"\\n\\n**Input**: \\\"Manchester United Secures Win in Premier League Football Match\\\" \\n**Output**: \\\"Sports\\\" \\n\\n# Notes\\n\\n- If the headline appears to fit into more than one category, choose the most dominant theme.\\n- Keywords or phrases such as \\\"stocks\\\", \\\"company acquisition\\\", \\\"match\\\", or technological brands can be good indicators for classification.\\n\"\n },\n {\n role: \"user\",\n content: \"{{item.input}}\"\n }\n ]\n },\n sampling_params: {\n max_completions_tokens: 2048\n },\n model: \"gpt-6-astra\",\n source: {\n type: \"file_content\",\n content: [\n {\n item: {\n input: \"Tech Company Launches Advanced Artificial Intelligence Platform\",\n ground_truth: \"Technology\"\n }\n }\n ]\n }\n }\n }\n);\nconsole.log(run);\n" + }, + "response": "{\n \"object\": \"eval.run\",\n \"id\": \"evalrun_67e57965b480819094274e3a32235e4c\",\n \"eval_id\": \"eval_67e579652b548190aaa83ada4b125f47\",\n \"report_url\": \"https://platform.openai.com/evaluations/eval_67e579652b548190aaa83ada4b125f47&run_id=evalrun_67e57965b480819094274e3a32235e4c\",\n \"status\": \"queued\",\n \"model\": \"gpt-6-astra\",\n \"name\": \"gpt-6-astra\",\n \"created_at\": 1743092069,\n \"result_counts\": {\n \"total\": 0,\n \"errored\": 0,\n \"failed\": 0,\n \"passed\": 0\n },\n \"per_model_usage\": null,\n \"per_testing_criteria_results\": null,\n \"data_source\": {\n \"type\": \"completions\",\n \"source\": {\n \"type\": \"file_content\",\n \"content\": [\n {\n \"item\": {\n \"input\": \"Tech Company Launches Advanced Artificial Intelligence Platform\",\n \"ground_truth\": \"Technology\"\n }\n }\n ]\n },\n \"input_messages\": {\n \"type\": \"template\",\n \"template\": [\n {\n \"type\": \"message\",\n \"role\": \"developer\",\n \"content\": {\n \"type\": \"input_text\",\n \"text\": \"Categorize a given news headline into one of the following topics: Technology, Markets, World, Business, or Sports.\\n\\n# Steps\\n\\n1. Analyze the content of the news headline to understand its primary focus.\\n2. Extract the subject matter, identifying any key indicators or keywords.\\n3. Use the identified indicators to determine the most suitable category out of the five options: Technology, Markets, World, Business, or Sports.\\n4. Ensure only one category is selected per headline.\\n\\n# Output Format\\n\\nRespond with the chosen category as a single word. For instance: \\\"Technology\\\", \\\"Markets\\\", \\\"World\\\", \\\"Business\\\", or \\\"Sports\\\".\\n\\n# Examples\\n\\n**Input**: \\\"Apple Unveils New iPhone Model, Featuring Advanced AI Features\\\" \\n**Output**: \\\"Technology\\\"\\n\\n**Input**: \\\"Global Stocks Mixed as Investors Await Central Bank Decisions\\\" \\n**Output**: \\\"Markets\\\"\\n\\n**Input**: \\\"War in Ukraine: Latest Updates on Negotiation Status\\\" \\n**Output**: \\\"World\\\"\\n\\n**Input**: \\\"Microsoft in Talks to Acquire Gaming Company for $2 Billion\\\" \\n**Output**: \\\"Business\\\"\\n\\n**Input**: \\\"Manchester United Secures Win in Premier League Football Match\\\" \\n**Output**: \\\"Sports\\\" \\n\\n# Notes\\n\\n- If the headline appears to fit into more than one category, choose the most dominant theme.\\n- Keywords or phrases such as \\\"stocks\\\", \\\"company acquisition\\\", \\\"match\\\", or technological brands can be good indicators for classification.\\n\"\n }\n },\n {\n \"type\": \"message\",\n \"role\": \"user\",\n \"content\": {\n \"type\": \"input_text\",\n \"text\": \"{{item.input}}\"\n }\n }\n ]\n },\n \"model\": \"gpt-6-astra\",\n \"sampling_params\": {\n \"max_completions_tokens\": 2048\n }\n },\n \"error\": null,\n \"metadata\": {}\n}\n" + } + } + } + }, + "/evals/{eval_id}/runs/{run_id}": { + "get": { + "operationId": "getEvalRun", + "tags": [ + "Evals" + ], + "summary": "Get an evaluation run by ID.\n", + "parameters": [ + { + "name": "eval_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the evaluation to retrieve runs for." + }, + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the run to retrieve." + } + ], + "responses": { + "200": { + "description": "The evaluation run", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvalRun" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Get an eval run", + "group": "evals", + "path": "get", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/evals/eval_67abd54d9b0081909a86353f6fb9317a/runs/evalrun_67abd54d60ec8190832b46859da808f7 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nrun = client.evals.runs.retrieve(\n \"eval_67abd54d9b0081909a86353f6fb9317a\",\n \"evalrun_67abd54d60ec8190832b46859da808f7\"\n)\nprint(run)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst run = await openai.evals.runs.retrieve(\n \"evalrun_67abd54d60ec8190832b46859da808f7\",\n { eval_id: \"eval_67abd54d9b0081909a86353f6fb9317a\" }\n);\nconsole.log(run);\n" + }, + "response": "{\n \"object\": \"eval.run\",\n \"id\": \"evalrun_67abd54d60ec8190832b46859da808f7\",\n \"eval_id\": \"eval_67abd54d9b0081909a86353f6fb9317a\",\n \"report_url\": \"https://platform.openai.com/evaluations/eval_67abd54d9b0081909a86353f6fb9317a?run_id=evalrun_67abd54d60ec8190832b46859da808f7\",\n \"status\": \"queued\",\n \"model\": \"gpt-6-astra\",\n \"name\": \"gpt-6-astra\",\n \"created_at\": 1743092069,\n \"result_counts\": {\n \"total\": 0,\n \"errored\": 0,\n \"failed\": 0,\n \"passed\": 0\n },\n \"per_model_usage\": null,\n \"per_testing_criteria_results\": null,\n \"data_source\": {\n \"type\": \"completions\",\n \"source\": {\n \"type\": \"file_content\",\n \"content\": [\n {\n \"item\": {\n \"input\": \"Tech Company Launches Advanced Artificial Intelligence Platform\",\n \"ground_truth\": \"Technology\"\n }\n },\n {\n \"item\": {\n \"input\": \"Central Bank Increases Interest Rates Amid Inflation Concerns\",\n \"ground_truth\": \"Markets\"\n }\n },\n {\n \"item\": {\n \"input\": \"International Summit Addresses Climate Change Strategies\",\n \"ground_truth\": \"World\"\n }\n },\n {\n \"item\": {\n \"input\": \"Major Retailer Reports Record-Breaking Holiday Sales\",\n \"ground_truth\": \"Business\"\n }\n },\n {\n \"item\": {\n \"input\": \"National Team Qualifies for World Championship Finals\",\n \"ground_truth\": \"Sports\"\n }\n },\n {\n \"item\": {\n \"input\": \"Stock Markets Rally After Positive Economic Data Released\",\n \"ground_truth\": \"Markets\"\n }\n },\n {\n \"item\": {\n \"input\": \"Global Manufacturer Announces Merger with Competitor\",\n \"ground_truth\": \"Business\"\n }\n },\n {\n \"item\": {\n \"input\": \"Breakthrough in Renewable Energy Technology Unveiled\",\n \"ground_truth\": \"Technology\"\n }\n },\n {\n \"item\": {\n \"input\": \"World Leaders Sign Historic Climate Agreement\",\n \"ground_truth\": \"World\"\n }\n },\n {\n \"item\": {\n \"input\": \"Professional Athlete Sets New Record in Championship Event\",\n \"ground_truth\": \"Sports\"\n }\n },\n {\n \"item\": {\n \"input\": \"Financial Institutions Adapt to New Regulatory Requirements\",\n \"ground_truth\": \"Business\"\n }\n },\n {\n \"item\": {\n \"input\": \"Tech Conference Showcases Advances in Artificial Intelligence\",\n \"ground_truth\": \"Technology\"\n }\n },\n {\n \"item\": {\n \"input\": \"Global Markets Respond to Oil Price Fluctuations\",\n \"ground_truth\": \"Markets\"\n }\n },\n {\n \"item\": {\n \"input\": \"International Cooperation Strengthened Through New Treaty\",\n \"ground_truth\": \"World\"\n }\n },\n {\n \"item\": {\n \"input\": \"Sports League Announces Revised Schedule for Upcoming Season\",\n \"ground_truth\": \"Sports\"\n }\n }\n ]\n },\n \"input_messages\": {\n \"type\": \"template\",\n \"template\": [\n {\n \"type\": \"message\",\n \"role\": \"developer\",\n \"content\": {\n \"type\": \"input_text\",\n \"text\": \"Categorize a given news headline into one of the following topics: Technology, Markets, World, Business, or Sports.\\n\\n# Steps\\n\\n1. Analyze the content of the news headline to understand its primary focus.\\n2. Extract the subject matter, identifying any key indicators or keywords.\\n3. Use the identified indicators to determine the most suitable category out of the five options: Technology, Markets, World, Business, or Sports.\\n4. Ensure only one category is selected per headline.\\n\\n# Output Format\\n\\nRespond with the chosen category as a single word. For instance: \\\"Technology\\\", \\\"Markets\\\", \\\"World\\\", \\\"Business\\\", or \\\"Sports\\\".\\n\\n# Examples\\n\\n**Input**: \\\"Apple Unveils New iPhone Model, Featuring Advanced AI Features\\\" \\n**Output**: \\\"Technology\\\"\\n\\n**Input**: \\\"Global Stocks Mixed as Investors Await Central Bank Decisions\\\" \\n**Output**: \\\"Markets\\\"\\n\\n**Input**: \\\"War in Ukraine: Latest Updates on Negotiation Status\\\" \\n**Output**: \\\"World\\\"\\n\\n**Input**: \\\"Microsoft in Talks to Acquire Gaming Company for $2 Billion\\\" \\n**Output**: \\\"Business\\\"\\n\\n**Input**: \\\"Manchester United Secures Win in Premier League Football Match\\\" \\n**Output**: \\\"Sports\\\" \\n\\n# Notes\\n\\n- If the headline appears to fit into more than one category, choose the most dominant theme.\\n- Keywords or phrases such as \\\"stocks\\\", \\\"company acquisition\\\", \\\"match\\\", or technological brands can be good indicators for classification.\\n\"\n }\n },\n {\n \"type\": \"message\",\n \"role\": \"user\",\n \"content\": {\n \"type\": \"input_text\",\n \"text\": \"{{item.input}}\"\n }\n }\n ]\n },\n \"model\": \"gpt-6-astra\",\n \"sampling_params\": {\n \"max_completions_tokens\": 2048\n }\n },\n \"error\": null,\n \"metadata\": {}\n}\n" + } + } + }, + "post": { + "operationId": "cancelEvalRun", + "tags": [ + "Evals" + ], + "summary": "Cancel an ongoing evaluation run.\n", + "parameters": [ + { + "name": "eval_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the evaluation whose run you want to cancel." + }, + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the run to cancel." + } + ], + "responses": { + "200": { + "description": "The canceled eval run object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvalRun" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Cancel eval run", + "group": "evals", + "path": "post", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/evals/eval_67abd54d9b0081909a86353f6fb9317a/runs/evalrun_67abd54d60ec8190832b46859da808f7/cancel \\\n -X POST \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ncanceled_run = client.evals.runs.cancel(\n \"eval_67abd54d9b0081909a86353f6fb9317a\",\n \"evalrun_67abd54d60ec8190832b46859da808f7\"\n)\nprint(canceled_run)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst canceledRun = await openai.evals.runs.cancel(\n \"evalrun_67abd54d60ec8190832b46859da808f7\",\n { eval_id: \"eval_67abd54d9b0081909a86353f6fb9317a\" }\n);\nconsole.log(canceledRun);\n" + }, + "response": "{\n \"object\": \"eval.run\",\n \"id\": \"evalrun_67abd54d60ec8190832b46859da808f7\",\n \"eval_id\": \"eval_67abd54d9b0081909a86353f6fb9317a\",\n \"report_url\": \"https://platform.openai.com/evaluations/eval_67abd54d9b0081909a86353f6fb9317a?run_id=evalrun_67abd54d60ec8190832b46859da808f7\",\n \"status\": \"canceled\",\n \"model\": \"gpt-6-astra\",\n \"name\": \"gpt-6-astra\",\n \"created_at\": 1743092069,\n \"result_counts\": {\n \"total\": 0,\n \"errored\": 0,\n \"failed\": 0,\n \"passed\": 0\n },\n \"per_model_usage\": null,\n \"per_testing_criteria_results\": null,\n \"data_source\": {\n \"type\": \"completions\",\n \"source\": {\n \"type\": \"file_content\",\n \"content\": [\n {\n \"item\": {\n \"input\": \"Tech Company Launches Advanced Artificial Intelligence Platform\",\n \"ground_truth\": \"Technology\"\n }\n },\n {\n \"item\": {\n \"input\": \"Central Bank Increases Interest Rates Amid Inflation Concerns\",\n \"ground_truth\": \"Markets\"\n }\n },\n {\n \"item\": {\n \"input\": \"International Summit Addresses Climate Change Strategies\",\n \"ground_truth\": \"World\"\n }\n },\n {\n \"item\": {\n \"input\": \"Major Retailer Reports Record-Breaking Holiday Sales\",\n \"ground_truth\": \"Business\"\n }\n },\n {\n \"item\": {\n \"input\": \"National Team Qualifies for World Championship Finals\",\n \"ground_truth\": \"Sports\"\n }\n },\n {\n \"item\": {\n \"input\": \"Stock Markets Rally After Positive Economic Data Released\",\n \"ground_truth\": \"Markets\"\n }\n },\n {\n \"item\": {\n \"input\": \"Global Manufacturer Announces Merger with Competitor\",\n \"ground_truth\": \"Business\"\n }\n },\n {\n \"item\": {\n \"input\": \"Breakthrough in Renewable Energy Technology Unveiled\",\n \"ground_truth\": \"Technology\"\n }\n },\n {\n \"item\": {\n \"input\": \"World Leaders Sign Historic Climate Agreement\",\n \"ground_truth\": \"World\"\n }\n },\n {\n \"item\": {\n \"input\": \"Professional Athlete Sets New Record in Championship Event\",\n \"ground_truth\": \"Sports\"\n }\n },\n {\n \"item\": {\n \"input\": \"Financial Institutions Adapt to New Regulatory Requirements\",\n \"ground_truth\": \"Business\"\n }\n },\n {\n \"item\": {\n \"input\": \"Tech Conference Showcases Advances in Artificial Intelligence\",\n \"ground_truth\": \"Technology\"\n }\n },\n {\n \"item\": {\n \"input\": \"Global Markets Respond to Oil Price Fluctuations\",\n \"ground_truth\": \"Markets\"\n }\n },\n {\n \"item\": {\n \"input\": \"International Cooperation Strengthened Through New Treaty\",\n \"ground_truth\": \"World\"\n }\n },\n {\n \"item\": {\n \"input\": \"Sports League Announces Revised Schedule for Upcoming Season\",\n \"ground_truth\": \"Sports\"\n }\n }\n ]\n },\n \"input_messages\": {\n \"type\": \"template\",\n \"template\": [\n {\n \"type\": \"message\",\n \"role\": \"developer\",\n \"content\": {\n \"type\": \"input_text\",\n \"text\": \"Categorize a given news headline into one of the following topics: Technology, Markets, World, Business, or Sports.\\n\\n# Steps\\n\\n1. Analyze the content of the news headline to understand its primary focus.\\n2. Extract the subject matter, identifying any key indicators or keywords.\\n3. Use the identified indicators to determine the most suitable category out of the five options: Technology, Markets, World, Business, or Sports.\\n4. Ensure only one category is selected per headline.\\n\\n# Output Format\\n\\nRespond with the chosen category as a single word. For instance: \\\"Technology\\\", \\\"Markets\\\", \\\"World\\\", \\\"Business\\\", or \\\"Sports\\\".\\n\\n# Examples\\n\\n**Input**: \\\"Apple Unveils New iPhone Model, Featuring Advanced AI Features\\\" \\n**Output**: \\\"Technology\\\"\\n\\n**Input**: \\\"Global Stocks Mixed as Investors Await Central Bank Decisions\\\" \\n**Output**: \\\"Markets\\\"\\n\\n**Input**: \\\"War in Ukraine: Latest Updates on Negotiation Status\\\" \\n**Output**: \\\"World\\\"\\n\\n**Input**: \\\"Microsoft in Talks to Acquire Gaming Company for $2 Billion\\\" \\n**Output**: \\\"Business\\\"\\n\\n**Input**: \\\"Manchester United Secures Win in Premier League Football Match\\\" \\n**Output**: \\\"Sports\\\" \\n\\n# Notes\\n\\n- If the headline appears to fit into more than one category, choose the most dominant theme.\\n- Keywords or phrases such as \\\"stocks\\\", \\\"company acquisition\\\", \\\"match\\\", or technological brands can be good indicators for classification.\\n\"\n }\n },\n {\n \"type\": \"message\",\n \"role\": \"user\",\n \"content\": {\n \"type\": \"input_text\",\n \"text\": \"{{item.input}}\"\n }\n }\n ]\n },\n \"model\": \"gpt-6-astra\",\n \"sampling_params\": {\n \"max_completions_tokens\": 2048\n }\n },\n \"error\": null,\n \"metadata\": {}\n}\n" + } + } + }, + "delete": { + "operationId": "deleteEvalRun", + "tags": [ + "Evals" + ], + "summary": "Delete an eval run.\n", + "parameters": [ + { + "name": "eval_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the evaluation to delete the run from." + }, + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the run to delete." + } + ], + "responses": { + "200": { + "description": "Successfully deleted the eval run", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "object": { + "type": "string", + "example": "eval.run.deleted" + }, + "deleted": { + "type": "boolean", + "example": true + }, + "run_id": { + "type": "string", + "example": "evalrun_677469f564d48190807532a852da3afb" + } + } + } + } + } + }, + "404": { + "description": "Run not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Delete eval run", + "group": "evals", + "path": "delete", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/evals/eval_123abc/runs/evalrun_abc456 \\\n -X DELETE \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ndeleted = client.evals.runs.delete(\n \"eval_123abc\",\n \"evalrun_abc456\"\n)\nprint(deleted)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst deleted = await openai.evals.runs.delete(\n \"eval_123abc\",\n \"evalrun_abc456\"\n);\nconsole.log(deleted);\n" + }, + "response": "{\n \"object\": \"eval.run.deleted\",\n \"deleted\": true,\n \"run_id\": \"evalrun_abc456\"\n}\n" + } + } + } + }, + "/evals/{eval_id}/runs/{run_id}/output_items": { + "get": { + "operationId": "getEvalRunOutputItems", + "tags": [ + "Evals" + ], + "summary": "Get a list of output items for an evaluation run.\n", + "parameters": [ + { + "name": "eval_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the evaluation to retrieve runs for." + }, + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the run to retrieve output items for." + }, + { + "name": "after", + "in": "query", + "description": "Identifier for the last output item from the previous pagination request.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "Number of output items to retrieve.", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "status", + "in": "query", + "description": "Filter output items by status. Use `failed` to filter by failed output\nitems or `pass` to filter by passed output items.\n", + "required": false, + "schema": { + "type": "string", + "enum": [ + "fail", + "pass" + ] + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for output items by timestamp. Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + } + ], + "responses": { + "200": { + "description": "A list of output items for the evaluation run", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvalRunOutputItemList" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Get eval run output items", + "group": "evals", + "path": "get", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/evals/egroup_67abd54d9b0081909a86353f6fb9317a/runs/erun_67abd54d60ec8190832b46859da808f7/output_items \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\noutput_items = client.evals.runs.output_items.list(\n \"egroup_67abd54d9b0081909a86353f6fb9317a\",\n \"erun_67abd54d60ec8190832b46859da808f7\"\n)\nprint(output_items)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst outputItems = await openai.evals.runs.outputItems.list(\n \"egroup_67abd54d9b0081909a86353f6fb9317a\",\n \"erun_67abd54d60ec8190832b46859da808f7\"\n);\nconsole.log(outputItems);\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"eval.run.output_item\",\n \"id\": \"outputitem_67e5796c28e081909917bf79f6e6214d\",\n \"created_at\": 1743092076,\n \"run_id\": \"evalrun_67abd54d60ec8190832b46859da808f7\",\n \"eval_id\": \"eval_67abd54d9b0081909a86353f6fb9317a\",\n \"status\": \"pass\",\n \"datasource_item_id\": 5,\n \"datasource_item\": {\n \"input\": \"Stock Markets Rally After Positive Economic Data Released\",\n \"ground_truth\": \"Markets\"\n },\n \"results\": [\n {\n \"name\": \"String check-a2486074-d803-4445-b431-ad2262e85d47\",\n \"sample\": null,\n \"passed\": true,\n \"score\": 1.0\n }\n ],\n \"sample\": {\n \"input\": [\n {\n \"role\": \"developer\",\n \"content\": \"Categorize a given news headline into one of the following topics: Technology, Markets, World, Business, or Sports.\\n\\n# Steps\\n\\n1. Analyze the content of the news headline to understand its primary focus.\\n2. Extract the subject matter, identifying any key indicators or keywords.\\n3. Use the identified indicators to determine the most suitable category out of the five options: Technology, Markets, World, Business, or Sports.\\n4. Ensure only one category is selected per headline.\\n\\n# Output Format\\n\\nRespond with the chosen category as a single word. For instance: \\\"Technology\\\", \\\"Markets\\\", \\\"World\\\", \\\"Business\\\", or \\\"Sports\\\".\\n\\n# Examples\\n\\n**Input**: \\\"Apple Unveils New iPhone Model, Featuring Advanced AI Features\\\" \\n**Output**: \\\"Technology\\\"\\n\\n**Input**: \\\"Global Stocks Mixed as Investors Await Central Bank Decisions\\\" \\n**Output**: \\\"Markets\\\"\\n\\n**Input**: \\\"War in Ukraine: Latest Updates on Negotiation Status\\\" \\n**Output**: \\\"World\\\"\\n\\n**Input**: \\\"Microsoft in Talks to Acquire Gaming Company for $2 Billion\\\" \\n**Output**: \\\"Business\\\"\\n\\n**Input**: \\\"Manchester United Secures Win in Premier League Football Match\\\" \\n**Output**: \\\"Sports\\\" \\n\\n# Notes\\n\\n- If the headline appears to fit into more than one category, choose the most dominant theme.\\n- Keywords or phrases such as \\\"stocks\\\", \\\"company acquisition\\\", \\\"match\\\", or technological brands can be good indicators for classification.\\n\",\n \"tool_call_id\": null,\n \"tool_calls\": null,\n \"function_call\": null\n },\n {\n \"role\": \"user\",\n \"content\": \"Stock Markets Rally After Positive Economic Data Released\",\n \"tool_call_id\": null,\n \"tool_calls\": null,\n \"function_call\": null\n }\n ],\n \"output\": [\n {\n \"role\": \"assistant\",\n \"content\": \"Markets\",\n \"tool_call_id\": null,\n \"tool_calls\": null,\n \"function_call\": null\n }\n ],\n \"finish_reason\": \"stop\",\n \"model\": \"gpt-6-astra\",\n \"usage\": {\n \"total_tokens\": 325,\n \"completion_tokens\": 2,\n \"prompt_tokens\": 323,\n \"cached_tokens\": 0\n },\n \"error\": null,\n \"temperature\": 1.0,\n \"max_completion_tokens\": 2048,\n \"top_p\": 1.0,\n \"seed\": 42\n }\n }\n ],\n \"first_id\": \"outputitem_67e5796c28e081909917bf79f6e6214d\",\n \"last_id\": \"outputitem_67e5796c28e081909917bf79f6e6214d\",\n \"has_more\": true\n}\n" + } + } + } + }, + "/evals/{eval_id}/runs/{run_id}/output_items/{output_item_id}": { + "get": { + "operationId": "getEvalRunOutputItem", + "tags": [ + "Evals" + ], + "summary": "Get an evaluation run output item by ID.\n", + "parameters": [ + { + "name": "eval_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the evaluation to retrieve runs for." + }, + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the run to retrieve." + }, + { + "name": "output_item_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the output item to retrieve." + } + ], + "responses": { + "200": { + "description": "The evaluation run output item", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvalRunOutputItem" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Get an output item of an eval run", + "group": "evals", + "path": "get", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/evals/eval_67abd54d9b0081909a86353f6fb9317a/runs/evalrun_67abd54d60ec8190832b46859da808f7/output_items/outputitem_67abd55eb6548190bb580745d5644a33 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\noutput_item = client.evals.runs.output_items.retrieve(\n \"eval_67abd54d9b0081909a86353f6fb9317a\",\n \"evalrun_67abd54d60ec8190832b46859da808f7\",\n \"outputitem_67abd55eb6548190bb580745d5644a33\"\n)\nprint(output_item)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst outputItem = await openai.evals.runs.outputItems.retrieve(\n \"outputitem_67abd55eb6548190bb580745d5644a33\",\n {\n eval_id: \"eval_67abd54d9b0081909a86353f6fb9317a\",\n run_id: \"evalrun_67abd54d60ec8190832b46859da808f7\",\n }\n);\nconsole.log(outputItem);\n" + }, + "response": "{\n \"object\": \"eval.run.output_item\",\n \"id\": \"outputitem_67e5796c28e081909917bf79f6e6214d\",\n \"created_at\": 1743092076,\n \"run_id\": \"evalrun_67abd54d60ec8190832b46859da808f7\",\n \"eval_id\": \"eval_67abd54d9b0081909a86353f6fb9317a\",\n \"status\": \"pass\",\n \"datasource_item_id\": 5,\n \"datasource_item\": {\n \"input\": \"Stock Markets Rally After Positive Economic Data Released\",\n \"ground_truth\": \"Markets\"\n },\n \"results\": [\n {\n \"name\": \"String check-a2486074-d803-4445-b431-ad2262e85d47\",\n \"sample\": null,\n \"passed\": true,\n \"score\": 1.0\n }\n ],\n \"sample\": {\n \"input\": [\n {\n \"role\": \"developer\",\n \"content\": \"Categorize a given news headline into one of the following topics: Technology, Markets, World, Business, or Sports.\\n\\n# Steps\\n\\n1. Analyze the content of the news headline to understand its primary focus.\\n2. Extract the subject matter, identifying any key indicators or keywords.\\n3. Use the identified indicators to determine the most suitable category out of the five options: Technology, Markets, World, Business, or Sports.\\n4. Ensure only one category is selected per headline.\\n\\n# Output Format\\n\\nRespond with the chosen category as a single word. For instance: \\\"Technology\\\", \\\"Markets\\\", \\\"World\\\", \\\"Business\\\", or \\\"Sports\\\".\\n\\n# Examples\\n\\n**Input**: \\\"Apple Unveils New iPhone Model, Featuring Advanced AI Features\\\" \\n**Output**: \\\"Technology\\\"\\n\\n**Input**: \\\"Global Stocks Mixed as Investors Await Central Bank Decisions\\\" \\n**Output**: \\\"Markets\\\"\\n\\n**Input**: \\\"War in Ukraine: Latest Updates on Negotiation Status\\\" \\n**Output**: \\\"World\\\"\\n\\n**Input**: \\\"Microsoft in Talks to Acquire Gaming Company for $2 Billion\\\" \\n**Output**: \\\"Business\\\"\\n\\n**Input**: \\\"Manchester United Secures Win in Premier League Football Match\\\" \\n**Output**: \\\"Sports\\\" \\n\\n# Notes\\n\\n- If the headline appears to fit into more than one category, choose the most dominant theme.\\n- Keywords or phrases such as \\\"stocks\\\", \\\"company acquisition\\\", \\\"match\\\", or technological brands can be good indicators for classification.\\n\",\n \"tool_call_id\": null,\n \"tool_calls\": null,\n \"function_call\": null\n },\n {\n \"role\": \"user\",\n \"content\": \"Stock Markets Rally After Positive Economic Data Released\",\n \"tool_call_id\": null,\n \"tool_calls\": null,\n \"function_call\": null\n }\n ],\n \"output\": [\n {\n \"role\": \"assistant\",\n \"content\": \"Markets\",\n \"tool_call_id\": null,\n \"tool_calls\": null,\n \"function_call\": null\n }\n ],\n \"finish_reason\": \"stop\",\n \"model\": \"gpt-6-astra\",\n \"usage\": {\n \"total_tokens\": 325,\n \"completion_tokens\": 2,\n \"prompt_tokens\": 323,\n \"cached_tokens\": 0\n },\n \"error\": null,\n \"temperature\": 1.0,\n \"max_completion_tokens\": 2048,\n \"top_p\": 1.0,\n \"seed\": 42\n }\n}\n" + } + } + } + }, + "/files": { + "get": { + "operationId": "listFiles", + "tags": [ + "Files" + ], + "summary": "Returns a list of files.", + "parameters": [ + { + "in": "query", + "name": "purpose", + "required": false, + "schema": { + "type": "string" + }, + "description": "Only return files with the given purpose." + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 10,000, and the default is 10,000.\n", + "required": false, + "schema": { + "type": "integer", + "default": 10000 + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "schema": { + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListFilesResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List files", + "group": "files", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/files \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.files.list()\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const list = await openai.files.list();\n\n for await (const file of list) {\n console.log(file);\n }\n}\n\nmain();" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"file-abc123\",\n \"object\": \"file\",\n \"bytes\": 175,\n \"created_at\": 1613677385,\n \"expires_at\": 1677614202,\n \"filename\": \"salesOverview.pdf\",\n \"purpose\": \"assistants\",\n },\n {\n \"id\": \"file-abc456\",\n \"object\": \"file\",\n \"bytes\": 140,\n \"created_at\": 1613779121,\n \"expires_at\": 1677614202,\n \"filename\": \"puppy.jsonl\",\n \"purpose\": \"fine-tune\",\n }\n ],\n \"first_id\": \"file-abc123\",\n \"last_id\": \"file-abc456\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "operationId": "createFile", + "tags": [ + "Files" + ], + "summary": "Upload a file that can be used across various endpoints. Individual files\ncan be up to 512 MB, and each project can store up to 2.5 TB of files in\ntotal. There is no organization-wide storage limit. Uploads to this\nendpoint are rate-limited to 1,000 requests per minute per authenticated\nuser.\n\n- The Assistants API supports files up to 2 million tokens and of specific\n file types. See the [Assistants Tools guide](https://developers.openai.com/api/docs/guides/tools) for\n details.\n- The Fine-tuning API only supports `.jsonl` files. The input also has\n certain required formats for fine-tuning\n [chat](https://developers.openai.com/api/docs/guides/supervised-fine-tuning#formatting-your-data) or\n [completions](https://developers.openai.com/api/docs/guides/supervised-fine-tuning#formatting-your-data) models.\n- The Batch API only supports `.jsonl` files up to 200 MB in size. The input\n also has a specific required\n [format](https://developers.openai.com/api/docs/guides/batch#1-prepare-your-batch-file).\n- For Retrieval or `file_search` ingestion, upload files here first. If\n you need to attach multiple uploaded files to the same vector store, use\n [`/vector_stores/{vector_store_id}/file_batches`](https://developers.openai.com/api/reference/resources/vector_stores/subresources/file_batches/methods/create)\n instead of attaching them one by one. Vector store attachment has separate\n limits from file upload, including 2,000 attached files per minute per\n organization.\n\nPlease [contact us](https://help.openai.com/) if you need to increase these\nstorage limits.\n", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateFileRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OpenAIFile" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Upload file", + "group": "files", + "description": "Uploads a file for later use across OpenAI APIs. Uploads to this endpoint are rate-limited to 1,000 requests per minute per authenticated user. For Retrieval or `file_search` ingestion, upload files here first. If you need to attach multiple uploaded files to the same vector store, use vector store file batches instead of attaching them one by one.\n", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/files \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -F purpose=\"fine-tune\" \\\n -F file=\"@mydata.jsonl\"\n -F expires_after[anchor]=\"created_at\"\n -F expires_after[seconds]=2592000\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.files.create(\n file=open(\"mydata.jsonl\", \"rb\"),\n purpose=\"fine-tune\",\n expires_after={\n \"anchor\": \"created_at\",\n \"seconds\": 2592000\n }\n)\n", + "javascript": "import fs from \"fs\";\nimport OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const file = await openai.files.create({\n file: fs.createReadStream(\"mydata.jsonl\"),\n purpose: \"fine-tune\",\n expires_after: {\n anchor: \"created_at\",\n seconds: 2592000\n }\n });\n\n console.log(file);\n}\n\nmain();" + }, + "response": "{\n \"id\": \"file-abc123\",\n \"object\": \"file\",\n \"bytes\": 120000,\n \"created_at\": 1677610602,\n \"expires_at\": 1677614202,\n \"filename\": \"mydata.jsonl\",\n \"purpose\": \"fine-tune\",\n}\n" + } + } + } + }, + "/files/{file_id}": { + "delete": { + "operationId": "deleteFile", + "tags": [ + "Files" + ], + "summary": "Delete a file and remove it from all vector stores.", + "parameters": [ + { + "in": "path", + "name": "file_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the file to use for this request." + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteFileResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete file", + "group": "files", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/files/file-abc123 \\\n -X DELETE \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.files.delete(\"file-abc123\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const file = await openai.files.delete(\"file-abc123\");\n\n console.log(file);\n}\n\nmain();" + }, + "response": "{\n \"id\": \"file-abc123\",\n \"object\": \"file\",\n \"deleted\": true\n}\n" + } + } + }, + "get": { + "operationId": "retrieveFile", + "tags": [ + "Files" + ], + "summary": "Returns information about a specific file.", + "parameters": [ + { + "in": "path", + "name": "file_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the file to use for this request." + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OpenAIFile" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve file", + "group": "files", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/files/file-abc123 \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.files.retrieve(\"file-abc123\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const file = await openai.files.retrieve(\"file-abc123\");\n\n console.log(file);\n}\n\nmain();" + }, + "response": "{\n \"id\": \"file-abc123\",\n \"object\": \"file\",\n \"bytes\": 120000,\n \"created_at\": 1677610602,\n \"expires_at\": 1677614202,\n \"filename\": \"mydata.jsonl\",\n \"purpose\": \"fine-tune\",\n}\n" + } + } + } + }, + "/files/{file_id}/content": { + "get": { + "operationId": "downloadFile", + "tags": [ + "Files" + ], + "summary": "Returns a response containing the contents of the specified file.", + "parameters": [ + { + "in": "path", + "name": "file_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the file to use for this request." + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve file content", + "group": "files", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/files/file-abc123/content \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" > file.jsonl\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\ncontent = client.files.content(\"file-abc123\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const response = await openai.files.content(\"file-abc123\");\n const content = await response.text();\n\n console.log(content);\n}\n\nmain();\n" + } + } + } + } + }, + "/fine_tuning/alpha/graders/run": { + "post": { + "operationId": "runGrader", + "tags": [ + "Fine-tuning" + ], + "summary": "Run a grader.\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunGraderRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunGraderResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Run grader", + "beta": true, + "group": "graders", + "examples": [ + { + "title": "Score text alignment", + "request": { + "curl": "curl -X POST https://api.openai.com/v1/fine_tuning/alpha/graders/run \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"grader\": {\n \"type\": \"score_model\",\n \"name\": \"Example score model grader\",\n \"input\": [\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"type\": \"input_text\",\n \"text\": \"Score how close the reference answer is to the model answer on a 0-1 scale. Return only the score.\\n\\nReference answer: {{item.reference_answer}}\\n\\nModel answer: {{sample.output_text}}\"\n }\n ]\n }\n ],\n \"model\": \"gpt-5-mini\",\n \"sampling_params\": {\n \"temperature\": 1,\n \"top_p\": 1,\n \"seed\": 42\n }\n },\n \"item\": {\n \"reference_answer\": \"fuzzy wuzzy was a bear\"\n },\n \"model_sample\": \"fuzzy wuzzy was a bear\"\n }'\n", + "python": "from openai import OpenAI\n\nclient = OpenAI()\nresult = client.fine_tuning.alpha.graders.run(\n grader={\n \"type\": \"score_model\",\n \"name\": \"Example score model grader\",\n \"input\": [\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"type\": \"input_text\",\n \"text\": \"Score how close the reference answer is to the model answer on a 0-1 scale. Return only the score.\\n\\nReference answer: {{item.reference_answer}}\\n\\nModel answer: {{sample.output_text}}\",\n }\n ],\n }\n ],\n \"model\": \"gpt-5-mini\",\n \"sampling_params\": {\"temperature\": 1, \"top_p\": 1, \"seed\": 42},\n },\n item={\"reference_answer\": \"fuzzy wuzzy was a bear\"},\n model_sample=\"fuzzy wuzzy was a bear\",\n)\nprint(result)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nconst result = await openai.fineTuning.alpha.graders.run({\n grader: {\n type: \"score_model\",\n name: \"Example score model grader\",\n input: [\n {\n role: \"user\",\n content: [\n {\n type: \"input_text\",\n text: \"Score how close the reference answer is to the model answer on a 0-1 scale. Return only the score.\\n\\nReference answer: {{item.reference_answer}}\\n\\nModel answer: {{sample.output_text}}\",\n },\n ],\n },\n ],\n model: \"gpt-5-mini\",\n sampling_params: { temperature: 1, top_p: 1, seed: 42 },\n },\n item: { reference_answer: \"fuzzy wuzzy was a bear\" },\n model_sample: \"fuzzy wuzzy was a bear\",\n});\nconsole.log(result);\n" + }, + "response": "{\n \"reward\": 1.0,\n \"metadata\": {\n \"name\": \"Example score model grader\",\n \"type\": \"score_model\",\n \"errors\": {\n \"formula_parse_error\": false,\n \"sample_parse_error\": false,\n \"truncated_observation_error\": false,\n \"unresponsive_reward_error\": false,\n \"invalid_variable_error\": false,\n \"other_error\": false,\n \"python_grader_server_error\": false,\n \"python_grader_server_error_type\": null,\n \"python_grader_runtime_error\": false,\n \"python_grader_runtime_error_details\": null,\n \"model_grader_server_error\": false,\n \"model_grader_refusal_error\": false,\n \"model_grader_parse_error\": false,\n \"model_grader_server_error_details\": null\n },\n \"execution_time\": 4.365238428115845,\n \"scores\": {},\n \"token_usage\": {\n \"prompt_tokens\": 190,\n \"total_tokens\": 324,\n \"completion_tokens\": 134,\n \"cached_tokens\": 0\n },\n \"sampled_model_name\": \"gpt-5-mini\"\n },\n \"sub_rewards\": {},\n \"model_grader_token_usage_per_model\": {\n \"gpt-5-mini\": {\n \"prompt_tokens\": 190,\n \"total_tokens\": 324,\n \"completion_tokens\": 134,\n \"cached_tokens\": 0\n }\n }\n}\n" + }, + { + "title": "Score an image caption", + "request": { + "curl": "curl -X POST https://api.openai.com/v1/fine_tuning/alpha/graders/run \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"grader\": {\n \"type\": \"score_model\",\n \"name\": \"Image caption grader\",\n \"input\": [\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"type\": \"input_text\",\n \"text\": \"Score how well the provided caption matches the image on a 0-1 scale. Only return the score.\\n\\nCaption: {{sample.output_text}}\"\n },\n {\n \"type\": \"input_image\",\n \"image_url\": \"https://example.com/dog-catching-ball.png\",\n \"file_id\": null,\n \"detail\": \"high\"\n }\n ]\n }\n ],\n \"model\": \"gpt-5-mini\",\n \"sampling_params\": {\n \"temperature\": 0.2\n }\n },\n \"item\": {\n \"expected_caption\": \"A golden retriever jumps to catch a tennis ball\"\n },\n \"model_sample\": \"A dog leaps to grab a tennis ball mid-air\"\n }'\n" + } + }, + { + "title": "Score an audio response", + "request": { + "curl": "curl -X POST https://api.openai.com/v1/fine_tuning/alpha/graders/run \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"grader\": {\n \"type\": \"score_model\",\n \"name\": \"Audio clarity grader\",\n \"input\": [\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"type\": \"input_text\",\n \"text\": \"Listen to the clip and return a confidence score from 0 to 1 that the speaker said: {{item.target_phrase}}\"\n },\n {\n \"type\": \"input_audio\",\n \"input_audio\": {\n \"data\": \"{{item.audio_clip_b64}}\",\n \"format\": \"mp3\"\n }\n }\n ]\n }\n ],\n \"model\": \"gpt-audio\",\n \"sampling_params\": {\n \"temperature\": 0.2,\n \"top_p\": 1,\n \"seed\": 123\n }\n },\n \"item\": {\n \"target_phrase\": \"Please deliver the package on Tuesday\",\n \"audio_clip_b64\": \"\"\n },\n \"model_sample\": \"Please deliver the package on Tuesday\"\n }'\n" + } + } + ] + } + } + }, + "/fine_tuning/alpha/graders/validate": { + "post": { + "operationId": "validateGrader", + "tags": [ + "Fine-tuning" + ], + "summary": "Validate a grader.\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidateGraderRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidateGraderResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Validate grader", + "beta": true, + "group": "graders", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/alpha/graders/validate \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"grader\": {\n \"type\": \"string_check\",\n \"name\": \"Example string check grader\",\n \"input\": \"{{sample.output_text}}\",\n \"reference\": \"{{item.label}}\",\n \"operation\": \"eq\"\n }\n }'\n" + }, + "response": "{\n \"grader\": {\n \"type\": \"string_check\",\n \"name\": \"Example string check grader\",\n \"input\": \"{{sample.output_text}}\",\n \"reference\": \"{{item.label}}\",\n \"operation\": \"eq\"\n }\n}\n" + } + } + } + }, + "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions": { + "get": { + "operationId": "listFineTuningCheckpointPermissions", + "tags": [ + "Fine-tuning" + ], + "summary": "**NOTE:** This endpoint requires an [admin API key](https://developers.openai.com/api/reference/resources/admin/subresources/organization/subresources/admin_api_keys).\n\nOrganization owners can use this endpoint to view all permissions for a fine-tuned model checkpoint.\n", + "parameters": [ + { + "in": "path", + "name": "fine_tuned_model_checkpoint", + "required": true, + "schema": { + "type": "string", + "example": "ft-AF1WoRqd3aJAHsqc9NY7iL8F" + }, + "description": "The ID of the fine-tuned model checkpoint to get permissions for.\n" + }, + { + "name": "project_id", + "in": "query", + "description": "The ID of the project to get permissions for.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "after", + "in": "query", + "description": "Identifier for the last permission ID from the previous pagination request.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "Number of permissions to retrieve.", + "required": false, + "schema": { + "type": "integer", + "default": 10 + } + }, + { + "name": "order", + "in": "query", + "description": "The order in which to retrieve permissions.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "ascending", + "descending" + ], + "default": "descending" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListFineTuningCheckpointPermissionResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List checkpoint permissions", + "group": "fine-tuning", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/checkpoints/ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd/permissions \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"checkpoint.permission\",\n \"id\": \"cp_zc4Q7MP6XxulcVzj4MZdwsAB\",\n \"created_at\": 1721764867,\n \"project_id\": \"proj_abGMw1llN8IrBb6SvvY5A1iH\"\n },\n {\n \"object\": \"checkpoint.permission\",\n \"id\": \"cp_enQCFmOTGj3syEpYVhBRLTSy\",\n \"created_at\": 1721764800,\n \"project_id\": \"proj_iqGMw1llN8IrBb6SvvY5A1oF\"\n },\n ],\n \"first_id\": \"cp_zc4Q7MP6XxulcVzj4MZdwsAB\",\n \"last_id\": \"cp_enQCFmOTGj3syEpYVhBRLTSy\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "operationId": "createFineTuningCheckpointPermission", + "tags": [ + "Fine-tuning" + ], + "summary": "**NOTE:** Calling this endpoint requires an [admin API key](https://developers.openai.com/api/reference/resources/admin/subresources/organization/subresources/admin_api_keys).\n\nThis enables organization owners to share fine-tuned models with other projects in their organization.\n", + "parameters": [ + { + "in": "path", + "name": "fine_tuned_model_checkpoint", + "required": true, + "schema": { + "type": "string", + "example": "ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd" + }, + "description": "The ID of the fine-tuned model checkpoint to create a permission for.\n" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateFineTuningCheckpointPermissionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListFineTuningCheckpointPermissionResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create checkpoint permissions", + "group": "fine-tuning", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/checkpoints/ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd/permissions \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n -d '{\"project_ids\": [\"proj_abGMw1llN8IrBb6SvvY5A1iH\"]}'\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"checkpoint.permission\",\n \"id\": \"cp_zc4Q7MP6XxulcVzj4MZdwsAB\",\n \"created_at\": 1721764867,\n \"project_id\": \"proj_abGMw1llN8IrBb6SvvY5A1iH\"\n }\n ],\n \"first_id\": \"cp_zc4Q7MP6XxulcVzj4MZdwsAB\",\n \"last_id\": \"cp_zc4Q7MP6XxulcVzj4MZdwsAB\",\n \"has_more\": false\n}\n" + } + } + } + }, + "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions/{permission_id}": { + "delete": { + "operationId": "deleteFineTuningCheckpointPermission", + "tags": [ + "Fine-tuning" + ], + "summary": "**NOTE:** This endpoint requires an [admin API key](https://developers.openai.com/api/reference/resources/admin/subresources/organization/subresources/admin_api_keys).\n\nOrganization owners can use this endpoint to delete a permission for a fine-tuned model checkpoint.\n", + "parameters": [ + { + "in": "path", + "name": "fine_tuned_model_checkpoint", + "required": true, + "schema": { + "type": "string", + "example": "ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd" + }, + "description": "The ID of the fine-tuned model checkpoint to delete a permission for.\n" + }, + { + "in": "path", + "name": "permission_id", + "required": true, + "schema": { + "type": "string", + "example": "cp_zc4Q7MP6XxulcVzj4MZdwsAB" + }, + "description": "The ID of the fine-tuned model checkpoint permission to delete.\n" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteFineTuningCheckpointPermissionResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete checkpoint permission", + "group": "fine-tuning", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/checkpoints/ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd/permissions/cp_zc4Q7MP6XxulcVzj4MZdwsAB \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n" + }, + "response": "{\n \"object\": \"checkpoint.permission\",\n \"id\": \"cp_zc4Q7MP6XxulcVzj4MZdwsAB\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/fine_tuning/jobs": { + "post": { + "operationId": "createFineTuningJob", + "tags": [ + "Fine-tuning" + ], + "summary": "Creates a fine-tuning job which begins the process of creating a new model from a given dataset.\n\nResponse includes details of the enqueued job including job status and the name of the fine-tuned models once complete.\n\n[Learn more about fine-tuning](https://developers.openai.com/api/docs/guides/model-optimization)\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateFineTuningJobRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FineTuningJob" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create fine-tuning job", + "group": "fine-tuning", + "examples": [ + { + "title": "Default", + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/jobs \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"training_file\": \"file-BK7bzQj3FfZFXr7DbL6xJwfo\",\n \"model\": \"gpt-4o-mini\"\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.fine_tuning.jobs.create(\n training_file=\"file-abc123\",\n model=\"gpt-4o-mini\"\n)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const fineTune = await openai.fineTuning.jobs.create({\n training_file: \"file-abc123\"\n });\n\n console.log(fineTune);\n}\n\nmain();\n" + }, + "response": "{\n \"object\": \"fine_tuning.job\",\n \"id\": \"ftjob-abc123\",\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"created_at\": 1721764800,\n \"fine_tuned_model\": null,\n \"organization_id\": \"org-123\",\n \"result_files\": [],\n \"status\": \"queued\",\n \"validation_file\": null,\n \"training_file\": \"file-abc123\",\n \"method\": {\n \"type\": \"supervised\",\n \"supervised\": {\n \"hyperparameters\": {\n \"batch_size\": \"auto\",\n \"learning_rate_multiplier\": \"auto\",\n \"n_epochs\": \"auto\",\n }\n }\n },\n \"metadata\": null\n}\n" + }, + { + "title": "Epochs", + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/jobs \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"training_file\": \"file-abc123\",\n \"model\": \"gpt-4o-mini\",\n \"method\": {\n \"type\": \"supervised\",\n \"supervised\": {\n \"hyperparameters\": {\n \"n_epochs\": 2\n }\n }\n }\n }'\n", + "python": "from openai import OpenAI\nfrom openai.types.fine_tuning import SupervisedMethod, SupervisedHyperparameters\n\nclient = OpenAI()\n\nclient.fine_tuning.jobs.create(\n training_file=\"file-abc123\",\n model=\"gpt-4o-mini\",\n method={\n \"type\": \"supervised\",\n \"supervised\": SupervisedMethod(\n hyperparameters=SupervisedHyperparameters(\n n_epochs=2\n )\n )\n }\n)\n", + "javascript": "import OpenAI from \"openai\";\nimport { SupervisedMethod, SupervisedHyperparameters } from \"openai/resources/fine-tuning/methods\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const fineTune = await openai.fineTuning.jobs.create({\n training_file: \"file-abc123\",\n model: \"gpt-4o-mini\",\n method: {\n type: \"supervised\",\n supervised: {\n hyperparameters: {\n n_epochs: 2\n }\n }\n }\n });\n\n console.log(fineTune);\n}\n\nmain();\n" + }, + "response": "{\n \"object\": \"fine_tuning.job\",\n \"id\": \"ftjob-abc123\",\n \"model\": \"gpt-4o-mini\",\n \"created_at\": 1721764800,\n \"fine_tuned_model\": null,\n \"organization_id\": \"org-123\",\n \"result_files\": [],\n \"status\": \"queued\",\n \"validation_file\": null,\n \"training_file\": \"file-abc123\",\n \"hyperparameters\": {\n \"batch_size\": \"auto\",\n \"learning_rate_multiplier\": \"auto\",\n \"n_epochs\": 2\n },\n \"method\": {\n \"type\": \"supervised\",\n \"supervised\": {\n \"hyperparameters\": {\n \"batch_size\": \"auto\",\n \"learning_rate_multiplier\": \"auto\",\n \"n_epochs\": 2\n }\n }\n },\n \"metadata\": null,\n \"error\": {\n \"code\": null,\n \"message\": null,\n \"param\": null\n },\n \"finished_at\": null,\n \"seed\": 683058546,\n \"trained_tokens\": null,\n \"estimated_finish\": null,\n \"integrations\": [],\n \"user_provided_suffix\": null,\n \"usage_metrics\": null,\n \"shared_with_openai\": false\n}\n" + }, + { + "title": "DPO", + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/jobs \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"training_file\": \"file-abc123\",\n \"validation_file\": \"file-abc123\",\n \"model\": \"gpt-4o-mini\",\n \"method\": {\n \"type\": \"dpo\",\n \"dpo\": {\n \"hyperparameters\": {\n \"beta\": 0.1\n }\n }\n }\n }'\n", + "python": "from openai import OpenAI\nfrom openai.types.fine_tuning import DpoMethod, DpoHyperparameters\n\nclient = OpenAI()\n\nclient.fine_tuning.jobs.create(\n training_file=\"file-abc\",\n validation_file=\"file-123\",\n model=\"gpt-4o-mini\",\n method={\n \"type\": \"dpo\",\n \"dpo\": DpoMethod(\n hyperparameters=DpoHyperparameters(beta=0.1)\n )\n }\n)\n" + }, + "response": "{\n \"object\": \"fine_tuning.job\",\n \"id\": \"ftjob-abc\",\n \"model\": \"gpt-4o-mini\",\n \"created_at\": 1746130590,\n \"fine_tuned_model\": null,\n \"organization_id\": \"org-abc\",\n \"result_files\": [],\n \"status\": \"queued\",\n \"validation_file\": \"file-123\",\n \"training_file\": \"file-abc\",\n \"method\": {\n \"type\": \"dpo\",\n \"dpo\": {\n \"hyperparameters\": {\n \"beta\": 0.1,\n \"batch_size\": \"auto\",\n \"learning_rate_multiplier\": \"auto\",\n \"n_epochs\": \"auto\"\n }\n }\n },\n \"metadata\": null,\n \"error\": {\n \"code\": null,\n \"message\": null,\n \"param\": null\n },\n \"finished_at\": null,\n \"hyperparameters\": null,\n \"seed\": 1036326793,\n \"estimated_finish\": null,\n \"integrations\": [],\n \"user_provided_suffix\": null,\n \"usage_metrics\": null,\n \"shared_with_openai\": false\n}\n" + }, + { + "title": "Reinforcement", + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/jobs \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"training_file\": \"file-abc\",\n \"validation_file\": \"file-123\",\n \"model\": \"o4-mini\",\n \"method\": {\n \"type\": \"reinforcement\",\n \"reinforcement\": {\n \"grader\": {\n \"type\": \"string_check\",\n \"name\": \"Example string check grader\",\n \"input\": \"{{sample.output_text}}\",\n \"reference\": \"{{item.label}}\",\n \"operation\": \"eq\"\n },\n \"hyperparameters\": {\n \"reasoning_effort\": \"medium\"\n }\n }\n }\n }'\n", + "python": "from openai import OpenAI\nfrom openai.types.fine_tuning import ReinforcementMethod, ReinforcementHyperparameters\nfrom openai.types.graders import StringCheckGrader\n\nclient = OpenAI()\n\nclient.fine_tuning.jobs.create(\n training_file=\"file-abc\",\n validation_file=\"file-123\",\n model=\"o4-mini\",\n method={\n \"type\": \"reinforcement\",\n \"reinforcement\": ReinforcementMethod(\n grader=StringCheckGrader(\n name=\"Example string check grader\",\n type=\"string_check\",\n input=\"{{item.label}}\",\n operation=\"eq\",\n reference=\"{{sample.output_text}}\"\n ),\n hyperparameters=ReinforcementHyperparameters(\n reasoning_effort=\"medium\",\n )\n )\n }, \n seed=42,\n)\n" + }, + "response": "{\n \"object\": \"fine_tuning.job\",\n \"id\": \"ftjob-abc123\",\n \"model\": \"o4-mini\",\n \"created_at\": 1721764800,\n \"finished_at\": null,\n \"fine_tuned_model\": null,\n \"organization_id\": \"org-123\",\n \"result_files\": [],\n \"status\": \"validating_files\",\n \"validation_file\": \"file-123\",\n \"training_file\": \"file-abc\",\n \"trained_tokens\": null,\n \"error\": {},\n \"user_provided_suffix\": null,\n \"seed\": 950189191,\n \"estimated_finish\": null,\n \"integrations\": [],\n \"method\": {\n \"type\": \"reinforcement\",\n \"reinforcement\": {\n \"hyperparameters\": {\n \"batch_size\": \"auto\",\n \"learning_rate_multiplier\": \"auto\",\n \"n_epochs\": \"auto\",\n \"eval_interval\": \"auto\",\n \"eval_samples\": \"auto\",\n \"compute_multiplier\": \"auto\",\n \"reasoning_effort\": \"medium\"\n },\n \"grader\": {\n \"type\": \"string_check\",\n \"name\": \"Example string check grader\",\n \"input\": \"{{sample.output_text}}\",\n \"reference\": \"{{item.label}}\",\n \"operation\": \"eq\"\n },\n \"response_format\": null\n }\n },\n \"metadata\": null,\n \"usage_metrics\": null,\n \"shared_with_openai\": false\n}\n \n" + }, + { + "title": "Validation file", + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/jobs \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"training_file\": \"file-abc123\",\n \"validation_file\": \"file-abc123\",\n \"model\": \"gpt-4o-mini\"\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.fine_tuning.jobs.create(\n training_file=\"file-abc123\",\n validation_file=\"file-def456\",\n model=\"gpt-4o-mini\"\n)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const fineTune = await openai.fineTuning.jobs.create({\n training_file: \"file-abc123\",\n validation_file: \"file-abc123\"\n });\n\n console.log(fineTune);\n}\n\nmain();\n" + }, + "response": "{\n \"object\": \"fine_tuning.job\",\n \"id\": \"ftjob-abc123\",\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"created_at\": 1721764800,\n \"fine_tuned_model\": null,\n \"organization_id\": \"org-123\",\n \"result_files\": [],\n \"status\": \"queued\",\n \"validation_file\": \"file-abc123\",\n \"training_file\": \"file-abc123\",\n \"method\": {\n \"type\": \"supervised\",\n \"supervised\": {\n \"hyperparameters\": {\n \"batch_size\": \"auto\",\n \"learning_rate_multiplier\": \"auto\",\n \"n_epochs\": \"auto\",\n }\n }\n },\n \"metadata\": null\n}\n" + }, + { + "title": "W&B Integration", + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/jobs \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"training_file\": \"file-abc123\",\n \"validation_file\": \"file-abc123\",\n \"model\": \"gpt-4o-mini\",\n \"integrations\": [\n {\n \"type\": \"wandb\",\n \"wandb\": {\n \"project\": \"my-wandb-project\",\n \"name\": \"ft-run-display-name\"\n \"tags\": [\n \"first-experiment\", \"v2\"\n ]\n }\n }\n ]\n }'\n" + }, + "response": "{\n \"object\": \"fine_tuning.job\",\n \"id\": \"ftjob-abc123\",\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"created_at\": 1721764800,\n \"fine_tuned_model\": null,\n \"organization_id\": \"org-123\",\n \"result_files\": [],\n \"status\": \"queued\",\n \"validation_file\": \"file-abc123\",\n \"training_file\": \"file-abc123\",\n \"integrations\": [\n {\n \"type\": \"wandb\",\n \"wandb\": {\n \"project\": \"my-wandb-project\",\n \"entity\": None,\n \"run_id\": \"ftjob-abc123\"\n }\n }\n ],\n \"method\": {\n \"type\": \"supervised\",\n \"supervised\": {\n \"hyperparameters\": {\n \"batch_size\": \"auto\",\n \"learning_rate_multiplier\": \"auto\",\n \"n_epochs\": \"auto\",\n }\n }\n },\n \"metadata\": null\n}\n" + } + ] + } + }, + "get": { + "operationId": "listPaginatedFineTuningJobs", + "tags": [ + "Fine-tuning" + ], + "summary": "List your organization's fine-tuning jobs\n", + "parameters": [ + { + "name": "after", + "in": "query", + "description": "Identifier for the last job from the previous pagination request.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "Number of fine-tuning jobs to retrieve.", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "in": "query", + "name": "metadata", + "required": false, + "schema": { + "type": "object", + "nullable": true, + "additionalProperties": { + "type": "string" + } + }, + "style": "deepObject", + "explode": true, + "description": "Optional metadata filter. To filter, use the syntax `metadata[k]=v`. Alternatively, set `metadata=null` to indicate no metadata.\n" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListPaginatedFineTuningJobsResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List fine-tuning jobs", + "group": "fine-tuning", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/jobs?limit=2&metadata[key]=value \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.fine_tuning.jobs.list()\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const list = await openai.fineTuning.jobs.list();\n\n for await (const fineTune of list) {\n console.log(fineTune);\n }\n}\n\nmain();" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"fine_tuning.job\",\n \"id\": \"ftjob-abc123\",\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"created_at\": 1721764800,\n \"fine_tuned_model\": null,\n \"organization_id\": \"org-123\",\n \"result_files\": [],\n \"status\": \"queued\",\n \"validation_file\": null,\n \"training_file\": \"file-abc123\",\n \"metadata\": {\n \"key\": \"value\"\n }\n },\n { ... },\n { ... }\n ], \"has_more\": true\n}\n" + } + } + } + }, + "/fine_tuning/jobs/{fine_tuning_job_id}": { + "get": { + "operationId": "retrieveFineTuningJob", + "tags": [ + "Fine-tuning" + ], + "summary": "Get info about a fine-tuning job.\n\n[Learn more about fine-tuning](https://developers.openai.com/api/docs/guides/model-optimization)\n", + "parameters": [ + { + "in": "path", + "name": "fine_tuning_job_id", + "required": true, + "schema": { + "type": "string", + "example": "ft-AF1WoRqd3aJAHsqc9NY7iL8F" + }, + "description": "The ID of the fine-tuning job.\n" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FineTuningJob" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve fine-tuning job", + "group": "fine-tuning", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/jobs/ft-AF1WoRqd3aJAHsqc9NY7iL8F \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.fine_tuning.jobs.retrieve(\"ftjob-abc123\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const fineTune = await openai.fineTuning.jobs.retrieve(\"ftjob-abc123\");\n\n console.log(fineTune);\n}\n\nmain();\n" + }, + "response": "{\n \"object\": \"fine_tuning.job\",\n \"id\": \"ftjob-abc123\",\n \"model\": \"davinci-002\",\n \"created_at\": 1692661014,\n \"finished_at\": 1692661190,\n \"fine_tuned_model\": \"ft:davinci-002:my-org:custom_suffix:7q8mpxmy\",\n \"organization_id\": \"org-123\",\n \"result_files\": [\n \"file-abc123\"\n ],\n \"status\": \"succeeded\",\n \"validation_file\": null,\n \"training_file\": \"file-abc123\",\n \"hyperparameters\": {\n \"n_epochs\": 4,\n \"batch_size\": 1,\n \"learning_rate_multiplier\": 1.0\n },\n \"trained_tokens\": 5768,\n \"integrations\": [],\n \"seed\": 0,\n \"estimated_finish\": 0,\n \"method\": {\n \"type\": \"supervised\",\n \"supervised\": {\n \"hyperparameters\": {\n \"n_epochs\": 4,\n \"batch_size\": 1,\n \"learning_rate_multiplier\": 1.0\n }\n }\n }\n}\n" + } + } + } + }, + "/fine_tuning/jobs/{fine_tuning_job_id}/cancel": { + "post": { + "operationId": "cancelFineTuningJob", + "tags": [ + "Fine-tuning" + ], + "summary": "Immediately cancel a fine-tune job.\n", + "parameters": [ + { + "in": "path", + "name": "fine_tuning_job_id", + "required": true, + "schema": { + "type": "string", + "example": "ft-AF1WoRqd3aJAHsqc9NY7iL8F" + }, + "description": "The ID of the fine-tuning job to cancel.\n" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FineTuningJob" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Cancel fine-tuning", + "group": "fine-tuning", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/fine_tuning/jobs/ftjob-abc123/cancel \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.fine_tuning.jobs.cancel(\"ftjob-abc123\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const fineTune = await openai.fineTuning.jobs.cancel(\"ftjob-abc123\");\n\n console.log(fineTune);\n}\nmain();" + }, + "response": "{\n \"object\": \"fine_tuning.job\",\n \"id\": \"ftjob-abc123\",\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"created_at\": 1721764800,\n \"fine_tuned_model\": null,\n \"organization_id\": \"org-123\",\n \"result_files\": [],\n \"status\": \"cancelled\",\n \"validation_file\": \"file-abc123\",\n \"training_file\": \"file-abc123\"\n}\n" + } + } + } + }, + "/fine_tuning/jobs/{fine_tuning_job_id}/checkpoints": { + "get": { + "operationId": "listFineTuningJobCheckpoints", + "tags": [ + "Fine-tuning" + ], + "summary": "List checkpoints for a fine-tuning job.\n", + "parameters": [ + { + "in": "path", + "name": "fine_tuning_job_id", + "required": true, + "schema": { + "type": "string", + "example": "ft-AF1WoRqd3aJAHsqc9NY7iL8F" + }, + "description": "The ID of the fine-tuning job to get checkpoints for.\n" + }, + { + "name": "after", + "in": "query", + "description": "Identifier for the last checkpoint ID from the previous pagination request.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "Number of checkpoints to retrieve.", + "required": false, + "schema": { + "type": "integer", + "default": 10 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListFineTuningJobCheckpointsResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List fine-tuning checkpoints", + "group": "fine-tuning", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/jobs/ftjob-abc123/checkpoints \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"fine_tuning.job.checkpoint\",\n \"id\": \"ftckpt_zc4Q7MP6XxulcVzj4MZdwsAB\",\n \"created_at\": 1721764867,\n \"fine_tuned_model_checkpoint\": \"ft:gpt-4o-mini-2024-07-18:my-org:custom-suffix:96olL566:ckpt-step-2000\",\n \"metrics\": {\n \"full_valid_loss\": 0.134,\n \"full_valid_mean_token_accuracy\": 0.874\n },\n \"fine_tuning_job_id\": \"ftjob-abc123\",\n \"step_number\": 2000\n },\n {\n \"object\": \"fine_tuning.job.checkpoint\",\n \"id\": \"ftckpt_enQCFmOTGj3syEpYVhBRLTSy\",\n \"created_at\": 1721764800,\n \"fine_tuned_model_checkpoint\": \"ft:gpt-4o-mini-2024-07-18:my-org:custom-suffix:7q8mpxmy:ckpt-step-1000\",\n \"metrics\": {\n \"full_valid_loss\": 0.167,\n \"full_valid_mean_token_accuracy\": 0.781\n },\n \"fine_tuning_job_id\": \"ftjob-abc123\",\n \"step_number\": 1000\n }\n ],\n \"first_id\": \"ftckpt_zc4Q7MP6XxulcVzj4MZdwsAB\",\n \"last_id\": \"ftckpt_enQCFmOTGj3syEpYVhBRLTSy\",\n \"has_more\": true\n}\n" + } + } + } + }, + "/fine_tuning/jobs/{fine_tuning_job_id}/events": { + "get": { + "operationId": "listFineTuningEvents", + "tags": [ + "Fine-tuning" + ], + "summary": "Get status updates for a fine-tuning job.\n", + "parameters": [ + { + "in": "path", + "name": "fine_tuning_job_id", + "required": true, + "schema": { + "type": "string", + "example": "ft-AF1WoRqd3aJAHsqc9NY7iL8F" + }, + "description": "The ID of the fine-tuning job to get events for.\n" + }, + { + "name": "after", + "in": "query", + "description": "Identifier for the last event from the previous pagination request.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "Number of events to retrieve.", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListFineTuningJobEventsResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List fine-tuning events", + "group": "fine-tuning", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/fine_tuning/jobs/ftjob-abc123/events \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.fine_tuning.jobs.list_events(\n fine_tuning_job_id=\"ftjob-abc123\",\n limit=2\n)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const list = await openai.fineTuning.list_events(id=\"ftjob-abc123\", limit=2);\n\n for await (const fineTune of list) {\n console.log(fineTune);\n }\n}\n\nmain();" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"fine_tuning.job.event\",\n \"id\": \"ft-event-ddTJfwuMVpfLXseO0Am0Gqjm\",\n \"created_at\": 1721764800,\n \"level\": \"info\",\n \"message\": \"Fine tuning job successfully completed\",\n \"data\": null,\n \"type\": \"message\"\n },\n {\n \"object\": \"fine_tuning.job.event\",\n \"id\": \"ft-event-tyiGuB72evQncpH87xe505Sv\",\n \"created_at\": 1721764800,\n \"level\": \"info\",\n \"message\": \"New fine-tuned model created: ft:gpt-4o-mini:openai::7p4lURel\",\n \"data\": null,\n \"type\": \"message\"\n }\n ],\n \"has_more\": true\n}\n" + } + } + } + }, + "/fine_tuning/jobs/{fine_tuning_job_id}/pause": { + "post": { + "operationId": "pauseFineTuningJob", + "tags": [ + "Fine-tuning" + ], + "summary": "Pause a fine-tune job.\n", + "parameters": [ + { + "in": "path", + "name": "fine_tuning_job_id", + "required": true, + "schema": { + "type": "string", + "example": "ft-AF1WoRqd3aJAHsqc9NY7iL8F" + }, + "description": "The ID of the fine-tuning job to pause.\n" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FineTuningJob" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Pause fine-tuning", + "group": "fine-tuning", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/fine_tuning/jobs/ftjob-abc123/pause \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.fine_tuning.jobs.pause(\"ftjob-abc123\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const fineTune = await openai.fineTuning.jobs.pause(\"ftjob-abc123\");\n\n console.log(fineTune);\n}\nmain();" + }, + "response": "{\n \"object\": \"fine_tuning.job\",\n \"id\": \"ftjob-abc123\",\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"created_at\": 1721764800,\n \"fine_tuned_model\": null,\n \"organization_id\": \"org-123\",\n \"result_files\": [],\n \"status\": \"paused\",\n \"validation_file\": \"file-abc123\",\n \"training_file\": \"file-abc123\"\n}\n" + } + } + } + }, + "/fine_tuning/jobs/{fine_tuning_job_id}/resume": { + "post": { + "operationId": "resumeFineTuningJob", + "tags": [ + "Fine-tuning" + ], + "summary": "Resume a fine-tune job.\n", + "parameters": [ + { + "in": "path", + "name": "fine_tuning_job_id", + "required": true, + "schema": { + "type": "string", + "example": "ft-AF1WoRqd3aJAHsqc9NY7iL8F" + }, + "description": "The ID of the fine-tuning job to resume.\n" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FineTuningJob" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Resume fine-tuning", + "group": "fine-tuning", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/fine_tuning/jobs/ftjob-abc123/resume \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.fine_tuning.jobs.resume(\"ftjob-abc123\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const fineTune = await openai.fineTuning.jobs.resume(\"ftjob-abc123\");\n\n console.log(fineTune);\n}\nmain();" + }, + "response": "{\n \"object\": \"fine_tuning.job\",\n \"id\": \"ftjob-abc123\",\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"created_at\": 1721764800,\n \"fine_tuned_model\": null,\n \"organization_id\": \"org-123\",\n \"result_files\": [],\n \"status\": \"queued\",\n \"validation_file\": \"file-abc123\",\n \"training_file\": \"file-abc123\"\n}\n" + } + } + } + }, + "/images/edits": { + "post": { + "operationId": "createImageEdit", + "tags": [ + "Images" + ], + "summary": "Creates an edited or extended image given one or more source images and a prompt. This endpoint supports GPT Image models and `dall-e-2`.", + "description": "You can call this endpoint with either:\n\n- `multipart/form-data`: use binary uploads via `image` (and optional `mask`).\n- `application/json`: use `images` (and optional `mask`) as references with either `image_url` or `file_id`.\n\nNote that JSON requests use `images` (array) instead of the multipart `image` field.\n", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateImageEditRequest" + }, + "examples": { + "multipart_edit": { + "summary": "Multipart form upload (binary image + prompt)", + "value": { + "model": "gpt-image-1.5", + "prompt": "Add a watercolor effect to this image", + "image": "", + "size": "1024x1024", + "quality": "high" + } + } + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditImageBodyJsonParam" + }, + "examples": { + "json_with_url": { + "summary": "JSON request with image URL", + "value": { + "model": "gpt-image-1.5", + "prompt": "Add a watercolor effect to this image", + "images": [ + { + "image_url": "https://example.com/source-image.png" + } + ], + "size": "1024x1024", + "quality": "high" + } + }, + "json_with_file_id": { + "summary": "JSON request with uploaded file id", + "value": { + "model": "gpt-image-1.5", + "prompt": "Replace the background with a snowy mountain scene", + "images": [ + { + "file_id": "file-abc123" + } + ], + "mask": { + "file_id": "file-mask123" + }, + "output_format": "png", + "output_compression": 100 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImagesResponse" + } + }, + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/ImageEditStreamEvent" + } + } + } + }, + "429": { + "$ref": "#/components/responses/InferenceRateLimited" + }, + "503": { + "$ref": "#/components/responses/InferenceServiceUnavailable" + } + }, + "x-oaiMeta": { + "name": "Create image edit", + "group": "images", + "examples": [ + { + "title": "Edit image", + "request": { + "curl": "curl -s -D >(grep -i x-request-id >&2) \\\n -o >(jq -r '.data[0].b64_json' | base64 --decode > gift-basket.png) \\\n -X POST \"https://api.openai.com/v1/images/edits\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -F \"model=gpt-image-1.5\" \\\n -F \"image[]=@body-lotion.png\" \\\n -F \"image[]=@bath-bomb.png\" \\\n -F \"image[]=@incense-kit.png\" \\\n -F \"image[]=@soap.png\" \\\n -F 'prompt=Create a lovely gift basket with these four items in it'\n", + "python": "import base64\nfrom openai import OpenAI\nclient = OpenAI()\n\nprompt = \"\"\"\nGenerate a photorealistic image of a gift basket on a white background\nlabeled 'Relax & Unwind' with a ribbon and handwriting-like font,\ncontaining all the items in the reference pictures.\n\"\"\"\n\nresult = client.images.edit(\n model=\"gpt-image-1.5\",\n image=[\n open(\"body-lotion.png\", \"rb\"),\n open(\"bath-bomb.png\", \"rb\"),\n open(\"incense-kit.png\", \"rb\"),\n open(\"soap.png\", \"rb\"),\n ],\n prompt=prompt\n)\n\nimage_base64 = result.data[0].b64_json\nimage_bytes = base64.b64decode(image_base64)\n\n# Save the image to a file\nwith open(\"gift-basket.png\", \"wb\") as f:\n f.write(image_bytes)\n", + "javascript": "import fs from \"fs\";\nimport OpenAI, { toFile } from \"openai\";\n\nconst client = new OpenAI();\n\nconst imageFiles = [\n \"bath-bomb.png\",\n \"body-lotion.png\",\n \"incense-kit.png\",\n \"soap.png\",\n];\n\nconst images = await Promise.all(\n imageFiles.map(async (file) =>\n await toFile(fs.createReadStream(file), null, {\n type: \"image/png\",\n })\n ),\n);\n\nconst rsp = await client.images.edit({\n model: \"gpt-image-1.5\",\n image: images,\n prompt: \"Create a lovely gift basket with these four items in it\",\n});\n\n// Save the image to a file\nconst image_base64 = rsp.data[0].b64_json;\nconst image_bytes = Buffer.from(image_base64, \"base64\");\nfs.writeFileSync(\"basket.png\", image_bytes);\n" + } + }, + { + "title": "Streaming", + "request": { + "curl": "curl -s -N -X POST \"https://api.openai.com/v1/images/edits\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -F \"model=gpt-image-1.5\" \\\n -F \"image[]=@body-lotion.png\" \\\n -F \"image[]=@bath-bomb.png\" \\\n -F \"image[]=@incense-kit.png\" \\\n -F \"image[]=@soap.png\" \\\n -F 'prompt=Create a lovely gift basket with these four items in it' \\\n -F \"stream=true\"\n", + "python": "from openai import OpenAI\n\nclient = OpenAI()\n\nprompt = \"\"\"\nGenerate a photorealistic image of a gift basket on a white background\nlabeled 'Relax & Unwind' with a ribbon and handwriting-like font,\ncontaining all the items in the reference pictures.\n\"\"\"\n\nstream = client.images.edit(\n model=\"gpt-image-1.5\",\n image=[\n open(\"body-lotion.png\", \"rb\"),\n open(\"bath-bomb.png\", \"rb\"),\n open(\"incense-kit.png\", \"rb\"),\n open(\"soap.png\", \"rb\"),\n ],\n prompt=prompt,\n stream=True\n)\n\nfor event in stream:\n print(event)\n", + "javascript": "import fs from \"fs\";\nimport OpenAI, { toFile } from \"openai\";\n\nconst client = new OpenAI();\n\nconst imageFiles = [\n \"bath-bomb.png\",\n \"body-lotion.png\",\n \"incense-kit.png\",\n \"soap.png\",\n];\n\nconst images = await Promise.all(\n imageFiles.map(async (file) =>\n await toFile(fs.createReadStream(file), null, {\n type: \"image/png\",\n })\n ),\n);\n\nconst stream = await client.images.edit({\n model: \"gpt-image-1.5\",\n image: images,\n prompt: \"Create a lovely gift basket with these four items in it\",\n stream: true,\n});\n\nfor await (const event of stream) {\n console.log(event);\n}\n" + }, + "response": "event: image_edit.partial_image\ndata: {\"type\":\"image_edit.partial_image\",\"b64_json\":\"...\",\"partial_image_index\":0}\n\nevent: image_edit.completed\ndata: {\"type\":\"image_edit.completed\",\"b64_json\":\"...\",\"usage\":{\"total_tokens\":100,\"input_tokens\":50,\"output_tokens\":50,\"input_tokens_details\":{\"text_tokens\":10,\"image_tokens\":40}}}\n" + } + ] + } + } + }, + "/images/generations": { + "post": { + "operationId": "createImage", + "tags": [ + "Images" + ], + "summary": "Creates an image given a prompt. [Learn more](https://developers.openai.com/api/docs/guides/images-vision).\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateImageRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImagesResponse" + } + }, + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/ImageGenStreamEvent" + } + } + } + }, + "429": { + "$ref": "#/components/responses/InferenceRateLimited" + }, + "503": { + "$ref": "#/components/responses/InferenceServiceUnavailable" + } + }, + "x-oaiMeta": { + "name": "Create image", + "group": "images", + "examples": [ + { + "title": "Generate image", + "request": { + "curl": "curl https://api.openai.com/v1/images/generations \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"model\": \"gpt-image-1.5\",\n \"prompt\": \"A cute baby sea otter\",\n \"n\": 1,\n \"size\": \"1024x1024\"\n }'\n", + "python": "import base64\nfrom openai import OpenAI\nclient = OpenAI()\n\nimg = client.images.generate(\n model=\"gpt-image-1.5\",\n prompt=\"A cute baby sea otter\",\n n=1,\n size=\"1024x1024\"\n)\n\nimage_bytes = base64.b64decode(img.data[0].b64_json)\nwith open(\"output.png\", \"wb\") as f:\n f.write(image_bytes)\n", + "javascript": "import OpenAI from \"openai\";\nimport { writeFile } from \"fs/promises\";\n\nconst client = new OpenAI();\n\nconst img = await client.images.generate({\n model: \"gpt-image-1.5\",\n prompt: \"A cute baby sea otter\",\n n: 1,\n size: \"1024x1024\"\n});\n\nconst imageBuffer = Buffer.from(img.data[0].b64_json, \"base64\");\nawait writeFile(\"output.png\", imageBuffer);\n" + }, + "response": "{\n \"created\": 1713833628,\n \"data\": [\n {\n \"b64_json\": \"...\"\n }\n ],\n \"usage\": {\n \"total_tokens\": 100,\n \"input_tokens\": 50,\n \"output_tokens\": 50,\n \"input_tokens_details\": {\n \"text_tokens\": 10,\n \"image_tokens\": 40\n }\n }\n}\n" + }, + { + "title": "Streaming", + "request": { + "curl": "curl https://api.openai.com/v1/images/generations \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"model\": \"gpt-image-1.5\",\n \"prompt\": \"A cute baby sea otter\",\n \"n\": 1,\n \"size\": \"1024x1024\",\n \"stream\": true\n }' \\\n --no-buffer\n", + "python": "from openai import OpenAI\n\nclient = OpenAI()\n\nstream = client.images.generate(\n model=\"gpt-image-1.5\",\n prompt=\"A cute baby sea otter\",\n n=1,\n size=\"1024x1024\",\n stream=True\n)\n\nfor event in stream:\n print(event)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst client = new OpenAI();\n\nconst stream = await client.images.generate({\n model: \"gpt-image-1.5\",\n prompt: \"A cute baby sea otter\",\n n: 1,\n size: \"1024x1024\",\n stream: true,\n});\n\nfor await (const event of stream) {\n console.log(event);\n}\n" + }, + "response": "event: image_generation.partial_image\ndata: {\"type\":\"image_generation.partial_image\",\"b64_json\":\"...\",\"partial_image_index\":0}\n\nevent: image_generation.completed\ndata: {\"type\":\"image_generation.completed\",\"b64_json\":\"...\",\"usage\":{\"total_tokens\":100,\"input_tokens\":50,\"output_tokens\":50,\"input_tokens_details\":{\"text_tokens\":10,\"image_tokens\":40}}}\n" + } + ] + } + } + }, + "/images/variations": { + "post": { + "operationId": "createImageVariation", + "tags": [ + "Images" + ], + "summary": "Creates a variation of a given image. This endpoint only supports `dall-e-2`.", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateImageVariationRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImagesResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create image variation", + "group": "images", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/images/variations \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -F image=\"@otter.png\" \\\n -F n=2 \\\n -F size=\"1024x1024\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nresponse = client.images.create_variation(\n image=open(\"image_edit_original.png\", \"rb\"),\n n=2,\n size=\"1024x1024\"\n)\n", + "javascript": "import fs from \"fs\";\nimport OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const image = await openai.images.createVariation({\n image: fs.createReadStream(\"otter.png\"),\n });\n\n console.log(image.data);\n}\nmain();", + "csharp": "using System;\n\nusing OpenAI.Images;\n\nImageClient client = new(\n model: \"dall-e-2\",\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nGeneratedImage image = client.GenerateImageVariation(imageFilePath: \"otter.png\");\n\nConsole.WriteLine(image.ImageUri);\n" + }, + "response": "{\n \"created\": 1589478378,\n \"data\": [\n {\n \"url\": \"https://...\"\n },\n {\n \"url\": \"https://...\"\n }\n ]\n}\n" + } + } + } + }, + "/live/sessions": { + "post": { + "operationId": "create-live", + "summary": "Create a Live WebRTC session. Start with the [Live prompting guide](https://developers.openai.com/api/docs/guides/live-prompting).", + "description": "Send JSON containing session configuration and transport with type webrtc and the SDP offer. The request starts the session. Apply transport.sdp from the response as the remote answer and wait for session.started on the data channel before sending commands. Audio uses the negotiated media track; omit audio.format and do not send session.start on the data channel. Before integrating, follow the [Live prompting guide](https://developers.openai.com/api/docs/guides/live-prompting) to write frontend conversation and delegation instructions and a separate backend prompt.", + "tags": [ + "Live" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LiveCreateRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Live session created with a WebRTC answer.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LiveCreateResponse" + }, + "example": { + "session": { + "id": "live_123" + }, + "transport": { + "type": "webrtc", + "sdp": "" + } + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create session", + "group": "live", + "returns": "Returns 201 Created with the session identifier in session.id and SDP answer in transport.sdp.", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/live/sessions \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"session\":{\"model\":\"gpt-live-1\",\"instructions\":\"Be concise. Ask for clarification when needed.\"},\"transport\":{\"type\":\"webrtc\",\"sdp\":\"\"}}'" + } + } + } + } + }, + "/live/sessions/{session_id}/accept": { + "post": { + "operationId": "accept-live-session", + "summary": "Accept an incoming SIP call. Supply session with type live, the model, and startup configuration. Before accepting calls, follow the [Live prompting guide](https://developers.openai.com/api/docs/guides/live-prompting) to write frontend conversation instructions and a separate backend prompt. SIP media format is negotiated; omit audio.format.", + "description": "Accept an incoming SIP call. Supply session with type live, the model, and startup configuration. Before accepting calls, follow the [Live prompting guide](https://developers.openai.com/api/docs/guides/live-prompting) to write frontend conversation instructions and a separate backend prompt. SIP media format is negotiated; omit audio.format.", + "tags": [ + "Live" + ], + "parameters": [ + { + "in": "path", + "name": "session_id", + "required": true, + "description": "Opaque Live session identifier from the creation response or incoming-call webhook. Preserve the returned value unchanged, including its prefix.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LiveCallAcceptRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Session accept request accepted." + } + }, + "x-oaiMeta": { + "name": "Accept call", + "group": "live-sessions", + "returns": "Returns 200 OK when the control request succeeds." + } + } + }, + "/live/sessions/{session_id}/fork": { + "post": { + "operationId": "fork-live-session", + "summary": "Fork a stored Live session onto a new WebRTC connection.", + "description": "Resume the stored conversation using a new WebRTC connection. The model, voice, and frontend instructions are inherited. Omit session or send an empty object to inherit the remaining configuration. Only Responses delegation settings, storage, and frontend data-channel permissions can be overridden. Apply transport.sdp as the remote answer and wait for session.started before sending commands. Do not send session.start on the data channel or supply audio.format; WebRTC negotiates its media format.", + "tags": [ + "Live" + ], + "parameters": [ + { + "in": "path", + "name": "session_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the stored Live session to fork." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LiveForkRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Forked Live session created with a WebRTC answer.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LiveCreateResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Fork session", + "group": "live", + "returns": "The new session identifier in session.id and SDP answer in transport.sdp.", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/live/sessions/live_123/fork \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"session\":{},\"transport\":{\"type\":\"webrtc\",\"sdp\":\"\"}}'" + } + } + } + } + }, + "/live/sessions/{session_id}/hangup": { + "post": { + "operationId": "hangup-live-session", + "summary": "End a SIP call identified by session_id.", + "description": "End a SIP call identified by session_id.", + "tags": [ + "Live" + ], + "parameters": [ + { + "in": "path", + "name": "session_id", + "required": true, + "description": "Opaque Live session identifier from the creation response or incoming-call webhook. Preserve the returned value unchanged, including its prefix.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Session hangup request accepted." + } + }, + "x-oaiMeta": { + "name": "Hang up session", + "group": "live-sessions", + "returns": "Returns 200 OK when the control request succeeds." + } + } + }, + "/live/sessions/{session_id}/refer": { + "post": { + "operationId": "refer-live-session", + "summary": "Transfer a SIP call to another destination. Supply a nonblank target_uri for the SIP Refer-To header.", + "description": "Transfer a SIP call to another destination. Supply a nonblank target_uri for the SIP Refer-To header.", + "tags": [ + "Live" + ], + "parameters": [ + { + "in": "path", + "name": "session_id", + "required": true, + "description": "Opaque Live session identifier from the creation response or incoming-call webhook. Preserve the returned value unchanged, including its prefix.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LiveCallReferRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Session refer request accepted." + } + }, + "x-oaiMeta": { + "name": "Transfer call", + "group": "live-sessions", + "returns": "Returns 200 OK when the control request succeeds." + } + } + }, + "/live/sessions/{session_id}/reject": { + "post": { + "operationId": "reject-live-session", + "summary": "Reject an incoming SIP call. Send a required SIP rejection status_code between 300 and 699.", + "description": "Reject an incoming SIP call. Send a required SIP rejection status_code between 300 and 699.", + "tags": [ + "Live" + ], + "parameters": [ + { + "in": "path", + "name": "session_id", + "required": true, + "description": "Opaque Live session identifier from the creation response or incoming-call webhook. Preserve the returned value unchanged, including its prefix.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LiveCallRejectRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Session reject request accepted." + } + }, + "x-oaiMeta": { + "name": "Reject call", + "group": "live-sessions", + "returns": "Returns 200 OK when the control request succeeds." + } + } + }, + "/models": { + "get": { + "operationId": "listModels", + "tags": [ + "Models" + ], + "summary": "Lists the currently available models, and provides basic information about each one such as the owner and availability.", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListModelsResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List models", + "group": "models", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/models \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.models.list()\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const list = await openai.models.list();\n\n for await (const model of list) {\n console.log(model);\n }\n}\nmain();", + "csharp": "using System;\n\nusing OpenAI.Models;\n\nOpenAIModelClient client = new(\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nforeach (var model in client.GetModels().Value)\n{\n Console.WriteLine(model.Id);\n}\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"model-id-0\",\n \"object\": \"model\",\n \"created\": 1686935002,\n \"owned_by\": \"organization-owner\",\n \"shutdown_date\": null\n },\n {\n \"id\": \"model-id-1\",\n \"object\": \"model\",\n \"created\": 1686935002,\n \"owned_by\": \"organization-owner\",\n \"shutdown_date\": null\n },\n {\n \"id\": \"model-id-2\",\n \"object\": \"model\",\n \"created\": 1686935002,\n \"owned_by\": \"openai\",\n \"shutdown_date\": \"2026-10-23\"\n },\n ]\n}\n" + } + } + } + }, + "/models/{model}": { + "get": { + "operationId": "retrieveModel", + "tags": [ + "Models" + ], + "summary": "Retrieves a model instance, providing basic information about the model such as the owner and permissioning.", + "parameters": [ + { + "in": "path", + "name": "model", + "required": true, + "schema": { + "type": "string", + "example": "gpt-6-astra" + }, + "description": "The ID of the model to use for this request" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Model" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve model", + "group": "models", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/models/gpt-6-astra \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.models.retrieve(\"gpt-6-astra\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const model = await openai.models.retrieve(\"gpt-6-astra\");\n\n console.log(model);\n}\n\nmain();", + "csharp": "using System;\nusing System.ClientModel;\n\nusing OpenAI.Models;\n\n OpenAIModelClient client = new(\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nClientResult model = client.GetModel(\"babbage-002\");\nConsole.WriteLine(model.Value.Id);\n" + }, + "response": "{\n \"id\": \"gpt-6-astra\",\n \"object\": \"model\",\n \"created\": 1686935002,\n \"owned_by\": \"openai\",\n \"shutdown_date\": \"2026-10-23\"\n}\n" + } + } + }, + "delete": { + "operationId": "deleteModel", + "tags": [ + "Models" + ], + "summary": "Delete a fine-tuned model. You must have the Owner role in your organization to delete a model.", + "parameters": [ + { + "in": "path", + "name": "model", + "required": true, + "schema": { + "type": "string", + "example": "ft:gpt-4o-mini:acemeco:suffix:abc123" + }, + "description": "The model to delete" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteModelResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete a fine-tuned model", + "group": "models", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/models/ft:gpt-4o-mini:acemeco:suffix:abc123 \\\n -X DELETE \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\"\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nclient.models.delete(\"ft:gpt-4o-mini:acemeco:suffix:abc123\")\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const model = await openai.models.delete(\"ft:gpt-4o-mini:acemeco:suffix:abc123\");\n \n console.log(model);\n}\nmain();", + "csharp": "using System;\nusing System.ClientModel;\n\nusing OpenAI.Models;\n\nOpenAIModelClient client = new(\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nClientResult success = client.DeleteModel(\"ft:gpt-4o-mini:acemeco:suffix:abc123\");\nConsole.WriteLine(success);\n" + }, + "response": "{\n \"id\": \"ft:gpt-4o-mini:acemeco:suffix:abc123\",\n \"object\": \"model\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/moderations": { + "post": { + "operationId": "createModeration", + "tags": [ + "Moderations" + ], + "summary": "Classifies if text and/or image inputs are potentially harmful. Learn\nmore in the [moderation guide](https://developers.openai.com/api/docs/guides/moderation).\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateModerationRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateModerationResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/InferenceRateLimited" + }, + "503": { + "$ref": "#/components/responses/InferenceServiceUnavailable" + } + }, + "x-oaiMeta": { + "name": "Create moderation", + "group": "moderations", + "examples": [ + { + "title": "Single string", + "request": { + "curl": "curl https://api.openai.com/v1/moderations \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"input\": \"I want to kill them.\"\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nmoderation = client.moderations.create(input=\"I want to kill them.\")\nprint(moderation)\n", + "javascript": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI();\n\nasync function main() {\n const moderation = await openai.moderations.create({ input: \"I want to kill them.\" });\n\n console.log(moderation);\n}\nmain();\n", + "csharp": "using System;\nusing System.ClientModel;\n\nusing OpenAI.Moderations;\n\nModerationClient client = new(\n model: \"omni-moderation-latest\",\n apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n);\n\nClientResult moderation = client.ClassifyText(\"I want to kill them.\");\n" + }, + "response": "{\n \"id\": \"modr-AB8CjOTu2jiq12hp1AQPfeqFWaORR\",\n \"model\": \"text-moderation-007\",\n \"results\": [\n {\n \"flagged\": true,\n \"categories\": {\n \"sexual\": false,\n \"hate\": false,\n \"harassment\": true,\n \"self-harm\": false,\n \"sexual/minors\": false,\n \"hate/threatening\": false,\n \"violence/graphic\": false,\n \"self-harm/intent\": false,\n \"self-harm/instructions\": false,\n \"harassment/threatening\": true,\n \"violence\": true\n },\n \"category_scores\": {\n \"sexual\": 0.000011726012417057063,\n \"hate\": 0.22706663608551025,\n \"harassment\": 0.5215635299682617,\n \"self-harm\": 2.227119921371923e-6,\n \"sexual/minors\": 7.107352217872176e-8,\n \"hate/threatening\": 0.023547329008579254,\n \"violence/graphic\": 0.00003391829886822961,\n \"self-harm/intent\": 1.646940972932498e-6,\n \"self-harm/instructions\": 1.1198755256458526e-9,\n \"harassment/threatening\": 0.5694745779037476,\n \"violence\": 0.9971134662628174\n }\n }\n ]\n}\n" + }, + { + "title": "Image and text", + "request": { + "curl": "curl https://api.openai.com/v1/moderations \\\n -X POST \\\n -H \"Content-Type: application/json\" \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -d '{\n \"model\": \"omni-moderation-latest\",\n \"input\": [\n { \"type\": \"text\", \"text\": \"...text to classify goes here...\" },\n {\n \"type\": \"image_url\",\n \"image_url\": {\n \"url\": \"https://example.com/image.png\"\n }\n }\n ]\n }'\n", + "python": "from openai import OpenAI\nclient = OpenAI()\n\nresponse = client.moderations.create(\n model=\"omni-moderation-latest\",\n input=[\n {\"type\": \"text\", \"text\": \"...text to classify goes here...\"},\n {\n \"type\": \"image_url\",\n \"image_url\": {\n \"url\": \"https://example.com/image.png\",\n # can also use base64 encoded image URLs\n # \"url\": \"data:image/jpeg;base64,abcdefg...\"\n }\n },\n ],\n)\n\nprint(response)\n", + "javascript": "import OpenAI from \"openai\";\nconst openai = new OpenAI();\n\nconst moderation = await openai.moderations.create({\n model: \"omni-moderation-latest\",\n input: [\n { type: \"text\", text: \"...text to classify goes here...\" },\n {\n type: \"image_url\",\n image_url: {\n url: \"https://example.com/image.png\"\n // can also use base64 encoded image URLs\n // url: \"data:image/jpeg;base64,abcdefg...\"\n }\n }\n ],\n});\n\nconsole.log(moderation);\n" + }, + "response": "{\n \"id\": \"modr-0d9740456c391e43c445bf0f010940c7\",\n \"model\": \"omni-moderation-latest\",\n \"results\": [\n {\n \"flagged\": true,\n \"categories\": {\n \"harassment\": true,\n \"harassment/threatening\": true,\n \"sexual\": false,\n \"hate\": false,\n \"hate/threatening\": false,\n \"illicit\": false,\n \"illicit/violent\": false,\n \"self-harm/intent\": false,\n \"self-harm/instructions\": false,\n \"self-harm\": false,\n \"sexual/minors\": false,\n \"violence\": true,\n \"violence/graphic\": true\n },\n \"category_scores\": {\n \"harassment\": 0.8189693396524255,\n \"harassment/threatening\": 0.804985420696006,\n \"sexual\": 1.573112165348997e-6,\n \"hate\": 0.007562942636942845,\n \"hate/threatening\": 0.004208854591835476,\n \"illicit\": 0.030535955153511665,\n \"illicit/violent\": 0.008925306722380033,\n \"self-harm/intent\": 0.00023023930975076432,\n \"self-harm/instructions\": 0.0002293869201073356,\n \"self-harm\": 0.012598046106750154,\n \"sexual/minors\": 2.212566909570261e-8,\n \"violence\": 0.9999992735124786,\n \"violence/graphic\": 0.843064871157054\n },\n \"category_applied_input_types\": {\n \"harassment\": [\n \"text\"\n ],\n \"harassment/threatening\": [\n \"text\"\n ],\n \"sexual\": [\n \"text\",\n \"image\"\n ],\n \"hate\": [\n \"text\"\n ],\n \"hate/threatening\": [\n \"text\"\n ],\n \"illicit\": [\n \"text\"\n ],\n \"illicit/violent\": [\n \"text\"\n ],\n \"self-harm/intent\": [\n \"text\",\n \"image\"\n ],\n \"self-harm/instructions\": [\n \"text\",\n \"image\"\n ],\n \"self-harm\": [\n \"text\",\n \"image\"\n ],\n \"sexual/minors\": [\n \"text\"\n ],\n \"violence\": [\n \"text\",\n \"image\"\n ],\n \"violence/graphic\": [\n \"text\",\n \"image\"\n ]\n }\n }\n ]\n}\n" + } + ] + } + } + }, + "/organization/admin_api_keys": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "List organization API keys", + "operationId": "admin-api-keys-list", + "description": "Retrieve a paginated list of organization admin API keys.", + "parameters": [ + { + "in": "query", + "name": "after", + "required": false, + "schema": { + "type": "string", + "nullable": true, + "description": "Return keys with IDs that come after this ID in the pagination order." + } + }, + { + "in": "query", + "name": "order", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc", + "description": "Order results by creation time, ascending or descending." + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer", + "default": 20, + "description": "Maximum number of keys to return." + } + } + ], + "responses": { + "200": { + "description": "A list of organization API keys.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiKeyList" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List all organization and project API keys.", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/admin_api_keys?after=key_abc&limit=20 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"organization.admin_api_key\",\n \"id\": \"key_abc\",\n \"name\": \"Main Admin Key\",\n \"redacted_value\": \"sk-admin...def\",\n \"created_at\": 1711471533,\n \"expires_at\": 1714063533,\n \"last_used_at\": 1711471534,\n \"owner\": {\n \"type\": \"service_account\",\n \"object\": \"organization.service_account\",\n \"id\": \"sa_456\",\n \"name\": \"My Service Account\",\n \"created_at\": 1711471533,\n \"role\": \"member\"\n }\n }\n ],\n \"first_id\": \"key_abc\",\n \"last_id\": \"key_abc\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Create an organization admin API key", + "operationId": "admin-api-keys-create", + "description": "Create a new admin-level API key for the organization.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "example": "New Admin Key" + }, + "expires_in_seconds": { + "type": "integer", + "minimum": 1, + "maximum": 31536000, + "example": 2592000, + "description": "The number of seconds until the API key expires. Omit this field for a key that does not expire." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The newly created admin API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminApiKeyCreateResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create admin API key", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/admin_api_keys \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"New Admin Key\",\n \"expires_in_seconds\": 2592000\n }'\n" + }, + "response": "{\n \"object\": \"organization.admin_api_key\",\n \"id\": \"key_xyz\",\n \"name\": \"New Admin Key\",\n \"redacted_value\": \"sk-admin...xyz\",\n \"created_at\": 1711471533,\n \"expires_at\": 1714063533,\n \"last_used_at\": 1711471534,\n \"owner\": {\n \"type\": \"user\",\n \"object\": \"organization.user\",\n \"id\": \"user_123\",\n \"name\": \"John Doe\",\n \"created_at\": 1711471533,\n \"role\": \"owner\"\n },\n \"value\": \"sk-admin-1234abcd\"\n}\n" + } + } + } + }, + "/organization/admin_api_keys/{key_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieve a single organization API key", + "operationId": "admin-api-keys-get", + "description": "Get details for a specific organization API key by its ID.", + "parameters": [ + { + "in": "path", + "name": "key_id", + "required": true, + "schema": { + "type": "string", + "description": "The ID of the API key." + } + } + ], + "responses": { + "200": { + "description": "Details of the requested API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminApiKey" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve admin API key", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/admin_api_keys/key_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"organization.admin_api_key\",\n \"id\": \"key_abc\",\n \"name\": \"Main Admin Key\",\n \"redacted_value\": \"sk-admin...xyz\",\n \"created_at\": 1711471533,\n \"last_used_at\": 1711471534,\n \"owner\": {\n \"type\": \"user\",\n \"object\": \"organization.user\",\n \"id\": \"user_123\",\n \"name\": \"John Doe\",\n \"created_at\": 1711471533,\n \"role\": \"owner\"\n }\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Delete an organization admin API key", + "operationId": "admin-api-keys-delete", + "description": "Delete the specified admin API key.", + "parameters": [ + { + "in": "path", + "name": "key_id", + "required": true, + "schema": { + "type": "string", + "description": "The ID of the API key to be deleted." + } + } + ], + "responses": { + "200": { + "description": "Confirmation that the API key was deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "key_abc" + }, + "object": { + "type": "string", + "enum": [ + "organization.admin_api_key.deleted" + ], + "example": "organization.admin_api_key.deleted", + "x-stainless-const": true + }, + "deleted": { + "type": "boolean", + "example": true + } + }, + "required": [ + "id", + "object", + "deleted" + ] + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete admin API key", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/admin_api_keys/key_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"id\": \"key_abc\",\n \"object\": \"organization.admin_api_key.deleted\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/audit_logs": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "List user actions and configuration changes within this organization.", + "operationId": "list-audit-logs", + "tags": [ + "Audit Logs" + ], + "parameters": [ + { + "name": "effective_at", + "in": "query", + "description": "Return only events whose `effective_at` (Unix seconds) is in this range.", + "required": false, + "schema": { + "type": "object", + "properties": { + "gt": { + "type": "integer", + "description": "Return only events whose `effective_at` (Unix seconds) is greater than this value." + }, + "gte": { + "type": "integer", + "description": "Return only events whose `effective_at` (Unix seconds) is greater than or equal to this value." + }, + "lt": { + "type": "integer", + "description": "Return only events whose `effective_at` (Unix seconds) is less than this value." + }, + "lte": { + "type": "integer", + "description": "Return only events whose `effective_at` (Unix seconds) is less than or equal to this value." + } + } + } + }, + { + "name": "project_ids[]", + "in": "query", + "description": "Return only events for these projects.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "event_types[]", + "in": "query", + "description": "Return only events with a `type` in one of these values. For example, `project.created`. For all options, see the documentation for the [audit log object](https://developers.openai.com/api/reference/resources/admin/subresources/organization/subresources/audit_logs).", + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuditLogEventType" + } + } + }, + { + "name": "actor_ids[]", + "in": "query", + "description": "Return only events performed by these actors. Can be a user ID, a service account ID, or an api key tracking ID.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "actor_emails[]", + "in": "query", + "description": "Return only events performed by users with these emails.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "resource_ids[]", + "in": "query", + "description": "Return only events performed on these targets. For example, a project ID updated. For ChatGPT connector role events, use the workspace connector resource ID shown in `details.id`, such as `__`.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "tenant_only", + "in": "query", + "description": "Return only tenant-scoped events associated with this organization. Required for tenant-scoped events such as `role.bound_to_resource` and `role.unbound_from_resource`. When `true`, all supplied event types must be tenant-scoped.", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "schema": { + "type": "string" + } + }, + { + "name": "before", + "in": "query", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Audit logs listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListAuditLogsResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List audit logs", + "group": "audit-logs", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/audit_logs \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"audit_log-xxx_yyyymmdd\",\n \"type\": \"project.archived\",\n \"effective_at\": 1722461446,\n \"actor\": {\n \"type\": \"api_key\",\n \"api_key\": {\n \"type\": \"user\",\n \"user\": {\n \"id\": \"user-xxx\",\n \"email\": \"user@example.com\"\n }\n }\n },\n \"project.archived\": {\n \"id\": \"proj_abc\"\n },\n },\n {\n \"id\": \"audit_log-yyy__20240101\",\n \"type\": \"api_key.updated\",\n \"effective_at\": 1720804190,\n \"actor\": {\n \"type\": \"session\",\n \"session\": {\n \"user\": {\n \"id\": \"user-xxx\",\n \"email\": \"user@example.com\"\n },\n \"ip_address\": \"127.0.0.1\",\n \"user_agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36\",\n \"ja3\": \"a497151ce4338a12c4418c44d375173e\",\n \"ja4\": \"q13d0313h3_55b375c5d22e_c7319ce65786\",\n \"ip_address_details\": {\n \"country\": \"US\",\n \"city\": \"San Francisco\",\n \"region\": \"California\",\n \"region_code\": \"CA\",\n \"asn\": \"1234\",\n \"latitude\": \"37.77490\",\n \"longitude\": \"-122.41940\"\n }\n }\n },\n \"api_key.updated\": {\n \"id\": \"key_xxxx\",\n \"data\": {\n \"scopes\": [\"resource_2.operation_2\"]\n }\n },\n }\n ],\n \"first_id\": \"audit_log-xxx__20240101\",\n \"last_id\": \"audit_log_yyy__20240101\",\n \"has_more\": true\n}\n" + } + } + } + }, + "/organization/certificates": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "List uploaded certificates for this organization.", + "operationId": "listOrganizationCertificates", + "tags": [ + "Certificates" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "schema": { + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ] + } + } + ], + "responses": { + "200": { + "description": "Certificates listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListCertificatesResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List organization certificates", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/certificates \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"organization.certificate\",\n \"id\": \"cert_abc\",\n \"name\": \"My Example Certificate\",\n \"active\": true,\n \"created_at\": 1234567,\n \"certificate_details\": {\n \"valid_at\": 12345667,\n \"expires_at\": 12345678\n }\n },\n ],\n \"first_id\": \"cert_abc\",\n \"last_id\": \"cert_abc\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Upload a certificate to the organization. This does **not** automatically activate the certificate.\n\nOrganizations can upload up to 50 certificates.\n", + "operationId": "uploadCertificate", + "tags": [ + "Certificates" + ], + "requestBody": { + "description": "The certificate upload payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UploadCertificateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Certificate uploaded successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Certificate" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Upload certificate", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/certificates \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\" \\\n-d '{\n \"name\": \"My Example Certificate\",\n \"certificate\": \"-----BEGIN CERTIFICATE-----\\\\nMIIDeT...\\\\n-----END CERTIFICATE-----\"\n}'\n" + }, + "response": "{\n \"object\": \"certificate\",\n \"id\": \"cert_abc\",\n \"name\": \"My Example Certificate\",\n \"created_at\": 1234567,\n \"certificate_details\": {\n \"valid_at\": 12345667,\n \"expires_at\": 12345678\n }\n}\n" + } + } + } + }, + "/organization/certificates/activate": { + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Activate certificates at the organization level.\n\nYou can atomically and idempotently activate up to 10 certificates at a time.\n", + "operationId": "activateOrganizationCertificates", + "tags": [ + "Certificates" + ], + "requestBody": { + "description": "The certificate activation payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ToggleCertificatesRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Certificates activated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationCertificateActivationResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Activate certificates for organization", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/certificates/activate \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\" \\\n-d '{\n \"certificate_ids\": [\"cert_abc\", \"cert_def\"]\n}'\n" + }, + "response": "{\n \"object\": \"organization.certificate.activation\",\n \"data\": [\n {\n \"object\": \"organization.certificate\",\n \"id\": \"cert_abc\",\n \"name\": \"My Example Certificate\",\n \"active\": true,\n \"created_at\": 1234567,\n \"certificate_details\": {\n \"valid_at\": 12345667,\n \"expires_at\": 12345678\n }\n },\n {\n \"object\": \"organization.certificate\",\n \"id\": \"cert_def\",\n \"name\": \"My Example Certificate 2\",\n \"active\": true,\n \"created_at\": 1234567,\n \"certificate_details\": {\n \"valid_at\": 12345667,\n \"expires_at\": 12345678\n }\n },\n ],\n}\n" + } + } + } + }, + "/organization/certificates/deactivate": { + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Deactivate certificates at the organization level.\n\nYou can atomically and idempotently deactivate up to 10 certificates at a time.\n", + "operationId": "deactivateOrganizationCertificates", + "tags": [ + "Certificates" + ], + "requestBody": { + "description": "The certificate deactivation payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ToggleCertificatesRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Certificates deactivated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationCertificateDeactivationResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Deactivate certificates for organization", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/certificates/deactivate \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\" \\\n-d '{\n \"certificate_ids\": [\"cert_abc\", \"cert_def\"]\n}'\n" + }, + "response": "{\n \"object\": \"organization.certificate.deactivation\",\n \"data\": [\n {\n \"object\": \"organization.certificate\",\n \"id\": \"cert_abc\",\n \"name\": \"My Example Certificate\",\n \"active\": false,\n \"created_at\": 1234567,\n \"certificate_details\": {\n \"valid_at\": 12345667,\n \"expires_at\": 12345678\n }\n },\n {\n \"object\": \"organization.certificate\",\n \"id\": \"cert_def\",\n \"name\": \"My Example Certificate 2\",\n \"active\": false,\n \"created_at\": 1234567,\n \"certificate_details\": {\n \"valid_at\": 12345667,\n \"expires_at\": 12345678\n }\n },\n ],\n}\n" + } + } + } + }, + "/organization/certificates/{certificate_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Get a certificate that has been uploaded to the organization.\n\nYou can get a certificate regardless of whether it is active or not.\n", + "operationId": "getCertificate", + "tags": [ + "Certificates" + ], + "parameters": [ + { + "name": "certificate_id", + "in": "path", + "description": "Unique ID of the certificate to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "include", + "in": "query", + "description": "A list of additional fields to include in the response. Currently the only supported value is `content` to fetch the PEM content of the certificate.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "content" + ] + } + } + } + ], + "responses": { + "200": { + "description": "Certificate retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Certificate" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Get certificate", + "group": "administration", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/organization/certificates/cert_abc?include[]=content\" \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\"\n" + }, + "response": "{\n \"object\": \"certificate\",\n \"id\": \"cert_abc\",\n \"name\": \"My Example Certificate\",\n \"created_at\": 1234567,\n \"certificate_details\": {\n \"valid_at\": 1234567,\n \"expires_at\": 12345678,\n \"content\": \"-----BEGIN CERTIFICATE-----MIIDeT...-----END CERTIFICATE-----\"\n }\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Modify a certificate. Note that only the name can be modified.\n", + "operationId": "modifyCertificate", + "tags": [ + "Certificates" + ], + "parameters": [ + { + "name": "certificate_id", + "in": "path", + "description": "Unique ID of the certificate to modify.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The certificate modification payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModifyCertificateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Certificate modified successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Certificate" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Modify certificate", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/certificates/cert_abc \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\" \\\n-d '{\n \"name\": \"Renamed Certificate\"\n}'\n" + }, + "response": "{\n \"object\": \"certificate\",\n \"id\": \"cert_abc\",\n \"name\": \"Renamed Certificate\",\n \"created_at\": 1234567,\n \"certificate_details\": {\n \"valid_at\": 12345667,\n \"expires_at\": 12345678\n }\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Delete a certificate from the organization.\n\nThe certificate must be inactive for the organization and all projects.\n", + "operationId": "deleteCertificate", + "tags": [ + "Certificates" + ], + "parameters": [ + { + "name": "certificate_id", + "in": "path", + "description": "Unique ID of the certificate to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Certificate deleted successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteCertificateResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Delete certificate", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/certificates/cert_abc \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\"\n" + }, + "response": "{\n \"object\": \"certificate.deleted\",\n \"id\": \"cert_abc\"\n}\n" + } + } + } + }, + "/organization/costs": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Get costs details for the organization.", + "operationId": "usage-costs", + "tags": [ + "Usage" + ], + "parameters": [ + { + "name": "start_time", + "in": "query", + "description": "Start time (Unix seconds) of the query time range, inclusive.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "end_time", + "in": "query", + "description": "End time (Unix seconds) of the query time range, exclusive.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "bucket_width", + "in": "query", + "description": "Width of each time bucket in response. Currently only `1d` is supported, default to `1d`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "1d" + ], + "default": "1d" + } + }, + { + "name": "project_ids", + "in": "query", + "description": "Return only costs for these projects.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "api_key_ids", + "in": "query", + "description": "Return only costs for these API keys.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "line_items", + "in": "query", + "description": "Return only costs for these exact line item names. Each value must match the complete `line_item` value, for example `gpt-6-astra, input_tokens`.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "group_by", + "in": "query", + "description": "Group the costs by the specified fields. Support fields include `project_id`, `line_item`, `api_key_id` and any combination of them.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "project_id", + "line_item", + "api_key_id" + ] + } + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of buckets to be returned. Limit can range between 1 and 180, and the default is 7.\n", + "required": false, + "schema": { + "type": "integer", + "default": 7 + } + }, + { + "name": "page", + "in": "query", + "description": "A cursor for use in pagination. Corresponding to the `next_page` field from the previous response.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Costs data retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Costs", + "group": "usage-costs", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/organization/costs?start_time=1730419200&limit=1\" \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"page\",\n \"data\": [\n {\n \"object\": \"bucket\",\n \"start_time\": 1730419200,\n \"end_time\": 1730505600,\n \"results\": [\n {\n \"object\": \"organization.costs.result\",\n \"amount\": {\n \"value\": 0.06,\n \"currency\": \"usd\"\n },\n \"line_item\": null,\n \"project_id\": null,\n \"api_key_id\": null,\n \"quantity\": null,\n \"quantity_unit\": null\n }\n ]\n }\n ],\n \"has_more\": false,\n \"next_page\": null\n}\n" + } + } + } + }, + "/organization/data_retention": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves organization data retention controls.", + "operationId": "retrieve-organization-data-retention", + "tags": [ + "Data retention" + ], + "responses": { + "200": { + "description": "Organization data retention controls retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationDataRetention" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve organization data retention", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/data_retention \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"organization.data_retention\",\n \"type\": \"modified_abuse_monitoring\"\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Updates organization data retention controls.", + "operationId": "update-organization-data-retention", + "tags": [ + "Data retention" + ], + "requestBody": { + "description": "The desired organization data retention setting.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateOrganizationDataRetentionBody" + } + } + } + }, + "responses": { + "200": { + "description": "Organization data retention controls updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationDataRetention" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Update organization data retention", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/data_retention \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"retention_type\": \"modified_abuse_monitoring\"\n }'\n" + }, + "response": "{\n \"object\": \"organization.data_retention\",\n \"type\": \"modified_abuse_monitoring\"\n}\n" + } + } + } + }, + "/organization/groups": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Lists all groups in the organization.", + "operationId": "list-groups", + "tags": [ + "Groups" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "A limit on the number of groups to be returned. Limit can range between 0 and 1000, and the default is 100.\n", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000, + "default": 100 + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is a group ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with group_abc, your subsequent call can include `after=group_abc` in order to fetch the next page of the list.\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "in": "query", + "description": "Specifies the sort order of the returned groups.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + } + ], + "responses": { + "200": { + "description": "Groups listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupListResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List groups", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/groups?limit=20&order=asc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"group\",\n \"id\": \"group_01J1F8ABCDXYZ\",\n \"name\": \"Support Team\",\n \"created_at\": 1711471533,\n \"is_scim_managed\": false\n }\n ],\n \"has_more\": false,\n \"next\": null\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Creates a new group in the organization.", + "operationId": "create-group", + "tags": [ + "Groups" + ], + "requestBody": { + "description": "Parameters for the group you want to create.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateGroupBody" + } + } + } + }, + "responses": { + "200": { + "description": "Group created successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Create group", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/groups \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Support Team\"\n }'\n" + }, + "response": "{\n \"object\": \"group\",\n \"id\": \"group_01J1F8ABCDXYZ\",\n \"name\": \"Support Team\",\n \"created_at\": 1711471533,\n \"is_scim_managed\": false\n}\n" + } + } + } + }, + "/organization/groups/{group_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves a group.", + "operationId": "retrieve-group", + "tags": [ + "Groups" + ], + "parameters": [ + { + "name": "group_id", + "in": "path", + "description": "The ID of the group to retrieve.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Group retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve group", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/groups/group_01J1F8ABCDXYZ \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"id\": \"group_01J1F8ABCDXYZ\",\n \"name\": \"Support Team\",\n \"created_at\": 1711471533,\n \"is_scim_managed\": false,\n \"group_type\": \"group\"\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Updates a group's information.", + "operationId": "update-group", + "tags": [ + "Groups" + ], + "parameters": [ + { + "name": "group_id", + "in": "path", + "description": "The ID of the group to update.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "New attributes to set on the group.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateGroupBody" + } + } + } + }, + "responses": { + "200": { + "description": "Group updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupResourceWithSuccess" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Update group", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/groups/group_01J1F8ABCDXYZ \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Escalations\"\n }'\n" + }, + "response": "{\n \"id\": \"group_01J1F8ABCDXYZ\",\n \"name\": \"Escalations\",\n \"created_at\": 1711471533,\n \"is_scim_managed\": false\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Deletes a group from the organization.", + "operationId": "delete-group", + "tags": [ + "Groups" + ], + "parameters": [ + { + "name": "group_id", + "in": "path", + "description": "The ID of the group to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Group deleted successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupDeletedResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Delete group", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/groups/group_01J1F8ABCDXYZ \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"group.deleted\",\n \"id\": \"group_01J1F8ABCDXYZ\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/groups/{group_id}/roles": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Lists the organization roles assigned to a group within the organization.", + "operationId": "list-group-role-assignments", + "tags": [ + "Group organization role assignments" + ], + "parameters": [ + { + "name": "group_id", + "in": "path", + "description": "The ID of the group whose organization role assignments you want to list.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of organization role assignments to return.", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000 + } + }, + { + "name": "after", + "in": "query", + "description": "Cursor for pagination. Provide the value from the previous response's `next` field to continue listing organization roles.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for the returned organization roles.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + } + ], + "responses": { + "200": { + "description": "Group organization role assignments listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleListResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List group organization role assignments", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/groups/group_01J1F8ABCDXYZ/roles \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"role_01J1F8ROLE01\",\n \"name\": \"API Group Manager\",\n \"permissions\": [\n \"api.groups.read\",\n \"api.groups.write\"\n ],\n \"resource_type\": \"api.organization\",\n \"predefined_role\": false,\n \"description\": \"Allows managing organization groups\",\n \"created_at\": 1711471533,\n \"updated_at\": 1711472599,\n \"created_by\": \"user_abc123\",\n \"created_by_user_obj\": {\n \"id\": \"user_abc123\",\n \"name\": \"Ada Lovelace\",\n \"email\": \"ada@example.com\"\n },\n \"metadata\": {}\n }\n ],\n \"has_more\": false,\n \"next\": null\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Assigns an organization role to a group within the organization.", + "operationId": "assign-group-role", + "tags": [ + "Group organization role assignments" + ], + "parameters": [ + { + "name": "group_id", + "in": "path", + "description": "The ID of the group that should receive the organization role.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Identifies the organization role to assign to the group.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicAssignOrganizationGroupRoleBody" + } + } + } + }, + "responses": { + "200": { + "description": "Organization role assigned to the group successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupRoleAssignment" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Assign organization role to group", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/groups/group_01J1F8ABCDXYZ/roles \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"role_id\": \"role_01J1F8ROLE01\"\n }'\n" + }, + "response": "{\n \"object\": \"group.role\",\n \"group\": {\n \"object\": \"group\",\n \"id\": \"group_01J1F8ABCDXYZ\",\n \"name\": \"Support Team\",\n \"created_at\": 1711471533,\n \"scim_managed\": false\n },\n \"role\": {\n \"object\": \"role\",\n \"id\": \"role_01J1F8ROLE01\",\n \"name\": \"API Group Manager\",\n \"description\": \"Allows managing organization groups\",\n \"permissions\": [\n \"api.groups.read\",\n \"api.groups.write\"\n ],\n \"resource_type\": \"api.organization\",\n \"predefined_role\": false\n }\n}\n" + } + } + } + }, + "/organization/groups/{group_id}/roles/{role_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves an organization role assigned to a group.", + "operationId": "retrieve-group-role", + "tags": [ + "Group organization role assignments" + ], + "parameters": [ + { + "name": "group_id", + "in": "path", + "description": "The ID of the group to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "role_id", + "in": "path", + "description": "The ID of the organization role to retrieve for the group.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Organization role retrieved for the group successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssignedRoleDetails" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve group organization role", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/groups/group_01J1F8ABCDXYZ/roles/role_01J1F8ROLE01 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"id\": \"role_01J1F8ROLE01\",\n \"name\": \"API Group Manager\",\n \"permissions\": [\n \"api.groups.read\",\n \"api.groups.write\"\n ],\n \"resource_type\": \"api.organization\",\n \"predefined_role\": false,\n \"description\": \"Allows managing organization groups\",\n \"created_at\": 1711471533,\n \"updated_at\": 1711472599,\n \"created_by\": \"user_abc123\",\n \"created_by_user_obj\": null,\n \"metadata\": {},\n \"assignment_sources\": null\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Unassigns an organization role from a group within the organization.", + "operationId": "unassign-group-role", + "tags": [ + "Group organization role assignments" + ], + "parameters": [ + { + "name": "group_id", + "in": "path", + "description": "The ID of the group to modify.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "role_id", + "in": "path", + "description": "The ID of the organization role to remove from the group.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Organization role unassigned from the group successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedRoleAssignmentResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Unassign organization role from group", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/groups/group_01J1F8ABCDXYZ/roles/role_01J1F8ROLE01 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"group.role.deleted\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/groups/{group_id}/users": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Lists the users assigned to a group.", + "operationId": "list-group-users", + "tags": [ + "Group users" + ], + "parameters": [ + { + "name": "group_id", + "in": "path", + "description": "The ID of the group to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of users to be returned. Limit can range between 0 and 1000, and the default is 100.\n", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000, + "default": 100 + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. Provide the ID of the last user from the previous list response to retrieve the next page.\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "in": "query", + "description": "Specifies the sort order of users in the list.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "desc" + } + } + ], + "responses": { + "200": { + "description": "Group users listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserListResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List group users", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/groups/group_01J1F8ABCDXYZ/users?limit=20 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"user_abc123\",\n \"name\": \"Ada Lovelace\",\n \"email\": \"ada@example.com\"\n }\n ],\n \"has_more\": false,\n \"next\": null\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Adds a user to a group.", + "operationId": "add-group-user", + "tags": [ + "Group users" + ], + "parameters": [ + { + "name": "group_id", + "in": "path", + "description": "The ID of the group to update.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Identifies the user that should be added to the group.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateGroupUserBody" + } + } + } + }, + "responses": { + "200": { + "description": "User added to the group successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupUserAssignment" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Add group user", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/groups/group_01J1F8ABCDXYZ/users \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"user_id\": \"user_abc123\"\n }'\n" + }, + "response": "{\n \"object\": \"group.user\",\n \"user_id\": \"user_abc123\",\n \"group_id\": \"group_01J1F8ABCDXYZ\"\n}\n" + } + } + } + }, + "/organization/groups/{group_id}/users/{user_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves a user in a group.", + "operationId": "retrieve-group-user", + "tags": [ + "Group users" + ], + "parameters": [ + { + "name": "group_id", + "in": "path", + "description": "The ID of the group to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "user_id", + "in": "path", + "description": "The ID of the user to retrieve from the group.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "User retrieved from the group successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupMemberUser" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve group user", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/groups/group_01J1F8ABCDXYZ/users/user_abc123 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"id\": \"user_abc123\",\n \"name\": \"Ada Lovelace\",\n \"email\": \"ada@example.com\",\n \"picture\": null,\n \"is_service_account\": false,\n \"user_type\": \"user\"\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Removes a user from a group.", + "operationId": "remove-group-user", + "tags": [ + "Group users" + ], + "parameters": [ + { + "name": "group_id", + "in": "path", + "description": "The ID of the group to update.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "user_id", + "in": "path", + "description": "The ID of the user to remove from the group.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "User removed from the group successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupUserDeletedResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Remove group user", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/groups/group_01J1F8ABCDXYZ/users/user_abc123 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"group.user.deleted\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/invites": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Returns a list of invites in the organization.", + "operationId": "list-invites", + "tags": [ + "Invites" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Invites listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InviteListResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List invites", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/invites?after=invite-abc&limit=20 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"organization.invite\",\n \"id\": \"invite-abc\",\n \"email\": \"user@example.com\",\n \"role\": \"owner\",\n \"status\": \"accepted\",\n \"created_at\": 1711471533,\n \"expires_at\": 1711471533,\n \"accepted_at\": 1711471533\n }\n ],\n \"first_id\": \"invite-abc\",\n \"last_id\": \"invite-abc\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Create an invite for a user to the organization. The invite must be accepted by the user before they have access to the organization.", + "operationId": "inviteUser", + "tags": [ + "Invites" + ], + "requestBody": { + "description": "The invite request payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InviteRequest" + } + } + } + }, + "responses": { + "200": { + "description": "User invited successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invite" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create invite", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/invites \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"email\": \"anotheruser@example.com\",\n \"role\": \"reader\",\n \"projects\": [\n {\n \"id\": \"project-xyz\",\n \"role\": \"member\"\n },\n {\n \"id\": \"project-abc\",\n \"role\": \"owner\"\n }\n ]\n }'\n" + }, + "response": "{\n \"object\": \"organization.invite\",\n \"id\": \"invite-def\",\n \"email\": \"anotheruser@example.com\",\n \"role\": \"reader\",\n \"status\": \"pending\",\n \"created_at\": 1711471533,\n \"expires_at\": 1711471533,\n \"accepted_at\": null,\n \"projects\": [\n {\n \"id\": \"project-xyz\",\n \"role\": \"member\"\n },\n {\n \"id\": \"project-abc\",\n \"role\": \"owner\"\n }\n ]\n}\n" + } + } + } + }, + "/organization/invites/{invite_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves an invite.", + "operationId": "retrieve-invite", + "tags": [ + "Invites" + ], + "parameters": [ + { + "in": "path", + "name": "invite_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the invite to retrieve." + } + ], + "responses": { + "200": { + "description": "Invite retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invite" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve invite", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/invites/invite-abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"organization.invite\",\n \"id\": \"invite-abc\",\n \"email\": \"user@example.com\",\n \"role\": \"owner\",\n \"status\": \"accepted\",\n \"created_at\": 1711471533,\n \"expires_at\": 1711471533,\n \"accepted_at\": 1711471533\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Delete an invite. If the invite has already been accepted, it cannot be deleted.", + "operationId": "delete-invite", + "tags": [ + "Invites" + ], + "parameters": [ + { + "in": "path", + "name": "invite_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "The ID of the invite to delete." + } + ], + "responses": { + "200": { + "description": "Invite deleted successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InviteDeleteResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete invite", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/invites/invite-abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"organization.invite.deleted\",\n \"id\": \"invite-abc\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/projects": { + "get": { + "summary": "Returns a list of projects.", + "operationId": "list-projects", + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "include_archived", + "in": "query", + "schema": { + "type": "boolean", + "default": false + }, + "description": "If `true` returns all projects including those that have been `archived`. Archived projects are not included by default." + } + ], + "responses": { + "200": { + "description": "Projects listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectListResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List projects", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects?after=proj_abc&limit=20&include_archived=false \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"proj_abc\",\n \"object\": \"organization.project\",\n \"name\": \"Project example\",\n \"created_at\": 1711471533,\n \"archived_at\": null,\n \"status\": \"active\"\n }\n ],\n \"first_id\": \"proj-abc\",\n \"last_id\": \"proj-xyz\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "summary": "Create a new project in the organization. Projects can be created and archived, but cannot be deleted.", + "operationId": "create-project", + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "tags": [ + "Projects" + ], + "requestBody": { + "description": "The project create request payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectCreateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Project created successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create project", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Project ABC\"\n }'\n" + }, + "response": "{\n \"id\": \"proj_abc\",\n \"object\": \"organization.project\",\n \"name\": \"Project ABC\",\n \"created_at\": 1711471533,\n \"archived_at\": null,\n \"status\": \"active\"\n}\n" + } + } + } + }, + "/organization/projects/{project_id}": { + "get": { + "summary": "Retrieves a project.", + "operationId": "retrieve-project", + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve project", + "group": "administration", + "description": "Retrieve a project.", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"id\": \"proj_abc\",\n \"object\": \"organization.project\",\n \"name\": \"Project example\",\n \"created_at\": 1711471533,\n \"archived_at\": null,\n \"status\": \"active\"\n}\n" + } + } + }, + "post": { + "summary": "Modifies a project in the organization.", + "operationId": "modify-project", + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The project update request payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Project updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + }, + "400": { + "description": "Error response when updating the default project.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Modify project", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects/proj_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Project DEF\"\n }'\n" + } + } + } + } + }, + "/organization/projects/{project_id}/api_keys": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Returns a list of API keys in the project.", + "operationId": "list-project-api-keys", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "owner_project_access", + "in": "query", + "description": "Filter API keys by whether the owner currently has effective access to the project. Use `active` for owners with access, `inactive` for owners without access, or `any` for all enabled project API keys. If omitted, the endpoint applies its existing membership-based visibility rules, which may exclude some enabled keys.\n", + "required": false, + "schema": { + "type": "string", + "enum": [ + "active", + "inactive", + "any" + ] + } + } + ], + "responses": { + "200": { + "description": "Project API keys listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectApiKeyListResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List project API keys", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/api_keys?after=key_abc&limit=20&owner_project_access=any \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"organization.project.api_key\",\n \"redacted_value\": \"sk-abc...def\",\n \"name\": \"My API Key\",\n \"created_at\": 1711471533,\n \"last_used_at\": 1711471534,\n \"id\": \"key_abc\",\n \"owner_project_access\": \"active\",\n \"owner\": {\n \"type\": \"user\",\n \"user\": {\n \"id\": \"user_abc\",\n \"name\": \"First Last\",\n \"email\": \"user@example.com\",\n \"role\": \"owner\",\n \"created_at\": 1711471533\n }\n }\n }\n ],\n \"first_id\": \"key_abc\",\n \"last_id\": \"key_xyz\",\n \"has_more\": false\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/api_keys/{api_key_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves an API key in the project.", + "operationId": "retrieve-project-api-key", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "api_key_id", + "in": "path", + "description": "The ID of the API key.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project API key retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectApiKey" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve project API key", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/api_keys/key_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"organization.project.api_key\",\n \"redacted_value\": \"sk-abc...def\",\n \"name\": \"My API Key\",\n \"created_at\": 1711471533,\n \"last_used_at\": 1711471534,\n \"id\": \"key_abc\",\n \"owner_project_access\": \"active\",\n \"owner\": {\n \"type\": \"user\",\n \"user\": {\n \"id\": \"user_abc\",\n \"name\": \"First Last\",\n \"email\": \"user@example.com\",\n \"role\": \"owner\",\n \"created_at\": 1711471533\n }\n }\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Deletes an API key from the project.\n\nReturns confirmation of the key deletion, or an error if the key belonged to\na service account.\n", + "operationId": "delete-project-api-key", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "api_key_id", + "in": "path", + "description": "The ID of the API key.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project API key deleted successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectApiKeyDeleteResponse" + } + } + } + }, + "400": { + "description": "Error response for various conditions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete project API key", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/projects/proj_abc/api_keys/key_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"organization.project.api_key.deleted\",\n \"id\": \"key_abc\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/archive": { + "post": { + "summary": "Archives a project in the organization. Archived projects cannot be used or updated.", + "operationId": "archive-project", + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project archived successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Archive project", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects/proj_abc/archive \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"id\": \"proj_abc\",\n \"object\": \"organization.project\",\n \"name\": \"Project DEF\",\n \"created_at\": 1711471533,\n \"archived_at\": 1711471533,\n \"status\": \"archived\"\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/certificates": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "List certificates for this project.", + "operationId": "listProjectCertificates", + "tags": [ + "Certificates" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and `desc` for descending order.\n", + "schema": { + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ] + } + } + ], + "responses": { + "200": { + "description": "Certificates listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListProjectCertificatesResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List project certificates", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/certificates \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"organization.project.certificate\",\n \"id\": \"cert_abc\",\n \"name\": \"My Example Certificate\",\n \"active\": true,\n \"created_at\": 1234567,\n \"certificate_details\": {\n \"valid_at\": 12345667,\n \"expires_at\": 12345678\n }\n },\n ],\n \"first_id\": \"cert_abc\",\n \"last_id\": \"cert_abc\",\n \"has_more\": false\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/certificates/activate": { + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Activate certificates at the project level.\n\nYou can atomically and idempotently activate up to 10 certificates at a time.\n", + "operationId": "activateProjectCertificates", + "tags": [ + "Certificates" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The certificate activation payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ToggleCertificatesRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Certificates activated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationProjectCertificateActivationResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Activate certificates for project", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/certificates/activate \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\" \\\n-d '{\n \"certificate_ids\": [\"cert_abc\", \"cert_def\"]\n}'\n" + }, + "response": "{\n \"object\": \"organization.project.certificate.activation\",\n \"data\": [\n {\n \"object\": \"organization.project.certificate\",\n \"id\": \"cert_abc\",\n \"name\": \"My Example Certificate\",\n \"active\": true,\n \"created_at\": 1234567,\n \"certificate_details\": {\n \"valid_at\": 12345667,\n \"expires_at\": 12345678\n }\n },\n {\n \"object\": \"organization.project.certificate\",\n \"id\": \"cert_def\",\n \"name\": \"My Example Certificate 2\",\n \"active\": true,\n \"created_at\": 1234567,\n \"certificate_details\": {\n \"valid_at\": 12345667,\n \"expires_at\": 12345678\n }\n },\n ],\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/certificates/deactivate": { + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Deactivate certificates at the project level. You can atomically and \nidempotently deactivate up to 10 certificates at a time.\n", + "operationId": "deactivateProjectCertificates", + "tags": [ + "Certificates" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The certificate deactivation payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ToggleCertificatesRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Certificates deactivated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationProjectCertificateDeactivationResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Deactivate certificates for project", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/certificates/deactivate \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\" \\\n-d '{\n \"certificate_ids\": [\"cert_abc\", \"cert_def\"]\n}'\n" + }, + "response": "{\n \"object\": \"organization.project.certificate.deactivation\",\n \"data\": [\n {\n \"object\": \"organization.project.certificate\",\n \"id\": \"cert_abc\",\n \"name\": \"My Example Certificate\",\n \"active\": false,\n \"created_at\": 1234567,\n \"certificate_details\": {\n \"valid_at\": 12345667,\n \"expires_at\": 12345678\n }\n },\n {\n \"object\": \"organization.project.certificate\",\n \"id\": \"cert_def\",\n \"name\": \"My Example Certificate 2\",\n \"active\": false,\n \"created_at\": 1234567,\n \"certificate_details\": {\n \"valid_at\": 12345667,\n \"expires_at\": 12345678\n }\n },\n ],\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/data_retention": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves project data retention controls.", + "operationId": "retrieve-project-data-retention", + "tags": [ + "Data retention" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to inspect.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project data retention controls retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectDataRetention" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve project data retention", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/data_retention \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"project.data_retention\",\n \"type\": \"organization_default\"\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Updates project data retention controls.", + "operationId": "update-project-data-retention", + "tags": [ + "Data retention" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to update.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The desired project data retention setting.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProjectDataRetentionBody" + } + } + } + }, + "responses": { + "200": { + "description": "Project data retention controls updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectDataRetention" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Update project data retention", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects/proj_abc/data_retention \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"retention_type\": \"modified_abuse_monitoring\"\n }'\n" + }, + "response": "{\n \"object\": \"project.data_retention\",\n \"type\": \"modified_abuse_monitoring\"\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/groups": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Lists the groups that have access to a project.", + "operationId": "list-project-groups", + "tags": [ + "Project groups" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of project groups to return. Defaults to 20.", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 20 + } + }, + { + "name": "after", + "in": "query", + "description": "Cursor for pagination. Provide the ID of the last group from the previous response to fetch the next page.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for the returned groups.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + } + ], + "responses": { + "200": { + "description": "Project groups listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectGroupListResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List project groups", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc123/groups?limit=20 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"project.group\",\n \"project_id\": \"proj_abc123\",\n \"group_id\": \"group_01J1F8ABCDXYZ\",\n \"group_name\": \"Support Team\",\n \"created_at\": 1711471533\n }\n ],\n \"has_more\": false,\n \"next\": null\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Grants a group access to a project.", + "operationId": "add-project-group", + "tags": [ + "Project groups" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to update.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Identifies the group and role to assign to the project.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InviteProjectGroupBody" + } + } + } + }, + "responses": { + "200": { + "description": "Group granted access to the project successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectGroup" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Add project group", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects/proj_abc123/groups \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"group_id\": \"group_01J1F8ABCDXYZ\",\n \"role\": \"role_01J1F8PROJ\"\n }'\n" + }, + "response": "{\n \"object\": \"project.group\",\n \"project_id\": \"proj_abc123\",\n \"group_id\": \"group_01J1F8ABCDXYZ\",\n \"group_name\": \"Support Team\",\n \"created_at\": 1711471533\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/groups/{group_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves a project's group.", + "operationId": "retrieve-project-group", + "tags": [ + "Project groups" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "group_id", + "in": "path", + "description": "The ID of the group to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "group_type", + "in": "query", + "description": "The type of group to retrieve.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "group", + "tenant_group" + ], + "default": "group" + } + } + ], + "responses": { + "200": { + "description": "Project group retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectGroup" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve project group", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc123/groups/group_01J1F8ABCDXYZ \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"project.group\",\n \"project_id\": \"proj_abc123\",\n \"group_id\": \"group_01J1F8ABCDXYZ\",\n \"group_name\": \"Support Team\",\n \"group_type\": \"group\",\n \"created_at\": 1711471533\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Revokes a group's access to a project.", + "operationId": "remove-project-group", + "tags": [ + "Project groups" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to update.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "group_id", + "in": "path", + "description": "The ID of the group to remove from the project.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Group removed from the project successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectGroupDeletedResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Remove project group", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/projects/proj_abc123/groups/group_01J1F8ABCDXYZ \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"project.group.deleted\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/hosted_tool_permissions": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Returns hosted tool permissions for a project.", + "operationId": "retrieve-project-hosted-tool-permissions", + "tags": [ + "Hosted tools" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project hosted tool permissions retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectHostedToolPermissions" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve project hosted tool permissions", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/hosted_tool_permissions \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"file_search\": {\n \"enabled\": true\n },\n \"web_search\": {\n \"enabled\": true\n },\n \"image_generation\": {\n \"enabled\": true\n },\n \"mcp\": {\n \"enabled\": true\n },\n \"code_interpreter\": {\n \"enabled\": true\n }\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Updates hosted tool permissions for a project.", + "operationId": "update-project-hosted-tool-permissions", + "tags": [ + "Hosted tools" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The project hosted tool permissions update request payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectHostedToolPermissionsUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Project hosted tool permissions updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectHostedToolPermissions" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Modify project hosted tool permissions", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects/proj_abc/hosted_tool_permissions \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"file_search\": {\n \"enabled\": true\n },\n \"image_generation\": {\n \"enabled\": false\n }\n }'\n" + }, + "response": "{\n \"file_search\": {\n \"enabled\": true\n },\n \"web_search\": {\n \"enabled\": true\n },\n \"image_generation\": {\n \"enabled\": false\n },\n \"mcp\": {\n \"enabled\": true\n },\n \"code_interpreter\": {\n \"enabled\": true\n }\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/model_permissions": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Returns model permissions for a project.", + "operationId": "retrieve-project-model-permissions", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project model permissions retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectModelPermissions" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve project model permissions", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/model_permissions \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"project.model_permissions\",\n \"mode\": \"allow_list\",\n \"model_ids\": [\n \"gpt-6-astra\",\n \"o3\"\n ]\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Updates model permissions for a project.", + "operationId": "update-project-model-permissions", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The project model permissions update request payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectModelPermissionsUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Project model permissions updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectModelPermissions" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Modify project model permissions", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects/proj_abc/model_permissions \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"mode\": \"deny_list\",\n \"model_ids\": [\n \"o3\"\n ]\n }'\n" + }, + "response": "{\n \"object\": \"project.model_permissions\",\n \"mode\": \"deny_list\",\n \"model_ids\": [\n \"o3\"\n ]\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Deletes model permissions for a project.", + "operationId": "delete-project-model-permissions", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project model permissions deleted successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectModelPermissionsDeleteResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete project model permissions", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/projects/proj_abc/model_permissions \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"project.model_permissions.deleted\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/rate_limits": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Returns the rate limits per model for a project.", + "operationId": "list-project-rate-limits", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. The default is 100.\n", + "required": false, + "schema": { + "type": "integer", + "default": 100 + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "before", + "in": "query", + "description": "A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, beginning with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project rate limits listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectRateLimitListResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List project rate limits", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/rate_limits?after=rl_xxx&limit=20 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"project.rate_limit\",\n \"id\": \"rl-ada\",\n \"model\": \"ada\",\n \"max_requests_per_1_minute\": 600,\n \"max_tokens_per_1_minute\": 150000,\n \"max_images_per_1_minute\": 10\n }\n ],\n \"first_id\": \"rl-ada\",\n \"last_id\": \"rl-ada\",\n \"has_more\": false\n}\n", + "error_response": "{\n \"code\": 404,\n \"message\": \"The project {project_id} was not found\"\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/rate_limits/{rate_limit_id}": { + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Updates a project rate limit.", + "operationId": "update-project-rate-limits", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "rate_limit_id", + "in": "path", + "description": "The ID of the rate limit.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The project rate limit update request payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectRateLimitUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Project rate limit updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectRateLimit" + } + } + } + }, + "400": { + "description": "Error response for various conditions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Modify project rate limit", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects/proj_abc/rate_limits/rl_xxx \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"max_requests_per_1_minute\": 500\n }'\n" + }, + "response": "{\n \"object\": \"project.rate_limit\",\n \"id\": \"rl-ada\",\n \"model\": \"ada\",\n \"max_requests_per_1_minute\": 600,\n \"max_tokens_per_1_minute\": 150000,\n \"max_images_per_1_minute\": 10\n }\n", + "error_response": "{\n \"code\": 404,\n \"message\": \"The project {project_id} was not found\"\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/service_accounts": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Returns a list of service accounts in the project.", + "operationId": "list-project-service-accounts", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project service accounts listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectServiceAccountListResponse" + } + } + } + }, + "400": { + "description": "Error response when project is archived.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List project service accounts", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/service_accounts?after=custom_id&limit=20 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"organization.project.service_account\",\n \"id\": \"svc_acct_abc\",\n \"name\": \"Service Account\",\n \"role\": \"owner\",\n \"created_at\": 1711471533\n }\n ],\n \"first_id\": \"svc_acct_abc\",\n \"last_id\": \"svc_acct_xyz\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Creates a new service account in the project. By default, this also returns an unredacted API key for the service account.", + "operationId": "create-project-service-account", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The project service account create request payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectServiceAccountCreateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Project service account created successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectServiceAccountCreateResponse" + } + } + } + }, + "400": { + "description": "Error response when project is archived.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create project service account", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects/proj_abc/service_accounts \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Production App\"\n }'\n" + }, + "response": "{\n \"object\": \"organization.project.service_account\",\n \"id\": \"svc_acct_abc\",\n \"name\": \"Production App\",\n \"role\": \"member\",\n \"created_at\": 1711471533,\n \"api_key\": {\n \"object\": \"organization.project.service_account.api_key\",\n \"value\": \"sk-abcdefghijklmnop123\",\n \"name\": \"Secret Key\",\n \"created_at\": 1711471533,\n \"id\": \"key_abc\"\n }\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/service_accounts/{service_account_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves a service account in the project.", + "operationId": "retrieve-project-service-account", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "service_account_id", + "in": "path", + "description": "The ID of the service account.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project service account retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectServiceAccount" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve project service account", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/service_accounts/svc_acct_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"organization.project.service_account\",\n \"id\": \"svc_acct_abc\",\n \"name\": \"Service Account\",\n \"role\": \"owner\",\n \"created_at\": 1711471533\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Updates a service account in the project.", + "operationId": "update-project-service-account", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "service_account_id", + "in": "path", + "description": "The ID of the service account.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Fields to update on the service account.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProjectServiceAccountBody" + } + } + } + }, + "responses": { + "200": { + "description": "Project service account updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectServiceAccount" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Update project service account", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects/proj_abc/service_accounts/svc_acct_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Updated service account\",\n \"role\": \"member\"\n }'\n" + }, + "response": "{\n \"object\": \"organization.project.service_account\",\n \"id\": \"svc_acct_abc\",\n \"name\": \"Updated service account\",\n \"role\": \"member\",\n \"created_at\": 1711471533\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Deletes a service account from the project.\n\nReturns confirmation of service account deletion, or an error if the project\nis archived (archived projects have no service accounts).\n", + "operationId": "delete-project-service-account", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "service_account_id", + "in": "path", + "description": "The ID of the service account.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project service account deleted successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectServiceAccountDeleteResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete project service account", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/projects/proj_abc/service_accounts/svc_acct_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"organization.project.service_account.deleted\",\n \"id\": \"svc_acct_abc\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/spend_alerts": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Lists project spend alerts.", + "operationId": "list-project-spend-alerts", + "tags": [ + "Spend alerts" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of spend alerts to return. Defaults to 20.", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for the returned spend alerts.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + }, + { + "name": "after", + "in": "query", + "description": "Cursor for pagination. Provide the ID of the last spend alert from the previous response to fetch the next page.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "before", + "in": "query", + "description": "Cursor for pagination. Provide the ID of the first spend alert from the previous response to fetch the previous page.", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project spend alerts listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectSpendAlertListResource" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List project spend alerts", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/spend_alerts?limit=20&order=asc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"alert_abc123\",\n \"object\": \"project.spend_alert\",\n \"threshold_amount\": 100000,\n \"currency\": \"USD\",\n \"interval\": \"month\",\n \"notification_channel\": {\n \"type\": \"email\",\n \"recipients\": [\"finance@example.com\"],\n \"subject_prefix\": \"OpenAI spend alert\"\n }\n }\n ],\n \"first_id\": \"alert_abc123\",\n \"last_id\": \"alert_abc123\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Creates a project spend alert.", + "operationId": "create-project-spend-alert", + "tags": [ + "Spend alerts" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to update.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Parameters for the project spend alert you want to create.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSpendAlertBody" + } + } + } + }, + "responses": { + "200": { + "description": "Project spend alert created successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectSpendAlert" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create project spend alert", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects/proj_abc/spend_alerts \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"threshold_amount\": 100000,\n \"currency\": \"USD\",\n \"interval\": \"month\",\n \"notification_channel\": {\n \"type\": \"email\",\n \"recipients\": [\"finance@example.com\"],\n \"subject_prefix\": \"OpenAI spend alert\"\n }\n }'\n" + }, + "response": "{\n \"id\": \"alert_abc123\",\n \"object\": \"project.spend_alert\",\n \"threshold_amount\": 100000,\n \"currency\": \"USD\",\n \"interval\": \"month\",\n \"notification_channel\": {\n \"type\": \"email\",\n \"recipients\": [\"finance@example.com\"],\n \"subject_prefix\": \"OpenAI spend alert\"\n }\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/spend_alerts/{alert_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves a project spend alert.", + "operationId": "retrieve-project-spend-alert", + "tags": [ + "Spend alerts" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "alert_id", + "in": "path", + "description": "The ID of the spend alert to retrieve.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project spend alert retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectSpendAlert" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve project spend alert", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/spend_alerts/alert_abc123 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"id\": \"alert_abc123\",\n \"object\": \"project.spend_alert\",\n \"threshold_amount\": 150000,\n \"currency\": \"USD\",\n \"interval\": \"month\",\n \"notification_channel\": {\n \"type\": \"email\",\n \"recipients\": [\"finance@example.com\"],\n \"subject_prefix\": \"OpenAI spend alert\"\n }\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Updates a project spend alert.", + "operationId": "update-project-spend-alert", + "tags": [ + "Spend alerts" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to update.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "alert_id", + "in": "path", + "description": "The ID of the spend alert to update.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Fields to update on the project spend alert.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSpendAlertBody" + } + } + } + }, + "responses": { + "200": { + "description": "Project spend alert updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectSpendAlert" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Update project spend alert", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects/proj_abc/spend_alerts/alert_abc123 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"threshold_amount\": 150000,\n \"currency\": \"USD\",\n \"interval\": \"month\",\n \"notification_channel\": {\n \"type\": \"email\",\n \"recipients\": [\"finance@example.com\"],\n \"subject_prefix\": \"OpenAI spend alert\"\n }\n }'\n" + }, + "response": "{\n \"id\": \"alert_abc123\",\n \"object\": \"project.spend_alert\",\n \"threshold_amount\": 150000,\n \"currency\": \"USD\",\n \"interval\": \"month\",\n \"notification_channel\": {\n \"type\": \"email\",\n \"recipients\": [\"finance@example.com\"],\n \"subject_prefix\": \"OpenAI spend alert\"\n }\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Deletes a project spend alert.", + "operationId": "delete-project-spend-alert", + "tags": [ + "Spend alerts" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to update.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "alert_id", + "in": "path", + "description": "The ID of the spend alert to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project spend alert deleted successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectSpendAlertDeletedResource" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete project spend alert", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/projects/proj_abc/spend_alerts/alert_abc123 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"id\": \"alert_abc123\",\n \"object\": \"project.spend_alert.deleted\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/users": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Returns a list of users in the project.", + "operationId": "list-project-users", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project users listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectUserListResponse" + } + } + } + }, + "400": { + "description": "Error response when project is archived.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List project users", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/users?after=user_abc&limit=20 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"organization.project.user\",\n \"id\": \"user_abc\",\n \"name\": \"First Last\",\n \"email\": \"user@example.com\",\n \"role\": \"owner\",\n \"added_at\": 1711471533\n }\n ],\n \"first_id\": \"user-abc\",\n \"last_id\": \"user-xyz\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Adds a user to the project. Users must already be members of the organization to be added to a project.", + "operationId": "create-project-user", + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "tags": [ + "Projects" + ], + "requestBody": { + "description": "The project user create request payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectUserCreateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "User added to project successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectUser" + } + } + } + }, + "400": { + "description": "Error response for various conditions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create project user", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects/proj_abc/users \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"user_id\": \"user_abc\",\n \"role\": \"member\"\n }'\n" + }, + "response": "{\n \"object\": \"organization.project.user\",\n \"id\": \"user_abc\",\n \"email\": \"user@example.com\",\n \"role\": \"owner\",\n \"added_at\": 1711471533\n}\n" + } + } + } + }, + "/organization/projects/{project_id}/users/{user_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves a user in the project.", + "operationId": "retrieve-project-user", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "user_id", + "in": "path", + "description": "The ID of the user.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project user retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectUser" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve project user", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/projects/proj_abc/users/user_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"organization.project.user\",\n \"id\": \"user_abc\",\n \"name\": \"First Last\",\n \"email\": \"user@example.com\",\n \"role\": \"owner\",\n \"added_at\": 1711471533\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Modifies a user's role in the project.", + "operationId": "modify-project-user", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "user_id", + "in": "path", + "description": "The ID of the user.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The project user update request payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectUserUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Project user's role updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectUser" + } + } + } + }, + "400": { + "description": "Error response for various conditions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Modify project user", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/projects/proj_abc/users/user_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"role\": \"owner\"\n }'\n" + }, + "response": "{\n \"object\": \"organization.project.user\",\n \"id\": \"user_abc\",\n \"name\": \"First Last\",\n \"email\": \"user@example.com\",\n \"role\": \"owner\",\n \"added_at\": 1711471533\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Deletes a user from the project.\n\nReturns confirmation of project user deletion, or an error if the project is\narchived (archived projects have no users).\n", + "operationId": "delete-project-user", + "tags": [ + "Projects" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "user_id", + "in": "path", + "description": "The ID of the user.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project user deleted successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectUserDeleteResponse" + } + } + } + }, + "400": { + "description": "Error response for various conditions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete project user", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/projects/proj_abc/users/user_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"organization.project.user.deleted\",\n \"id\": \"user_abc\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/roles": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Lists the roles configured for the organization.", + "operationId": "list-roles", + "tags": [ + "Roles" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "A limit on the number of roles to return. Defaults to 1000.", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000, + "default": 1000 + } + }, + { + "name": "after", + "in": "query", + "description": "Cursor for pagination. Provide the value from the previous response's `next` field to continue listing roles.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for the returned roles.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + } + ], + "responses": { + "200": { + "description": "Roles listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicRoleListResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List organization roles", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/roles?limit=20 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"role\",\n \"id\": \"role_01J1F8ROLE01\",\n \"name\": \"API Group Manager\",\n \"description\": \"Allows managing organization groups\",\n \"permissions\": [\n \"api.groups.read\",\n \"api.groups.write\"\n ],\n \"resource_type\": \"api.organization\",\n \"predefined_role\": false\n }\n ],\n \"has_more\": false,\n \"next\": null\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Creates a custom role for the organization.", + "operationId": "create-role", + "tags": [ + "Roles" + ], + "requestBody": { + "description": "Parameters for the role you want to create.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicCreateOrganizationRoleBody" + } + } + } + }, + "responses": { + "200": { + "description": "Role created successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Role" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Create organization role", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/roles \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"role_name\": \"API Group Manager\",\n \"permissions\": [\n \"api.groups.read\",\n \"api.groups.write\"\n ],\n \"description\": \"Allows managing organization groups\"\n }'\n" + }, + "response": "{\n \"object\": \"role\",\n \"id\": \"role_01J1F8ROLE01\",\n \"name\": \"API Group Manager\",\n \"description\": \"Allows managing organization groups\",\n \"permissions\": [\n \"api.groups.read\",\n \"api.groups.write\"\n ],\n \"resource_type\": \"api.organization\",\n \"predefined_role\": false\n}\n" + } + } + } + }, + "/organization/roles/{role_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves an organization role.", + "operationId": "retrieve-role", + "tags": [ + "Roles" + ], + "parameters": [ + { + "name": "role_id", + "in": "path", + "description": "The ID of the role to retrieve.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Role retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Role" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve organization role", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/roles/role_01J1F8ROLE01 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"role\",\n \"id\": \"role_01J1F8ROLE01\",\n \"name\": \"API Group Manager\",\n \"description\": \"Allows managing organization groups\",\n \"permissions\": [\n \"api.groups.read\",\n \"api.groups.write\"\n ],\n \"resource_type\": \"api.organization\",\n \"predefined_role\": false\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Updates an existing organization role.", + "operationId": "update-role", + "tags": [ + "Roles" + ], + "parameters": [ + { + "name": "role_id", + "in": "path", + "description": "The ID of the role to update.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Fields to update on the role.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicUpdateOrganizationRoleBody" + } + } + } + }, + "responses": { + "200": { + "description": "Role updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Role" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Update organization role", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/roles/role_01J1F8ROLE01 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"role_name\": \"API Group Manager\",\n \"permissions\": [\n \"api.groups.read\",\n \"api.groups.write\"\n ],\n \"description\": \"Allows managing organization groups\"\n }'\n" + }, + "response": "{\n \"object\": \"role\",\n \"id\": \"role_01J1F8ROLE01\",\n \"name\": \"API Group Manager\",\n \"description\": \"Allows managing organization groups\",\n \"permissions\": [\n \"api.groups.read\",\n \"api.groups.write\"\n ],\n \"resource_type\": \"api.organization\",\n \"predefined_role\": false\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Deletes a custom role from the organization.", + "operationId": "delete-role", + "tags": [ + "Roles" + ], + "parameters": [ + { + "name": "role_id", + "in": "path", + "description": "The ID of the role to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Role deleted successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleDeletedResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Delete organization role", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/roles/role_01J1F8ROLE01 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"role.deleted\",\n \"id\": \"role_01J1F8ROLE01\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/spend_alerts": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Lists organization spend alerts.", + "operationId": "list-organization-spend-alerts", + "tags": [ + "Spend alerts" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "A limit on the number of spend alerts to return. Defaults to 20.", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for the returned spend alerts.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + }, + { + "name": "after", + "in": "query", + "description": "Cursor for pagination. Provide the ID of the last spend alert from the previous response to fetch the next page.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "before", + "in": "query", + "description": "Cursor for pagination. Provide the ID of the first spend alert from the previous response to fetch the previous page.", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Organization spend alerts listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationSpendAlertListResource" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List organization spend alerts", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/spend_alerts?limit=20&order=asc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"alert_abc123\",\n \"object\": \"organization.spend_alert\",\n \"threshold_amount\": 100000,\n \"currency\": \"USD\",\n \"interval\": \"month\",\n \"notification_channel\": {\n \"type\": \"email\",\n \"recipients\": [\"finance@example.com\"],\n \"subject_prefix\": \"OpenAI spend alert\"\n }\n }\n ],\n \"first_id\": \"alert_abc123\",\n \"last_id\": \"alert_abc123\",\n \"has_more\": false\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Creates an organization spend alert.", + "operationId": "create-organization-spend-alert", + "tags": [ + "Spend alerts" + ], + "requestBody": { + "description": "Parameters for the organization spend alert you want to create.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSpendAlertBody" + } + } + } + }, + "responses": { + "200": { + "description": "Organization spend alert created successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationSpendAlert" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create organization spend alert", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/spend_alerts \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"threshold_amount\": 100000,\n \"currency\": \"USD\",\n \"interval\": \"month\",\n \"notification_channel\": {\n \"type\": \"email\",\n \"recipients\": [\"finance@example.com\"],\n \"subject_prefix\": \"OpenAI spend alert\"\n }\n }'\n" + }, + "response": "{\n \"id\": \"alert_abc123\",\n \"object\": \"organization.spend_alert\",\n \"threshold_amount\": 100000,\n \"currency\": \"USD\",\n \"interval\": \"month\",\n \"notification_channel\": {\n \"type\": \"email\",\n \"recipients\": [\"finance@example.com\"],\n \"subject_prefix\": \"OpenAI spend alert\"\n }\n}\n" + } + } + } + }, + "/organization/spend_alerts/{alert_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves an organization spend alert.", + "operationId": "retrieve-organization-spend-alert", + "tags": [ + "Spend alerts" + ], + "parameters": [ + { + "name": "alert_id", + "in": "path", + "description": "The ID of the spend alert to retrieve.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Organization spend alert retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationSpendAlert" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve organization spend alert", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/spend_alerts/alert_abc123 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"id\": \"alert_abc123\",\n \"object\": \"organization.spend_alert\",\n \"threshold_amount\": 150000,\n \"currency\": \"USD\",\n \"interval\": \"month\",\n \"notification_channel\": {\n \"type\": \"email\",\n \"recipients\": [\"finance@example.com\"],\n \"subject_prefix\": \"OpenAI spend alert\"\n }\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Updates an organization spend alert.", + "operationId": "update-organization-spend-alert", + "tags": [ + "Spend alerts" + ], + "parameters": [ + { + "name": "alert_id", + "in": "path", + "description": "The ID of the spend alert to update.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Fields to update on the organization spend alert.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSpendAlertBody" + } + } + } + }, + "responses": { + "200": { + "description": "Organization spend alert updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationSpendAlert" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Update organization spend alert", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/spend_alerts/alert_abc123 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"threshold_amount\": 150000,\n \"currency\": \"USD\",\n \"interval\": \"month\",\n \"notification_channel\": {\n \"type\": \"email\",\n \"recipients\": [\"finance@example.com\"],\n \"subject_prefix\": \"OpenAI spend alert\"\n }\n }'\n" + }, + "response": "{\n \"id\": \"alert_abc123\",\n \"object\": \"organization.spend_alert\",\n \"threshold_amount\": 150000,\n \"currency\": \"USD\",\n \"interval\": \"month\",\n \"notification_channel\": {\n \"type\": \"email\",\n \"recipients\": [\"finance@example.com\"],\n \"subject_prefix\": \"OpenAI spend alert\"\n }\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Deletes an organization spend alert.", + "operationId": "delete-organization-spend-alert", + "tags": [ + "Spend alerts" + ], + "parameters": [ + { + "name": "alert_id", + "in": "path", + "description": "The ID of the spend alert to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Organization spend alert deleted successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationSpendAlertDeletedResource" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete organization spend alert", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/spend_alerts/alert_abc123 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"id\": \"alert_abc123\",\n \"object\": \"organization.spend_alert.deleted\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/usage/audio_speeches": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Get audio speeches usage details for the organization.", + "operationId": "usage-audio-speeches", + "tags": [ + "Usage" + ], + "parameters": [ + { + "name": "start_time", + "in": "query", + "description": "Start time (Unix seconds) of the query time range, inclusive.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "end_time", + "in": "query", + "description": "End time (Unix seconds) of the query time range, exclusive.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "bucket_width", + "in": "query", + "description": "Width of each time bucket in response. Currently `1m`, `1h` and `1d` are supported, default to `1d`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "1m", + "1h", + "1d" + ], + "default": "1d" + } + }, + { + "name": "project_ids", + "in": "query", + "description": "Return only usage for these projects.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "user_ids", + "in": "query", + "description": "Return only usage for these users.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "api_key_ids", + "in": "query", + "description": "Return only usage for these API keys.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "models", + "in": "query", + "description": "Return only usage for these models.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "group_by", + "in": "query", + "description": "Group the usage data by the specified fields. Support fields include `project_id`, `user_id`, `api_key_id`, `model` or any combination of them.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "project_id", + "user_id", + "api_key_id", + "model" + ] + } + } + }, + { + "name": "limit", + "in": "query", + "description": "Specifies the number of buckets to return.\n- `bucket_width=1d`: default: 7, max: 31\n- `bucket_width=1h`: default: 24, max: 168\n- `bucket_width=1m`: default: 60, max: 1440\n", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "description": "A cursor for use in pagination. Corresponding to the `next_page` field from the previous response.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Usage data retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Audio speeches", + "group": "usage-audio-speeches", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/organization/usage/audio_speeches?start_time=1730419200&limit=1\" \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"page\",\n \"data\": [\n {\n \"object\": \"bucket\",\n \"start_time\": 1730419200,\n \"end_time\": 1730505600,\n \"results\": [\n {\n \"object\": \"organization.usage.audio_speeches.result\",\n \"characters\": 45,\n \"num_model_requests\": 1,\n \"project_id\": null,\n \"user_id\": null,\n \"api_key_id\": null,\n \"model\": null\n }\n ]\n }\n ],\n \"has_more\": false,\n \"next_page\": null\n}\n" + } + } + } + }, + "/organization/usage/audio_transcriptions": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Get audio transcriptions usage details for the organization.", + "operationId": "usage-audio-transcriptions", + "tags": [ + "Usage" + ], + "parameters": [ + { + "name": "start_time", + "in": "query", + "description": "Start time (Unix seconds) of the query time range, inclusive.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "end_time", + "in": "query", + "description": "End time (Unix seconds) of the query time range, exclusive.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "bucket_width", + "in": "query", + "description": "Width of each time bucket in response. Currently `1m`, `1h` and `1d` are supported, default to `1d`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "1m", + "1h", + "1d" + ], + "default": "1d" + } + }, + { + "name": "project_ids", + "in": "query", + "description": "Return only usage for these projects.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "user_ids", + "in": "query", + "description": "Return only usage for these users.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "api_key_ids", + "in": "query", + "description": "Return only usage for these API keys.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "models", + "in": "query", + "description": "Return only usage for these models.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "group_by", + "in": "query", + "description": "Group the usage data by the specified fields. Support fields include `project_id`, `user_id`, `api_key_id`, `model` or any combination of them.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "project_id", + "user_id", + "api_key_id", + "model" + ] + } + } + }, + { + "name": "limit", + "in": "query", + "description": "Specifies the number of buckets to return.\n- `bucket_width=1d`: default: 7, max: 31\n- `bucket_width=1h`: default: 24, max: 168\n- `bucket_width=1m`: default: 60, max: 1440\n", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "description": "A cursor for use in pagination. Corresponding to the `next_page` field from the previous response.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Usage data retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Audio transcriptions", + "group": "usage-audio-transcriptions", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/organization/usage/audio_transcriptions?start_time=1730419200&limit=1\" \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"page\",\n \"data\": [\n {\n \"object\": \"bucket\",\n \"start_time\": 1730419200,\n \"end_time\": 1730505600,\n \"results\": [\n {\n \"object\": \"organization.usage.audio_transcriptions.result\",\n \"seconds\": 20,\n \"num_model_requests\": 1,\n \"project_id\": null,\n \"user_id\": null,\n \"api_key_id\": null,\n \"model\": null\n }\n ]\n }\n ],\n \"has_more\": false,\n \"next_page\": null\n}\n" + } + } + } + }, + "/organization/usage/code_interpreter_sessions": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Get code interpreter sessions usage details for the organization.", + "operationId": "usage-code-interpreter-sessions", + "tags": [ + "Usage" + ], + "parameters": [ + { + "name": "start_time", + "in": "query", + "description": "Start time (Unix seconds) of the query time range, inclusive.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "end_time", + "in": "query", + "description": "End time (Unix seconds) of the query time range, exclusive.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "bucket_width", + "in": "query", + "description": "Width of each time bucket in response. Currently `1m`, `1h` and `1d` are supported, default to `1d`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "1m", + "1h", + "1d" + ], + "default": "1d" + } + }, + { + "name": "project_ids", + "in": "query", + "description": "Return only usage for these projects.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "group_by", + "in": "query", + "description": "Group the usage data by the specified fields. Support fields include `project_id`.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "project_id" + ] + } + } + }, + { + "name": "limit", + "in": "query", + "description": "Specifies the number of buckets to return.\n- `bucket_width=1d`: default: 7, max: 31\n- `bucket_width=1h`: default: 24, max: 168\n- `bucket_width=1m`: default: 60, max: 1440\n", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "description": "A cursor for use in pagination. Corresponding to the `next_page` field from the previous response.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Usage data retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Code interpreter sessions", + "group": "usage-code-interpreter-sessions", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/organization/usage/code_interpreter_sessions?start_time=1730419200&limit=1\" \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"page\",\n \"data\": [\n {\n \"object\": \"bucket\",\n \"start_time\": 1730419200,\n \"end_time\": 1730505600,\n \"results\": [\n {\n \"object\": \"organization.usage.code_interpreter_sessions.result\",\n \"num_sessions\": 1,\n \"project_id\": null\n }\n ]\n }\n ],\n \"has_more\": false,\n \"next_page\": null\n}\n" + } + } + } + }, + "/organization/usage/completions": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Get completions usage details for the organization.", + "operationId": "usage-completions", + "tags": [ + "Usage" + ], + "parameters": [ + { + "name": "start_time", + "in": "query", + "description": "Start time (Unix seconds) of the query time range, inclusive.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "end_time", + "in": "query", + "description": "End time (Unix seconds) of the query time range, exclusive.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "bucket_width", + "in": "query", + "description": "Width of each time bucket in response. Currently `1m`, `1h` and `1d` are supported, default to `1d`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "1m", + "1h", + "1d" + ], + "default": "1d" + } + }, + { + "name": "project_ids", + "in": "query", + "description": "Return only usage for these projects.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "user_ids", + "in": "query", + "description": "Return only usage for these users.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "api_key_ids", + "in": "query", + "description": "Return only usage for these API keys.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "models", + "in": "query", + "description": "Return only usage for these models.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "batch", + "in": "query", + "description": "If `true`, return batch jobs only. If `false`, return non-batch jobs only. By default, return both.\n", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "group_by", + "in": "query", + "description": "Group the usage data by the specified fields. Support fields include `project_id`, `user_id`, `api_key_id`, `model`, `batch`, `service_tier` or any combination of them.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "project_id", + "user_id", + "api_key_id", + "model", + "batch", + "service_tier" + ] + } + } + }, + { + "name": "limit", + "in": "query", + "description": "Specifies the number of buckets to return.\n- `bucket_width=1d`: default: 7, max: 31\n- `bucket_width=1h`: default: 24, max: 168\n- `bucket_width=1m`: default: 60, max: 1440\n", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "description": "A cursor for use in pagination. Corresponding to the `next_page` field from the previous response.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Usage data retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Completions", + "group": "usage-completions", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/organization/usage/completions?start_time=1730419200&limit=1\" \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"page\",\n \"data\": [\n {\n \"object\": \"bucket\",\n \"start_time\": 1730419200,\n \"end_time\": 1730505600,\n \"results\": [\n {\n \"object\": \"organization.usage.completions.result\",\n \"input_tokens\": 1000,\n \"input_cached_tokens\": 400,\n \"input_cache_write_tokens\": 100,\n \"input_uncached_tokens\": 500,\n \"output_tokens\": 500,\n \"input_text_tokens\": 400,\n \"output_text_tokens\": 400,\n \"input_cached_text_tokens\": 300,\n \"input_audio_tokens\": 50,\n \"input_cached_audio_tokens\": 50,\n \"output_audio_tokens\": 50,\n \"input_image_tokens\": 50,\n \"input_cached_image_tokens\": 50,\n \"output_image_tokens\": 50,\n \"num_model_requests\": 5,\n \"project_id\": null,\n \"user_id\": null,\n \"api_key_id\": null,\n \"model\": null,\n \"batch\": null,\n \"service_tier\": null\n }\n ]\n }\n ],\n \"has_more\": true,\n \"next_page\": \"page_AAAAAGdGxdEiJdKOAAAAAGcqsYA=\"\n}\n" + } + } + } + }, + "/organization/usage/embeddings": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Get embeddings usage details for the organization.", + "operationId": "usage-embeddings", + "tags": [ + "Usage" + ], + "parameters": [ + { + "name": "start_time", + "in": "query", + "description": "Start time (Unix seconds) of the query time range, inclusive.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "end_time", + "in": "query", + "description": "End time (Unix seconds) of the query time range, exclusive.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "bucket_width", + "in": "query", + "description": "Width of each time bucket in response. Currently `1m`, `1h` and `1d` are supported, default to `1d`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "1m", + "1h", + "1d" + ], + "default": "1d" + } + }, + { + "name": "project_ids", + "in": "query", + "description": "Return only usage for these projects.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "user_ids", + "in": "query", + "description": "Return only usage for these users.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "api_key_ids", + "in": "query", + "description": "Return only usage for these API keys.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "models", + "in": "query", + "description": "Return only usage for these models.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "group_by", + "in": "query", + "description": "Group the usage data by the specified fields. Support fields include `project_id`, `user_id`, `api_key_id`, `model` or any combination of them.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "project_id", + "user_id", + "api_key_id", + "model" + ] + } + } + }, + { + "name": "limit", + "in": "query", + "description": "Specifies the number of buckets to return.\n- `bucket_width=1d`: default: 7, max: 31\n- `bucket_width=1h`: default: 24, max: 168\n- `bucket_width=1m`: default: 60, max: 1440\n", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "description": "A cursor for use in pagination. Corresponding to the `next_page` field from the previous response.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Usage data retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Embeddings", + "group": "usage-embeddings", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/organization/usage/embeddings?start_time=1730419200&limit=1\" \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"page\",\n \"data\": [\n {\n \"object\": \"bucket\",\n \"start_time\": 1730419200,\n \"end_time\": 1730505600,\n \"results\": [\n {\n \"object\": \"organization.usage.embeddings.result\",\n \"input_tokens\": 16,\n \"num_model_requests\": 2,\n \"project_id\": null,\n \"user_id\": null,\n \"api_key_id\": null,\n \"model\": null\n }\n ]\n }\n ],\n \"has_more\": false,\n \"next_page\": null\n}\n" + } + } + } + }, + "/organization/usage/file_search_calls": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Get file search calls usage details for the organization.", + "operationId": "usage-file-search-calls", + "tags": [ + "Usage" + ], + "parameters": [ + { + "name": "start_time", + "in": "query", + "description": "Start time (Unix seconds) of the query time range, inclusive.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "end_time", + "in": "query", + "description": "End time (Unix seconds) of the query time range, exclusive.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "bucket_width", + "in": "query", + "description": "Width of each time bucket in response. Currently `1m`, `1h` and `1d` are supported, default to `1d`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "1m", + "1h", + "1d" + ], + "default": "1d" + } + }, + { + "name": "project_ids", + "in": "query", + "description": "Return only usage for these projects.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "user_ids", + "in": "query", + "description": "Return only usage for these users.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "api_key_ids", + "in": "query", + "description": "Return only usage for these API keys.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "vector_store_ids", + "in": "query", + "description": "Return only usage for these vector stores.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "group_by", + "in": "query", + "description": "Group the usage data by the specified fields. Support fields include `project_id`, `user_id`, `api_key_id`, `vector_store_id` or any combination of them.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "project_id", + "user_id", + "api_key_id", + "vector_store_id" + ] + } + } + }, + { + "name": "limit", + "in": "query", + "description": "Specifies the number of buckets to return.\n- `bucket_width=1d`: default: 7, max: 31\n- `bucket_width=1h`: default: 24, max: 168\n- `bucket_width=1m`: default: 60, max: 1440\n", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "description": "A cursor for use in pagination. Corresponding to the `next_page` field from the previous response.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Usage data retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "File search calls", + "group": "usage-file-search-calls", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/organization/usage/file_search_calls?start_time=1730419200&limit=1\" \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"page\",\n \"data\": [\n {\n \"object\": \"bucket\",\n \"start_time\": 1730419200,\n \"end_time\": 1730505600,\n \"results\": [\n {\n \"object\": \"organization.usage.file_searches.result\",\n \"num_requests\": 2,\n \"project_id\": null,\n \"user_id\": null,\n \"api_key_id\": null,\n \"vector_store_id\": null\n }\n ]\n }\n ],\n \"has_more\": false,\n \"next_page\": null\n}\n" + } + } + } + }, + "/organization/usage/images": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Get images usage details for the organization.", + "operationId": "usage-images", + "tags": [ + "Usage" + ], + "parameters": [ + { + "name": "start_time", + "in": "query", + "description": "Start time (Unix seconds) of the query time range, inclusive.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "end_time", + "in": "query", + "description": "End time (Unix seconds) of the query time range, exclusive.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "bucket_width", + "in": "query", + "description": "Width of each time bucket in response. Currently `1m`, `1h` and `1d` are supported, default to `1d`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "1m", + "1h", + "1d" + ], + "default": "1d" + } + }, + { + "name": "sources", + "in": "query", + "description": "Return only usages for these sources. Possible values are `image.generation`, `image.edit`, `image.variation` or any combination of them.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "image.generation", + "image.edit", + "image.variation" + ] + } + } + }, + { + "name": "sizes", + "in": "query", + "description": "Return only usages for these image sizes. Possible values are `256x256`, `512x512`, `1024x1024`, `1792x1792`, `1024x1792` or any combination of them.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "256x256", + "512x512", + "1024x1024", + "1792x1792", + "1024x1792" + ] + } + } + }, + { + "name": "project_ids", + "in": "query", + "description": "Return only usage for these projects.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "user_ids", + "in": "query", + "description": "Return only usage for these users.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "api_key_ids", + "in": "query", + "description": "Return only usage for these API keys.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "models", + "in": "query", + "description": "Return only usage for these models.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "group_by", + "in": "query", + "description": "Group the usage data by the specified fields. Support fields include `project_id`, `user_id`, `api_key_id`, `model`, `size`, `source` or any combination of them.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "project_id", + "user_id", + "api_key_id", + "model", + "size", + "source" + ] + } + } + }, + { + "name": "limit", + "in": "query", + "description": "Specifies the number of buckets to return.\n- `bucket_width=1d`: default: 7, max: 31\n- `bucket_width=1h`: default: 24, max: 168\n- `bucket_width=1m`: default: 60, max: 1440\n", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "description": "A cursor for use in pagination. Corresponding to the `next_page` field from the previous response.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Usage data retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Images", + "group": "usage-images", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/organization/usage/images?start_time=1730419200&limit=1\" \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"page\",\n \"data\": [\n {\n \"object\": \"bucket\",\n \"start_time\": 1730419200,\n \"end_time\": 1730505600,\n \"results\": [\n {\n \"object\": \"organization.usage.images.result\",\n \"images\": 2,\n \"num_model_requests\": 2,\n \"size\": null,\n \"source\": null,\n \"project_id\": null,\n \"user_id\": null,\n \"api_key_id\": null,\n \"model\": null\n }\n ]\n }\n ],\n \"has_more\": false,\n \"next_page\": null\n}\n" + } + } + } + }, + "/organization/usage/moderations": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Get moderations usage details for the organization.", + "operationId": "usage-moderations", + "tags": [ + "Usage" + ], + "parameters": [ + { + "name": "start_time", + "in": "query", + "description": "Start time (Unix seconds) of the query time range, inclusive.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "end_time", + "in": "query", + "description": "End time (Unix seconds) of the query time range, exclusive.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "bucket_width", + "in": "query", + "description": "Width of each time bucket in response. Currently `1m`, `1h` and `1d` are supported, default to `1d`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "1m", + "1h", + "1d" + ], + "default": "1d" + } + }, + { + "name": "project_ids", + "in": "query", + "description": "Return only usage for these projects.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "user_ids", + "in": "query", + "description": "Return only usage for these users.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "api_key_ids", + "in": "query", + "description": "Return only usage for these API keys.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "models", + "in": "query", + "description": "Return only usage for these models.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "group_by", + "in": "query", + "description": "Group the usage data by the specified fields. Support fields include `project_id`, `user_id`, `api_key_id`, `model` or any combination of them.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "project_id", + "user_id", + "api_key_id", + "model" + ] + } + } + }, + { + "name": "limit", + "in": "query", + "description": "Specifies the number of buckets to return.\n- `bucket_width=1d`: default: 7, max: 31\n- `bucket_width=1h`: default: 24, max: 168\n- `bucket_width=1m`: default: 60, max: 1440\n", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "description": "A cursor for use in pagination. Corresponding to the `next_page` field from the previous response.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Usage data retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Moderations", + "group": "usage-moderations", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/organization/usage/moderations?start_time=1730419200&limit=1\" \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"page\",\n \"data\": [\n {\n \"object\": \"bucket\",\n \"start_time\": 1730419200,\n \"end_time\": 1730505600,\n \"results\": [\n {\n \"object\": \"organization.usage.moderations.result\",\n \"input_tokens\": 16,\n \"num_model_requests\": 2,\n \"project_id\": null,\n \"user_id\": null,\n \"api_key_id\": null,\n \"model\": null\n }\n ]\n }\n ],\n \"has_more\": false,\n \"next_page\": null\n}\n" + } + } + } + }, + "/organization/usage/vector_stores": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Get vector stores usage details for the organization.", + "operationId": "usage-vector-stores", + "tags": [ + "Usage" + ], + "parameters": [ + { + "name": "start_time", + "in": "query", + "description": "Start time (Unix seconds) of the query time range, inclusive.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "end_time", + "in": "query", + "description": "End time (Unix seconds) of the query time range, exclusive.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "bucket_width", + "in": "query", + "description": "Width of each time bucket in response. Currently `1m`, `1h` and `1d` are supported, default to `1d`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "1m", + "1h", + "1d" + ], + "default": "1d" + } + }, + { + "name": "project_ids", + "in": "query", + "description": "Return only usage for these projects.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "group_by", + "in": "query", + "description": "Group the usage data by the specified fields. Support fields include `project_id`.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "project_id" + ] + } + } + }, + { + "name": "limit", + "in": "query", + "description": "Specifies the number of buckets to return.\n- `bucket_width=1d`: default: 7, max: 31\n- `bucket_width=1h`: default: 24, max: 168\n- `bucket_width=1m`: default: 60, max: 1440\n", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "description": "A cursor for use in pagination. Corresponding to the `next_page` field from the previous response.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Usage data retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Vector stores", + "group": "usage-vector-stores", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/organization/usage/vector_stores?start_time=1730419200&limit=1\" \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"page\",\n \"data\": [\n {\n \"object\": \"bucket\",\n \"start_time\": 1730419200,\n \"end_time\": 1730505600,\n \"results\": [\n {\n \"object\": \"organization.usage.vector_stores.result\",\n \"usage_bytes\": 1024,\n \"project_id\": null\n }\n ]\n }\n ],\n \"has_more\": false,\n \"next_page\": null\n}\n" + } + } + } + }, + "/organization/usage/web_search_calls": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Get web search calls usage details for the organization.", + "operationId": "usage-web-search-calls", + "tags": [ + "Usage" + ], + "parameters": [ + { + "name": "start_time", + "in": "query", + "description": "Start time (Unix seconds) of the query time range, inclusive.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "end_time", + "in": "query", + "description": "End time (Unix seconds) of the query time range, exclusive.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "bucket_width", + "in": "query", + "description": "Width of each time bucket in response. Currently `1m`, `1h` and `1d` are supported, default to `1d`.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "1m", + "1h", + "1d" + ], + "default": "1d" + } + }, + { + "name": "project_ids", + "in": "query", + "description": "Return only usage for these projects.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "user_ids", + "in": "query", + "description": "Return only usage for these users.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "api_key_ids", + "in": "query", + "description": "Return only usage for these API keys.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "models", + "in": "query", + "description": "Return only usage for these models.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "context_levels", + "in": "query", + "description": "Return only web search usage for these context levels.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ] + } + } + }, + { + "name": "group_by", + "in": "query", + "description": "Group the usage data by the specified fields. Support fields include `project_id`, `user_id`, `api_key_id`, `model`, `context_level` or any combination of them.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "project_id", + "user_id", + "api_key_id", + "model", + "context_level" + ] + } + } + }, + { + "name": "limit", + "in": "query", + "description": "Specifies the number of buckets to return.\n- `bucket_width=1d`: default: 7, max: 31\n- `bucket_width=1h`: default: 24, max: 168\n- `bucket_width=1m`: default: 60, max: 1440\n", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "page", + "in": "query", + "description": "A cursor for use in pagination. Corresponding to the `next_page` field from the previous response.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Usage data retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Web search calls", + "group": "usage-web-search-calls", + "examples": { + "request": { + "curl": "curl \"https://api.openai.com/v1/organization/usage/web_search_calls?start_time=1730419200&limit=1\" \\\n-H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n-H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"page\",\n \"data\": [\n {\n \"object\": \"bucket\",\n \"start_time\": 1730419200,\n \"end_time\": 1730505600,\n \"results\": [\n {\n \"object\": \"organization.usage.web_searches.result\",\n \"num_model_requests\": 2,\n \"num_requests\": 2,\n \"project_id\": null,\n \"user_id\": null,\n \"api_key_id\": null,\n \"model\": null,\n \"context_level\": null\n }\n ]\n }\n ],\n \"has_more\": false,\n \"next_page\": null\n}\n" + } + } + } + }, + "/organization/users": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Lists all of the users in the organization.", + "operationId": "list-users", + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.\n", + "required": false, + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "after", + "in": "query", + "description": "A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "emails", + "in": "query", + "description": "Filter by the email address of users.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "Users listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserListResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "List users", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/users?after=user_abc&limit=20 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"organization.user\",\n \"id\": \"user_abc\",\n \"name\": \"First Last\",\n \"email\": \"user@example.com\",\n \"role\": \"owner\",\n \"added_at\": 1711471533\n }\n ],\n \"first_id\": \"user-abc\",\n \"last_id\": \"user-xyz\",\n \"has_more\": false\n}\n" + } + } + } + }, + "/organization/users/{user_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves a user by their identifier.", + "operationId": "retrieve-user", + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "user_id", + "in": "path", + "description": "The ID of the user.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "User retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Retrieve user", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/users/user_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"organization.user\",\n \"id\": \"user_abc\",\n \"name\": \"First Last\",\n \"email\": \"user@example.com\",\n \"role\": \"owner\",\n \"added_at\": 1711471533\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Modifies a user's role in the organization.", + "operationId": "modify-user", + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "user_id", + "in": "path", + "description": "The ID of the user.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The new user role to modify. This must be one of `owner` or `member`.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserRoleUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "User role updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Modify user", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/users/user_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"role\": \"owner\"\n }'\n" + }, + "response": "{\n \"object\": \"organization.user\",\n \"id\": \"user_abc\",\n \"name\": \"First Last\",\n \"email\": \"user@example.com\",\n \"role\": \"owner\",\n \"added_at\": 1711471533\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Deletes a user from the organization.", + "operationId": "delete-user", + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "user_id", + "in": "path", + "description": "The ID of the user.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "User deleted successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDeleteResponse" + } + } + } + } + }, + "x-oaiMeta": { + "name": "Delete user", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/users/user_abc \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"organization.user.deleted\",\n \"id\": \"user_abc\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/organization/users/{user_id}/roles": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Lists the organization roles assigned to a user within the organization.", + "operationId": "list-user-role-assignments", + "tags": [ + "User organization role assignments" + ], + "parameters": [ + { + "name": "user_id", + "in": "path", + "description": "The ID of the user to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of organization role assignments to return.", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000 + } + }, + { + "name": "after", + "in": "query", + "description": "Cursor for pagination. Provide the value from the previous response's `next` field to continue listing organization roles.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for the returned organization roles.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + } + ], + "responses": { + "200": { + "description": "User organization role assignments listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleListResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List user organization role assignments", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/users/user_abc123/roles \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"role_01J1F8ROLE01\",\n \"name\": \"API Group Manager\",\n \"permissions\": [\n \"api.groups.read\",\n \"api.groups.write\"\n ],\n \"resource_type\": \"api.organization\",\n \"predefined_role\": false,\n \"description\": \"Allows managing organization groups\",\n \"created_at\": 1711471533,\n \"updated_at\": 1711472599,\n \"created_by\": \"user_abc123\",\n \"created_by_user_obj\": {\n \"id\": \"user_abc123\",\n \"name\": \"Ada Lovelace\",\n \"email\": \"ada@example.com\"\n },\n \"metadata\": {}\n }\n ],\n \"has_more\": false,\n \"next\": null\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Assigns an organization role to a user within the organization.", + "operationId": "assign-user-role", + "tags": [ + "User organization role assignments" + ], + "parameters": [ + { + "name": "user_id", + "in": "path", + "description": "The ID of the user that should receive the organization role.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Identifies the organization role to assign to the user.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicAssignOrganizationGroupRoleBody" + } + } + } + }, + "responses": { + "200": { + "description": "Organization role assigned to the user successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserRoleAssignment" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Assign organization role to user", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/organization/users/user_abc123/roles \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"role_id\": \"role_01J1F8ROLE01\"\n }'\n" + }, + "response": "{\n \"object\": \"user.role\",\n \"user\": {\n \"object\": \"organization.user\",\n \"id\": \"user_abc123\",\n \"name\": \"Ada Lovelace\",\n \"email\": \"ada@example.com\",\n \"role\": \"owner\",\n \"added_at\": 1711470000\n },\n \"role\": {\n \"object\": \"role\",\n \"id\": \"role_01J1F8ROLE01\",\n \"name\": \"API Group Manager\",\n \"description\": \"Allows managing organization groups\",\n \"permissions\": [\n \"api.groups.read\",\n \"api.groups.write\"\n ],\n \"resource_type\": \"api.organization\",\n \"predefined_role\": false\n }\n}\n" + } + } + } + }, + "/organization/users/{user_id}/roles/{role_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves an organization role assigned to a user.", + "operationId": "retrieve-user-role", + "tags": [ + "User organization role assignments" + ], + "parameters": [ + { + "name": "user_id", + "in": "path", + "description": "The ID of the user to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "role_id", + "in": "path", + "description": "The ID of the organization role to retrieve for the user.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Organization role retrieved for the user successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssignedRoleDetails" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve user organization role", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/organization/users/user_abc123/roles/role_01J1F8ROLE01 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"id\": \"role_01J1F8ROLE01\",\n \"name\": \"API Group Manager\",\n \"permissions\": [\n \"api.groups.read\",\n \"api.groups.write\"\n ],\n \"resource_type\": \"api.organization\",\n \"predefined_role\": false,\n \"description\": \"Allows managing organization groups\",\n \"created_at\": 1711471533,\n \"updated_at\": 1711472599,\n \"created_by\": \"user_abc123\",\n \"created_by_user_obj\": null,\n \"metadata\": {},\n \"assignment_sources\": null\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Unassigns an organization role from a user within the organization.", + "operationId": "unassign-user-role", + "tags": [ + "User organization role assignments" + ], + "parameters": [ + { + "name": "user_id", + "in": "path", + "description": "The ID of the user to modify.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "role_id", + "in": "path", + "description": "The ID of the organization role to remove from the user.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Organization role unassigned from the user successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedRoleAssignmentResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Unassign organization role from user", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/organization/users/user_abc123/roles/role_01J1F8ROLE01 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"user.role.deleted\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/projects/{project_id}/groups/{group_id}/roles": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Lists the project roles assigned to a group within a project.", + "operationId": "list-project-group-role-assignments", + "tags": [ + "Project group role assignments" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "group_id", + "in": "path", + "description": "The ID of the group to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of project role assignments to return.", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000 + } + }, + { + "name": "after", + "in": "query", + "description": "Cursor for pagination. Provide the value from the previous response's `next` field to continue listing project roles.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for the returned project roles.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + } + ], + "responses": { + "200": { + "description": "Project group role assignments listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleListResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List project group role assignments", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/projects/proj_abc123/groups/group_01J1F8ABCDXYZ/roles \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"role_01J1F8PROJ\",\n \"name\": \"API Project Key Manager\",\n \"permissions\": [\n \"api.organization.projects.api_keys.read\",\n \"api.organization.projects.api_keys.write\"\n ],\n \"resource_type\": \"api.project\",\n \"predefined_role\": false,\n \"description\": \"Allows managing API keys for the project\",\n \"created_at\": 1711471533,\n \"updated_at\": 1711472599,\n \"created_by\": \"user_abc123\",\n \"created_by_user_obj\": {\n \"id\": \"user_abc123\",\n \"name\": \"Ada Lovelace\",\n \"email\": \"ada@example.com\"\n },\n \"metadata\": {}\n }\n ],\n \"has_more\": false,\n \"next\": null\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Assigns a project role to a group within a project.", + "operationId": "assign-project-group-role", + "tags": [ + "Project group role assignments" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to update.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "group_id", + "in": "path", + "description": "The ID of the group that should receive the project role.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Identifies the project role to assign to the group.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicAssignOrganizationGroupRoleBody" + } + } + } + }, + "responses": { + "200": { + "description": "Project role assigned to the group successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupRoleAssignment" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Assign project role to group", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/projects/proj_abc123/groups/group_01J1F8ABCDXYZ/roles \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"role_id\": \"role_01J1F8PROJ\"\n }'\n" + }, + "response": "{\n \"object\": \"group.role\",\n \"group\": {\n \"object\": \"group\",\n \"id\": \"group_01J1F8ABCDXYZ\",\n \"name\": \"Support Team\",\n \"created_at\": 1711471533,\n \"scim_managed\": false\n },\n \"role\": {\n \"object\": \"role\",\n \"id\": \"role_01J1F8PROJ\",\n \"name\": \"API Project Key Manager\",\n \"description\": \"Allows managing API keys for the project\",\n \"permissions\": [\n \"api.organization.projects.api_keys.read\",\n \"api.organization.projects.api_keys.write\"\n ],\n \"resource_type\": \"api.project\",\n \"predefined_role\": false\n }\n}\n" + } + } + } + }, + "/projects/{project_id}/groups/{group_id}/roles/{role_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves a project role assigned to a group.", + "operationId": "retrieve-project-group-role", + "tags": [ + "Project group role assignments" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "group_id", + "in": "path", + "description": "The ID of the group to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "role_id", + "in": "path", + "description": "The ID of the project role to retrieve for the group.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project role retrieved for the group successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssignedRoleDetails" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve project group role", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/projects/proj_abc123/groups/group_01J1F8ABCDXYZ/roles/role_01J1F8PROJ \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"id\": \"role_01J1F8PROJ\",\n \"name\": \"API Project Key Manager\",\n \"permissions\": [\n \"api.organization.projects.api_keys.read\",\n \"api.organization.projects.api_keys.write\"\n ],\n \"resource_type\": \"api.project\",\n \"predefined_role\": false,\n \"description\": \"Allows managing API keys for the project\",\n \"created_at\": 1711471533,\n \"updated_at\": 1711472599,\n \"created_by\": \"user_abc123\",\n \"created_by_user_obj\": null,\n \"metadata\": {},\n \"assignment_sources\": null\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Unassigns a project role from a group within a project.", + "operationId": "unassign-project-group-role", + "tags": [ + "Project group role assignments" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to modify.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "group_id", + "in": "path", + "description": "The ID of the group whose project role assignment should be removed.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "role_id", + "in": "path", + "description": "The ID of the project role to remove from the group.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project role unassigned from the group successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedRoleAssignmentResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Unassign project role from group", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/projects/proj_abc123/groups/group_01J1F8ABCDXYZ/roles/role_01J1F8PROJ \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"group.role.deleted\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/projects/{project_id}/roles": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Lists the roles configured for a project.", + "operationId": "list-project-roles", + "tags": [ + "Roles" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of roles to return. Defaults to 1000.", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000, + "default": 1000 + } + }, + { + "name": "after", + "in": "query", + "description": "Cursor for pagination. Provide the value from the previous response's `next` field to continue listing roles.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for the returned roles.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + } + ], + "responses": { + "200": { + "description": "Project roles listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicRoleListResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List project roles", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/projects/proj_abc123/roles?limit=20 \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"role\",\n \"id\": \"role_01J1F8PROJ\",\n \"name\": \"API Project Key Manager\",\n \"description\": \"Allows managing API keys for the project\",\n \"permissions\": [\n \"api.organization.projects.api_keys.read\",\n \"api.organization.projects.api_keys.write\"\n ],\n \"resource_type\": \"api.project\",\n \"predefined_role\": false\n }\n ],\n \"has_more\": false,\n \"next\": null\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Creates a custom role for a project.", + "operationId": "create-project-role", + "tags": [ + "Roles" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to update.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Parameters for the project role you want to create.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicCreateOrganizationRoleBody" + } + } + } + }, + "responses": { + "200": { + "description": "Project role created successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Role" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Create project role", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/projects/proj_abc123/roles \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"role_name\": \"API Project Key Manager\",\n \"permissions\": [\n \"api.organization.projects.api_keys.read\",\n \"api.organization.projects.api_keys.write\"\n ],\n \"description\": \"Allows managing API keys for the project\"\n }'\n" + }, + "response": "{\n \"object\": \"role\",\n \"id\": \"role_01J1F8PROJ\",\n \"name\": \"API Project Key Manager\",\n \"description\": \"Allows managing API keys for the project\",\n \"permissions\": [\n \"api.organization.projects.api_keys.read\",\n \"api.organization.projects.api_keys.write\"\n ],\n \"resource_type\": \"api.project\",\n \"predefined_role\": false\n}\n" + } + } + } + }, + "/projects/{project_id}/roles/{role_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves a project role.", + "operationId": "retrieve-project-role", + "tags": [ + "Roles" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "role_id", + "in": "path", + "description": "The ID of the role to retrieve.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project role retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Role" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve project role", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/projects/proj_abc123/roles/role_01J1F8PROJ \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"role\",\n \"id\": \"role_01J1F8PROJ\",\n \"name\": \"API Project Key Manager\",\n \"description\": \"Allows managing API keys for the project\",\n \"permissions\": [\n \"api.organization.projects.api_keys.read\",\n \"api.organization.projects.api_keys.write\"\n ],\n \"resource_type\": \"api.project\",\n \"predefined_role\": false\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Updates an existing project role.", + "operationId": "update-project-role", + "tags": [ + "Roles" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to update.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "role_id", + "in": "path", + "description": "The ID of the role to update.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Fields to update on the project role.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicUpdateOrganizationRoleBody" + } + } + } + }, + "responses": { + "200": { + "description": "Project role updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Role" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Update project role", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/projects/proj_abc123/roles/role_01J1F8PROJ \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"role_name\": \"API Project Key Manager\",\n \"permissions\": [\n \"api.organization.projects.api_keys.read\",\n \"api.organization.projects.api_keys.write\"\n ],\n \"description\": \"Allows managing API keys for the project\"\n }'\n" + }, + "response": "{\n \"object\": \"role\",\n \"id\": \"role_01J1F8PROJ\",\n \"name\": \"API Project Key Manager\",\n \"description\": \"Allows managing API keys for the project\",\n \"permissions\": [\n \"api.organization.projects.api_keys.read\",\n \"api.organization.projects.api_keys.write\"\n ],\n \"resource_type\": \"api.project\",\n \"predefined_role\": false\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Deletes a custom role from a project.", + "operationId": "delete-project-role", + "tags": [ + "Roles" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to update.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "role_id", + "in": "path", + "description": "The ID of the role to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project role deleted successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleDeletedResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Delete project role", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/projects/proj_abc123/roles/role_01J1F8PROJ \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"role.deleted\",\n \"id\": \"role_01J1F8PROJ\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/projects/{project_id}/users/{user_id}/roles": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Lists the project roles assigned to a user within a project.", + "operationId": "list-project-user-role-assignments", + "tags": [ + "Project user role assignments" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "user_id", + "in": "path", + "description": "The ID of the user to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "A limit on the number of project role assignments to return.", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000 + } + }, + { + "name": "after", + "in": "query", + "description": "Cursor for pagination. Provide the value from the previous response's `next` field to continue listing project roles.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "in": "query", + "description": "Sort order for the returned project roles.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + } + ], + "responses": { + "200": { + "description": "Project user role assignments listed successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleListResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "List project user role assignments", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/projects/proj_abc123/users/user_abc123/roles \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"role_01J1F8PROJ\",\n \"name\": \"API Project Key Manager\",\n \"permissions\": [\n \"api.organization.projects.api_keys.read\",\n \"api.organization.projects.api_keys.write\"\n ],\n \"resource_type\": \"api.project\",\n \"predefined_role\": false,\n \"description\": \"Allows managing API keys for the project\",\n \"created_at\": 1711471533,\n \"updated_at\": 1711472599,\n \"created_by\": \"user_abc123\",\n \"created_by_user_obj\": {\n \"id\": \"user_abc123\",\n \"name\": \"Ada Lovelace\",\n \"email\": \"ada@example.com\"\n },\n \"metadata\": {}\n }\n ],\n \"has_more\": false,\n \"next\": null\n}\n" + } + } + }, + "post": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Assigns a project role to a user within a project.", + "operationId": "assign-project-user-role", + "tags": [ + "Project user role assignments" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to update.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "user_id", + "in": "path", + "description": "The ID of the user that should receive the project role.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Identifies the project role to assign to the user.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicAssignOrganizationGroupRoleBody" + } + } + } + }, + "responses": { + "200": { + "description": "Project role assigned to the user successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserRoleAssignment" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Assign project role to user", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/projects/proj_abc123/users/user_abc123/roles \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"role_id\": \"role_01J1F8PROJ\"\n }'\n" + }, + "response": "{\n \"object\": \"user.role\",\n \"user\": {\n \"object\": \"organization.user\",\n \"id\": \"user_abc123\",\n \"name\": \"Ada Lovelace\",\n \"email\": \"ada@example.com\",\n \"role\": \"owner\",\n \"added_at\": 1711470000\n },\n \"role\": {\n \"object\": \"role\",\n \"id\": \"role_01J1F8PROJ\",\n \"name\": \"API Project Key Manager\",\n \"description\": \"Allows managing API keys for the project\",\n \"permissions\": [\n \"api.organization.projects.api_keys.read\",\n \"api.organization.projects.api_keys.write\"\n ],\n \"resource_type\": \"api.project\",\n \"predefined_role\": false\n }\n}\n" + } + } + } + }, + "/projects/{project_id}/users/{user_id}/roles/{role_id}": { + "get": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Retrieves a project role assigned to a user.", + "operationId": "retrieve-project-user-role", + "tags": [ + "Project user role assignments" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "user_id", + "in": "path", + "description": "The ID of the user to inspect.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "role_id", + "in": "path", + "description": "The ID of the project role to retrieve for the user.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project role retrieved for the user successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssignedRoleDetails" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Retrieve project user role", + "group": "administration", + "examples": { + "request": { + "curl": "curl https://api.openai.com/v1/projects/proj_abc123/users/user_abc123/roles/role_01J1F8PROJ \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"id\": \"role_01J1F8PROJ\",\n \"name\": \"API Project Key Manager\",\n \"permissions\": [\n \"api.organization.projects.api_keys.read\",\n \"api.organization.projects.api_keys.write\"\n ],\n \"resource_type\": \"api.project\",\n \"predefined_role\": false,\n \"description\": \"Allows managing API keys for the project\",\n \"created_at\": 1711471533,\n \"updated_at\": 1711472599,\n \"created_by\": \"user_abc123\",\n \"created_by_user_obj\": null,\n \"metadata\": {},\n \"assignment_sources\": null\n}\n" + } + } + }, + "delete": { + "security": [ + { + "AdminApiKeyAuth": [] + } + ], + "summary": "Unassigns a project role from a user within a project.", + "operationId": "unassign-project-user-role", + "tags": [ + "Project user role assignments" + ], + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The ID of the project to modify.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "user_id", + "in": "path", + "description": "The ID of the user whose project role assignment should be removed.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "role_id", + "in": "path", + "description": "The ID of the project role to remove from the user.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project role unassigned from the user successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedRoleAssignmentResource" + } + } + } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + }, + "x-oaiMeta": { + "name": "Unassign project role from user", + "group": "administration", + "examples": { + "request": { + "curl": "curl -X DELETE https://api.openai.com/v1/projects/proj_abc123/users/user_abc123/roles/role_01J1F8PROJ \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n" + }, + "response": "{\n \"object\": \"user.role.deleted\",\n \"deleted\": true\n}\n" + } + } + } + }, + "/realtime/calls": { + "post": { + "summary": "Create a new Realtime API call over WebRTC and receive the SDP answer needed\nto complete the peer connection.", + "operationId": "create-realtime-call", + "tags": [ + "Realtime" + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/RealtimeCallCreateRequest" + }, + "encoding": { + "sdp": { + "contentType": "application/sdp" + }, + "session": { + "contentType": "application/json" + } + } + }, + "application/sdp": { + "schema": { + "type": "string", + "description": "WebRTC SDP offer. Use this variant when you have previously created an\nephemeral **session token** and are authenticating the request with it.\nRealtime session parameters will be retrieved from the session token." + } + } + } + }, + "responses": { + "201": { + "description": "Realtime call created successfully.", + "headers": { + "Location": { + "description": "Relative URL containing the call ID for subsequent control requests.", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/sdp": { + "schema": { + "type": "string", + "description": "SDP answer produced by OpenAI for the peer connection." + } + } + } + } + }, + "x-oaiMeta": { + "name": "Create call", + "group": "realtime", + "returns": "Returns `201 Created` with the SDP answer in the response body. The\n`Location` response header includes the call ID for follow-up requests,\ne.g., establishing a monitoring WebSocket or hanging up the call.", + "examples": { + "request": { + "curl": "curl -X POST https://api.openai.com/v1/realtime/calls \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n -F \"sdp=