From 75bf4484ba957344ae374354858bbdfe104f5c4f Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Sun, 27 Sep 2026 18:09:05 +0000 Subject: [PATCH 1/2] Add scoped Core administration error details and console failure codes --- CONTRIBUTING.md | 9 + contracts/agents-api/admin-api.md | 6 + contracts/agents-api/core-errors.md | 60 ++ contracts/agents-api/core.openapi.yaml | 602 +++++++++--------- docs/api/README.md | 4 + docs/api/web-management.md | 4 + packages/agents-client/src/client.ts | 7 + .../agents-client/src/core-request.test.ts | 55 ++ packages/agents-client/src/core-request.ts | 22 +- packages/agents-client/src/index.ts | 2 +- .../internal/api/admin_agents_routes.go | 6 +- .../api/admin_environment_templates_routes.go | 6 +- .../internal/api/admin_files_routes.go | 6 +- .../agents-api/internal/api/admin_history.go | 2 +- .../agents-api/internal/api/admin_runtime.go | 2 +- .../internal/api/admin_session_archive.go | 4 +- .../internal/api/admin_sessions_routes.go | 26 +- .../agents-api/internal/api/admin_summary.go | 2 +- .../internal/api/admin_vaults_routes.go | 12 +- .../agents-api/internal/api/core_errors.go | 114 ++++ .../internal/api/core_errors_test.go | 139 ++++ .../agents-api/internal/api/core_metrics.go | 2 +- .../agents-api/internal/api/core_routes.go | 1 + .../api/environment_executor_management.go | 6 +- services/agents-api/internal/api/errors.go | 12 +- .../internal/api/harness_model_providers.go | 8 +- .../agents-api/internal/api/installation.go | 2 +- .../internal/api/project_api_keys.go | 14 +- .../internal/api/sandbox_deployment_setup.go | 6 +- .../internal/api/sandbox_manager.go | 12 +- .../internal/api/sandbox_node_detail.go | 2 +- .../agents-api/internal/api/write_audit.go | 4 +- services/core-console/core_errors.go | 33 + services/core-console/core_errors_test.go | 78 +++ services/core-console/server.go | 20 +- services/core-console/server_test.go | 3 + 36 files changed, 925 insertions(+), 368 deletions(-) create mode 100644 contracts/agents-api/core-errors.md create mode 100644 packages/agents-client/src/core-request.test.ts create mode 100644 services/agents-api/internal/api/core_errors.go create mode 100644 services/agents-api/internal/api/core_errors_test.go create mode 100644 services/core-console/core_errors.go create mode 100644 services/core-console/core_errors_test.go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bf56154e7..b32f49e9c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3665,3 +3665,12 @@ unchanged. Persisted credential encryption domains and native-session resume key also remain stable so existing data can be decrypted and Sessions can resume. Conversion inputs, retirement diagnostics and historical evidence must still name the identifiers they reject or migrate; current examples use the new names. + + +Core administration error details are scoped by the `/core/v1` router writer mark, +not a request path test. Use `writeCoreError` with typed `CoreErrorDetails` values +and document fixed keys in `contracts/agents-api/core-errors.md` when adding a +code. Include only safe Core-owned facts; never pass submitted values, secrets, +native text or provider bodies. Invalid/empty details are omitted. Preserve the +public and machine error serializers, observer callbacks and streaming interfaces. +The Core client ignores malformed optional details and never retries a mutation. diff --git a/contracts/agents-api/admin-api.md b/contracts/agents-api/admin-api.md index db90042e9..490f63135 100644 --- a/contracts/agents-api/admin-api.md +++ b/contracts/agents-api/admin-api.md @@ -11,6 +11,12 @@ Core records without verifying. Web sends `console`; direct Core key scripts normally send none but could set any label. Never use it for authorization or as proof of origin. +## Error envelope + +See [Core administration errors](core-errors.md) for the optional flat `details` +object and the distinct console proxy failure codes. Public and machine response +shapes remain unchanged. + ## Projects and keys A Project owns one tenant and shared principal. Its keys have equal access to all diff --git a/contracts/agents-api/core-errors.md b/contracts/agents-api/core-errors.md new file mode 100644 index 000000000..38210a623 --- /dev/null +++ b/contracts/agents-api/core-errors.md @@ -0,0 +1,60 @@ +# Core administration errors + +Errors on `/core/v1` retain the envelope below. `message` is safe English text; +`code` and `param` are nullable. Clients use stable `code` values and the optional +field `param`, and fall back to `message` for an unknown code. They do not parse +messages or retry rejected mutations automatically. + +```json +{"error":{"message":"A valid Core key is required as the bearer credential.","type":"invalid_request_error","code":"invalid_admin_key","param":null}} +``` + +## Optional details + +`error.details`, when present, is a nonempty flat object. Its values may only be +strings, finite numbers, booleans, null or arrays of strings (including empty +arrays). It contains documented Core-owned facts, never submitted names, URLs, +keys, echoed request values, native error text or provider response bodies. Each +operation that adds details must document its exact keys alongside its error +code. This foundation does not add operation-specific validators, diagnostics +endpoints or new details to existing operations. + +The typed Go `CoreErrorDetails` values have string, number, boolean, null and +string-array constructors. `writeCoreError` emits them only through the marked +Core router; empty or invalid details are omitted as a whole. The mark preserves +error observation, flushing and `http.ResponseController` access. A shared +handler or a Core-looking request path alone cannot change a public or machine +error envelope. Core authentication still runs before operation configuration +checks, and unknown paths retain their existing status and admission rules. + +`AgentCoreError.details` is optional `CoreErrorDetails` in the TypeScript client. +The Core clients accept only the flat value types above, snapshot string arrays, +and ignore malformed or empty details without changing the error's message, +status, code, param or type. The public `OpenAIAgentsClient` does not read this +Core-only field. `/v1` and `/api/v1` response shapes remain unchanged. + +## Console-generated failures + +The console uses the same envelope for its sign-in, origin, unsafe-request and +transport failures in the `/core` namespace. It does not expose request values +or transport exceptions. Core responses pass +through the proxy; the console does not reinterpret their codes or details. + +| HTTP status | Code | Meaning | Param / details | +| --- | --- | --- | --- | +| 401 | `console_sign_in_required` | Console session is missing or expired | null / omitted | +| 403 | `console_origin_rejected` | Host, Origin or Fetch Metadata checks failed | null / omitted | +| 400 | `console_request_invalid` | Request path, method or upgrade is unsafe | null / omitted | +| 502 | `core_unreachable` | Core transport failed or Core tried to redirect | null / omitted | + +The first three use `type: "invalid_request_error"`; the last uses +`type: "server_error"`. A Core `401 invalid_admin_key` remains distinguishable +from a missing console sign-in. `/console/auth` keeps its existing +`{"error":"…"}` errors. Bare, retired and direct public/machine paths do not +become proxyable operations. Host, origin, authentication, credential stripping, +path checks and the no-retry rule are unchanged. + +Existing operation-specific codes remain documented in the [administrator +contract](admin-api.md), [sandbox deployment contract](sandbox-deployment.md), +[executor credential contract](environment-executor-credentials.md) and related +resource contracts. This change does not reclassify their validation failures. diff --git a/contracts/agents-api/core.openapi.yaml b/contracts/agents-api/core.openapi.yaml index 88b00698d..d857e9a2d 100644 --- a/contracts/agents-api/core.openapi.yaml +++ b/contracts/agents-api/core.openapi.yaml @@ -180,6 +180,35 @@ definitions: total_sessions: type: integer type: object + api.CoreAPIError: + properties: + code: + type: string + x-nullable: true + details: + description: |- + Details contains only documented, Core-owned facts: string, finite number, + boolean, null or string array values. Never include request echoes, secrets + or native/provider error text. Empty or invalid details are omitted. + type: object + message: + type: string + param: + type: string + x-nullable: true + type: + type: string + required: + - message + - type + type: object + api.CoreErrorResponse: + properties: + error: + $ref: '#/definitions/api.CoreAPIError' + required: + - error + type: object api.CoreHarness: properties: default: @@ -1177,22 +1206,6 @@ definitions: next_cursor: type: string type: object - 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: @@ -1465,13 +1478,6 @@ definitions: - has_more - object type: object - v1.ErrorResponse: - properties: - error: - $ref: '#/definitions/v1.APIError' - required: - - error - type: object v1.ExecutionProviderSelection: properties: configuration: @@ -3063,15 +3069,15 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Query committed administrator mutations @@ -3090,11 +3096,11 @@ paths: "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List harnesses and their deployment default model providers @@ -3119,15 +3125,15 @@ paths: "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Remove a harness's deployment default model provider @@ -3155,15 +3161,15 @@ paths: "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve a harness's deployment default model provider @@ -3199,27 +3205,27 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "413": description: Request Entity Too Large schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Replace a harness's deployment default model provider @@ -3238,11 +3244,11 @@ paths: "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve installation facts and process settings @@ -3271,15 +3277,15 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve Core operational metrics @@ -3310,19 +3316,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List Projects and active key counts @@ -3348,19 +3354,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Create an empty independent Project @@ -3392,23 +3398,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Rename a Project @@ -3451,19 +3457,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List reusable Agents in a Project @@ -3493,23 +3499,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "413": description: Request Entity Too Large schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Delete a reusable Agent in a Project @@ -3538,19 +3544,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve a reusable Agent in a Project @@ -3574,19 +3580,19 @@ paths: "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Archive a Project and revoke all its keys while retaining assets @@ -3629,19 +3635,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List Environment Templates in a Project @@ -3671,19 +3677,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Delete an Environment Template in a Project @@ -3712,19 +3718,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve an Environment Template in a Project @@ -3754,15 +3760,15 @@ paths: "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List a self_hosted Environment's executor credentials @@ -3799,23 +3805,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Issue or explicitly rotate a self_hosted Environment executor credential @@ -3846,15 +3852,15 @@ paths: "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Revoke a self_hosted Environment executor credential @@ -3902,23 +3908,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List source files in a Project @@ -3948,23 +3954,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Delete a source file in a Project @@ -3993,23 +3999,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve source file metadata in a Project @@ -4045,19 +4051,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List safe key metadata for a Project @@ -4088,23 +4094,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Issue an independent secret in an existing Project @@ -4133,19 +4139,19 @@ paths: "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Revoke one Project key while retaining shared assets @@ -4180,19 +4186,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Batch lookup resource creation keys @@ -4239,19 +4245,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List execution Sessions in a Project @@ -4281,27 +4287,27 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "413": description: Request Entity Too Large schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Delete an execution Session in a Project @@ -4330,19 +4336,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve an execution Session in a Project @@ -4372,23 +4378,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve a managed Session's resource cleanup state @@ -4425,31 +4431,31 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "413": description: Request Entity Too Large schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Release a managed Session's execution resources while retaining history @@ -4502,23 +4508,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List immutable Session artifacts in a Project @@ -4553,23 +4559,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Delete a published artifact in a Project @@ -4603,23 +4609,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve immutable artifact metadata in a Project @@ -4654,23 +4660,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Download immutable artifact bytes in a Project @@ -4700,23 +4706,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve a Session's frozen execution configuration in a Project @@ -4764,19 +4770,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List persisted execution Items in a Project @@ -4826,23 +4832,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve Session Runtime history in a Project @@ -4872,23 +4878,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve a Session Runtime observation in a Project @@ -4937,19 +4943,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List execution Turns in a Project @@ -4984,19 +4990,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve an execution Turn in a Project @@ -5307,19 +5313,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List Vaults in a Project @@ -5349,23 +5355,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "413": description: Request Entity Too Large schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Delete a Vault and all its Credentials in a Project @@ -5394,19 +5400,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve a Vault in a Project @@ -5470,19 +5476,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List safe Vault Credential metadata in a Project @@ -5517,23 +5523,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "413": description: Request Entity Too Large schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Delete a Vault Credential in a Project @@ -5567,19 +5573,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve safe Vault Credential metadata in a Project @@ -5632,19 +5638,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Query API-key write operations @@ -5663,27 +5669,27 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve sandbox deployment @@ -5710,23 +5716,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Initialize the deployment sandbox provider @@ -5753,23 +5759,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Change a fully drained deployment's sandbox configuration @@ -5797,23 +5803,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Pause or resume new hosted allocations @@ -5841,27 +5847,27 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Create a ten-minute one-use node enrollment token @@ -5880,27 +5886,27 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List deployment sandbox nodes @@ -5925,27 +5931,27 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Remove a sandbox node with no retained resources @@ -5977,23 +5983,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Retrieve sandbox node and host history @@ -6025,27 +6031,27 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Update sandbox node name and capacity @@ -6070,27 +6076,27 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "409": description: Conflict schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List retained allocations on a sandbox node @@ -6129,23 +6135,23 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "503": description: Service Unavailable schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: List Runtime observations across managed Projects @@ -6205,19 +6211,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "401": description: Unauthorized schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "404": description: Not Found schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' "500": description: Internal Server Error schema: - $ref: '#/definitions/v1.ErrorResponse' + $ref: '#/definitions/api.CoreErrorResponse' security: - DeploymentAdminAuth: [] summary: Summarize resource counts and Session usage by Project, Agent or creator key diff --git a/docs/api/README.md b/docs/api/README.md index d7b2c2994..e332e8dd6 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -94,3 +94,7 @@ Generated schemas do not establish complete compatibility or real execution support. The [coverage record](../../contracts/agents-api/README.md) identifies qualified workflows, native differences and unresolved behavior. Update the relevant contract and this index when adding or moving an API surface. + +Core administration failures use the [Core error envelope](../../contracts/agents-api/core-errors.md), +including typed optional safe details and distinct console proxy rejection codes. +The public and machine error contracts remain unchanged. diff --git a/docs/api/web-management.md b/docs/api/web-management.md index 4c81ea846..0b09b6017 100644 --- a/docs/api/web-management.md +++ b/docs/api/web-management.md @@ -5,6 +5,10 @@ resource inspection, Project and key management, credential issuance, audit, usa and sandbox operations. It never calls `/v1` or `/api/v1`, and has no Agent execution, copy or arbitrary asset editing operation. +Core request failures use the [Core administration error envelope](../../contracts/agents-api/core-errors.md), +including optional typed safe details. Console sign-in endpoints retain their +separate error shape described below. + ## Browser to console The browser uses the console's own origin and signs in with the diff --git a/packages/agents-client/src/client.ts b/packages/agents-client/src/client.ts index 181c1d504..d2d7ad0ba 100644 --- a/packages/agents-client/src/client.ts +++ b/packages/agents-client/src/client.ts @@ -118,11 +118,16 @@ interface APIErrorEnvelope { }; } +/** Safe optional facts on Core administration errors; never native error text. */ +export type CoreErrorDetail = string | number | boolean | null | readonly string[]; +export type CoreErrorDetails = Readonly>; + export class AgentCoreError extends Error { readonly status: number; readonly code?: string | null; readonly param?: string | null; readonly errorType?: string; + readonly details?: CoreErrorDetails; constructor( message: string, @@ -130,6 +135,7 @@ export class AgentCoreError extends Error { code?: string | null, param?: string | null, errorType?: string, + details?: CoreErrorDetails, ) { super(message); this.name = "AgentCoreError"; @@ -137,6 +143,7 @@ export class AgentCoreError extends Error { this.code = code; this.param = param; this.errorType = errorType; + if (details !== undefined) this.details = details; } } diff --git a/packages/agents-client/src/core-request.test.ts b/packages/agents-client/src/core-request.test.ts new file mode 100644 index 000000000..82a204f5a --- /dev/null +++ b/packages/agents-client/src/core-request.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it, vi } from "vitest"; +import { AgentCoreError, OpenAIAgentsClient } from "./client"; +import { CoreRequester } from "./core-request"; + +function requester(details: unknown) { + const fetch = vi.fn(async () => ({ + ok: false, status: 409, + json: async () => ({ error: { message: "Refresh the configuration.", type: "conflict_error", code: "sandbox_generation_stale", param: "expected_generation", details } }), + }) as Response); + return { fetch, request: new CoreRequester("/core/v1", undefined, fetch, () => { throw new Error("invalid success body"); }) }; +} + +describe("Core error details", () => { + it("projects documented flat values and retains one-request semantics", async () => { + const details = { current_generation: 4, supported: ["docker", "microsandbox"], enabled: true, missing: null, label: "ready", empty: [] }; + const { fetch, request } = requester(details); + const error = await request.response("/sandbox/deployment", undefined, "PUT", { expected_generation: 3 }).catch((value: unknown) => value); + expect(error).toBeInstanceOf(AgentCoreError); + expect(error).toMatchObject({ status: 409, code: "sandbox_generation_stale", param: "expected_generation", errorType: "conflict_error", details }); + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch.mock.calls[0]).toEqual(["/core/v1/sandbox/deployment", expect.objectContaining({ method: "PUT", credentials: "same-origin", redirect: "error" })]); + details.supported[0] = "mutated"; + expect((error as AgentCoreError).details?.supported).toEqual(["docker", "microsandbox"]); + }); + + it.each([ + undefined, null, {}, [], "private native text", 5, true, new Date(), + { nested: { secret: "private" } }, { values: ["safe", 1] }, { values: [null] }, + { value: Infinity }, { value: NaN }, { value: undefined }, { "": "invalid key" }, + { good: 3, bad: { nested: true } }, { values: Array(2) }, + ])("ignores malformed or empty optional details (%j)", async (details) => { + const { fetch, request } = requester(details); + const error = await request.response("/projects").catch((value: unknown) => value); + expect(error).toBeInstanceOf(AgentCoreError); + expect(error).toMatchObject({ status: 409, code: "sandbox_generation_stale", message: "Refresh the configuration." }); + expect((error as AgentCoreError).details).toBeUndefined(); + expect(fetch).toHaveBeenCalledTimes(1); + }); + + it("preserves literal keys without changing object prototypes", async () => { + const details = JSON.parse('{"__proto__":"literal","constructor":null}'); + const error = await requester(details).request.response("/projects").catch((value: unknown) => value) as AgentCoreError; + expect(error.details).toEqual(details); + expect(Object.getPrototypeOf(error.details)).toBe(Object.prototype); + expect(Object.prototype.hasOwnProperty.call(error.details, "__proto__")).toBe(true); + }); + + it("does not add Core details to public-client errors", async () => { + const fetch = vi.fn(async () => new Response(JSON.stringify({ error: { message: "Rejected.", type: "invalid_request_error", code: "invalid_request", param: null, details: { max_length: 128 } } }), { status: 400 })); + const error = await new OpenAIAgentsClient({ fetch }).listAgents().catch((value: unknown) => value) as AgentCoreError; + expect(error).toBeInstanceOf(AgentCoreError); + expect(error.details).toBeUndefined(); + expect(fetch).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/agents-client/src/core-request.ts b/packages/agents-client/src/core-request.ts index 8da5742eb..76afc1b6a 100644 --- a/packages/agents-client/src/core-request.ts +++ b/packages/agents-client/src/core-request.ts @@ -1,7 +1,26 @@ -import { AgentCoreError } from "./client"; +import { AgentCoreError, type CoreErrorDetail, type CoreErrorDetails } from "./client"; import { isRecord } from "./response-projection"; import type { ReadOptions } from "./types"; +function isCoreErrorDetail(value: unknown): value is CoreErrorDetail { + return value === null || typeof value === "string" || typeof value === "boolean" + || (typeof value === "number" && Number.isFinite(value)) + || (Array.isArray(value) && Array.from(value).every((item) => typeof item === "string")); +} + +function coreErrorDetails(value: unknown): CoreErrorDetails | undefined { + if (!isRecord(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return undefined; + const entries = Object.entries(value); + if (entries.length === 0) return undefined; + const projected: [string, CoreErrorDetail][] = []; + for (const [key, detail] of entries) { + if (!key || !isCoreErrorDetail(detail)) return undefined; + projected.push([key, Array.isArray(detail) ? Object.freeze([...detail]) : detail]); + } + // Object.fromEntries keeps a literal __proto__ key from changing the prototype. + return Object.freeze(Object.fromEntries(projected)); +} + /** Constructor options of the Core clients that take a plain bearer `token`. */ export interface CoreClientOptions { /** Prefix that request paths are appended to: `/core/v1/sandbox` for SandboxAdminClient, `/core/v1` for CoreMetricsClient by default. */ @@ -50,6 +69,7 @@ export class CoreRequester { typeof error.code === "string" || error.code === null ? error.code : undefined, typeof error.param === "string" || error.param === null ? error.param : undefined, typeof error.type === "string" ? error.type : undefined, + coreErrorDetails(error.details), ); } return response; diff --git a/packages/agents-client/src/index.ts b/packages/agents-client/src/index.ts index 0cb31e6b2..7a73d2078 100644 --- a/packages/agents-client/src/index.ts +++ b/packages/agents-client/src/index.ts @@ -1,5 +1,5 @@ export { AgentCoreError, CreationStreamRetryError, createIdempotencyKey, isSessionDeletionConflict, OpenAIAgentsClient } from "./client"; -export type { OpenAIAgentsClientOptions } from "./client"; +export type { OpenAIAgentsClientOptions, CoreErrorDetail, CoreErrorDetails } from "./client"; export { createSSEDecoder } from "./sse"; export type { SSEDecoder, SSEMessage } from "./sse"; export type * from "./types"; diff --git a/services/agents-api/internal/api/admin_agents_routes.go b/services/agents-api/internal/api/admin_agents_routes.go index 14b787eab..de13a90f2 100644 --- a/services/agents-api/internal/api/admin_agents_routes.go +++ b/services/agents-api/internal/api/admin_agents_routes.go @@ -11,7 +11,7 @@ import "net/http" // @Param limit query int64 false "Page size; 0 is treated as 1 and values above 100 as 100" minimum(0) default(20) // @Param order query string false "Creation order; omit for descending, explicit empty values are invalid" Enums(asc,desc) default(desc) // @Success 200 {object} v1.SavedAgentList -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/agents [get] func (h *Handler) adminListAgents(w http.ResponseWriter, r *http.Request) { @@ -25,7 +25,7 @@ func (h *Handler) adminListAgents(w http.ResponseWriter, r *http.Request) { // @Security DeploymentAdminAuth // @Param agent_id path string true "Agent ID" // @Success 200 {object} v1.SavedAgent -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/agents/{agent_id} [get] func (h *Handler) adminGetAgent(w http.ResponseWriter, r *http.Request) { @@ -39,7 +39,7 @@ func (h *Handler) adminGetAgent(w http.ResponseWriter, r *http.Request) { // @Security DeploymentAdminAuth // @Param agent_id path string true "Agent ID" // @Success 200 {object} v1.AgentDeleted -// @Failure 400,401,404,413,500 {object} v1.ErrorResponse +// @Failure 400,401,404,413,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/agents/{agent_id} [delete] func (h *Handler) adminDeleteAgent(w http.ResponseWriter, r *http.Request) { diff --git a/services/agents-api/internal/api/admin_environment_templates_routes.go b/services/agents-api/internal/api/admin_environment_templates_routes.go index 9b0dea39a..29f3aba8c 100644 --- a/services/agents-api/internal/api/admin_environment_templates_routes.go +++ b/services/agents-api/internal/api/admin_environment_templates_routes.go @@ -11,7 +11,7 @@ import "net/http" // @Param limit query integer false "Page size; 0 is treated as 1 and values above 100 as 100" default(20) minimum(0) // @Param order query string false "Creation order; omit for descending, explicit empty values are invalid" Enums(asc,desc) default(desc) // @Success 200 {object} v1.EnvironmentTemplateList -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/environment-templates [get] func (h *Handler) adminListEnvironmentTemplates(w http.ResponseWriter, r *http.Request) { @@ -25,7 +25,7 @@ func (h *Handler) adminListEnvironmentTemplates(w http.ResponseWriter, r *http.R // @Security DeploymentAdminAuth // @Param environment_template_id path string true "Template ID" // @Success 200 {object} v1.EnvironmentTemplate -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/environment-templates/{environment_template_id} [get] func (h *Handler) adminGetEnvironmentTemplate(w http.ResponseWriter, r *http.Request) { @@ -39,7 +39,7 @@ func (h *Handler) adminGetEnvironmentTemplate(w http.ResponseWriter, r *http.Req // @Security DeploymentAdminAuth // @Param environment_template_id path string true "Template ID" // @Success 200 {object} v1.EnvironmentTemplateDeleted -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/environment-templates/{environment_template_id} [delete] func (h *Handler) adminDeleteEnvironmentTemplate(w http.ResponseWriter, r *http.Request) { diff --git a/services/agents-api/internal/api/admin_files_routes.go b/services/agents-api/internal/api/admin_files_routes.go index b37410233..12baf49ce 100644 --- a/services/agents-api/internal/api/admin_files_routes.go +++ b/services/agents-api/internal/api/admin_files_routes.go @@ -12,7 +12,7 @@ import "net/http" // @Param order query string false "Creation order; omit for descending, explicit empty values are invalid" Enums(asc,desc) default(desc) // @Param purpose query string false "Only return Files with this purpose" // @Success 200 {object} v1.SourceFileList -// @Failure 400,401,404,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,500,503 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/files [get] func (h *Handler) adminListSourceFiles(w http.ResponseWriter, r *http.Request) { @@ -26,7 +26,7 @@ func (h *Handler) adminListSourceFiles(w http.ResponseWriter, r *http.Request) { // @Security DeploymentAdminAuth // @Param file_id path string true "Source file ID" // @Success 200 {object} v1.SourceFile -// @Failure 400,401,404,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,500,503 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/files/{file_id} [get] func (h *Handler) adminGetSourceFile(w http.ResponseWriter, r *http.Request) { @@ -40,7 +40,7 @@ func (h *Handler) adminGetSourceFile(w http.ResponseWriter, r *http.Request) { // @Security DeploymentAdminAuth // @Param file_id path string true "Source file ID" // @Success 200 {object} v1.SourceFileDeleted -// @Failure 400,401,404,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,500,503 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/files/{file_id} [delete] func (h *Handler) adminDeleteSourceFile(w http.ResponseWriter, r *http.Request) { diff --git a/services/agents-api/internal/api/admin_history.go b/services/agents-api/internal/api/admin_history.go index ca3d26551..30fd05457 100644 --- a/services/agents-api/internal/api/admin_history.go +++ b/services/agents-api/internal/api/admin_history.go @@ -22,7 +22,7 @@ import ( // @Param limit query int false "Page size" minimum(1) maximum(100) default(50) // @Param after query string false "Opaque next_cursor from the preceding page" // @Success 200 {object} store.AdminAuditPage -// @Failure 400,401,500 {object} v1.ErrorResponse +// @Failure 400,401,500 {object} CoreErrorResponse // @Router /core/v1/audit-log [get] func (h *Handler) listAdminAudit(w http.ResponseWriter, r *http.Request) { values, err := url.ParseQuery(r.URL.RawQuery) diff --git a/services/agents-api/internal/api/admin_runtime.go b/services/agents-api/internal/api/admin_runtime.go index 82bb03bd9..e5c1868af 100644 --- a/services/agents-api/internal/api/admin_runtime.go +++ b/services/agents-api/internal/api/admin_runtime.go @@ -44,7 +44,7 @@ type AdminRuntimeObservationList struct { // @Param limit query int false "Page size" minimum(1) maximum(100) default(20) // @Param order query string false "Session creation order" Enums(asc,desc) default(desc) // @Success 200 {object} api.AdminRuntimeObservationList -// @Failure 400,401,404,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,500,503 {object} CoreErrorResponse // @Router /core/v1/sandbox/runtime-observations [get] func (h *Handler) adminRuntimeObservations(w http.ResponseWriter, r *http.Request) { if h.runtimeObservations == nil { diff --git a/services/agents-api/internal/api/admin_session_archive.go b/services/agents-api/internal/api/admin_session_archive.go index 1b44122b7..b9ccdaa38 100644 --- a/services/agents-api/internal/api/admin_session_archive.go +++ b/services/agents-api/internal/api/admin_session_archive.go @@ -27,7 +27,7 @@ type AdminSessionArchiveRequest struct { // @Param session_id path string true "Session ID" // @Param body body api.AdminSessionArchiveRequest true "Current deployment generation" // @Success 200 {object} store.ManagedSessionArchive -// @Failure 400,401,404,409,413,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,409,413,500,503 {object} CoreErrorResponse // @Router /core/v1/projects/{project_id}/sessions/{session_id}/archive [post] func (h *Handler) adminArchiveSession(w http.ResponseWriter, r *http.Request) { raw, ok := readJSONBodyLimit(w, r, 4096, "Archive request is too large.") @@ -59,7 +59,7 @@ func (h *Handler) adminArchiveSession(w http.ResponseWriter, r *http.Request) { // @Param project_id path string true "Project ID" // @Param session_id path string true "Session ID" // @Success 200 {object} store.ManagedSessionArchive -// @Failure 400,401,404,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,500,503 {object} CoreErrorResponse // @Router /core/v1/projects/{project_id}/sessions/{session_id}/archive [get] func (h *Handler) adminGetSessionArchive(w http.ResponseWriter, r *http.Request) { result, err := h.adminManagement.GetManagedSessionArchive(r.Context(), tenantID(r), chi.URLParam(r, "session_id")) diff --git a/services/agents-api/internal/api/admin_sessions_routes.go b/services/agents-api/internal/api/admin_sessions_routes.go index 5ad0e5924..7775b4e27 100644 --- a/services/agents-api/internal/api/admin_sessions_routes.go +++ b/services/agents-api/internal/api/admin_sessions_routes.go @@ -12,7 +12,7 @@ import "net/http" // @Param limit query int false "Page size; 0 is treated as 1 and values above 100 as 100" minimum(0) default(20) // @Param order query string false "Creation order; omit for descending, explicit empty values are invalid" Enums(asc,desc) default(desc) // @Success 200 {object} v1.SessionList -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/sessions [get] func (h *Handler) adminListSessions(w http.ResponseWriter, r *http.Request) { @@ -26,7 +26,7 @@ func (h *Handler) adminListSessions(w http.ResponseWriter, r *http.Request) { // @Security DeploymentAdminAuth // @Param session_id path string true "Session ID" // @Success 200 {object} v1.Session -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/sessions/{session_id} [get] func (h *Handler) adminGetSession(w http.ResponseWriter, r *http.Request) { @@ -40,7 +40,7 @@ func (h *Handler) adminGetSession(w http.ResponseWriter, r *http.Request) { // @Security DeploymentAdminAuth // @Param session_id path string true "Session ID" // @Success 200 {object} v1.SessionDeleted -// @Failure 400,401,404,409,413,500 {object} v1.ErrorResponse +// @Failure 400,401,404,409,413,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/sessions/{session_id} [delete] func (h *Handler) adminDeleteSession(w http.ResponseWriter, r *http.Request) { @@ -57,7 +57,7 @@ func (h *Handler) adminDeleteSession(w http.ResponseWriter, r *http.Request) { // @Param limit query int false "Page size" minimum(1) maximum(100) default(20) // @Param order query string false "Creation order; omit for descending, explicit empty values are invalid" Enums(asc,desc) default(desc) // @Success 200 {object} v1.TurnList -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/sessions/{session_id}/turns [get] func (h *Handler) adminListTurns(w http.ResponseWriter, r *http.Request) { @@ -72,7 +72,7 @@ func (h *Handler) adminListTurns(w http.ResponseWriter, r *http.Request) { // @Param session_id path string true "Session ID" // @Param turn_id path string true "Turn ID" // @Success 200 {object} v1.Turn -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/sessions/{session_id}/turns/{turn_id} [get] func (h *Handler) adminGetTurn(w http.ResponseWriter, r *http.Request) { @@ -89,7 +89,7 @@ func (h *Handler) adminGetTurn(w http.ResponseWriter, r *http.Request) { // @Param limit query int false "Page size; 0 is treated as 1 and values above 100 as 100" minimum(0) default(20) // @Param order query string false "Creation order; omit for descending, explicit empty values are invalid" Enums(asc,desc) default(desc) // @Success 200 {object} v1.ItemList -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/sessions/{session_id}/items [get] func (h *Handler) adminListItems(w http.ResponseWriter, r *http.Request) { @@ -107,7 +107,7 @@ func (h *Handler) adminListItems(w http.ResponseWriter, r *http.Request) { // @Param limit query int false "Page size" minimum(1) maximum(100) default(20) // @Param order query string false "Publication order; omit for descending, explicit empty values are invalid" Enums(asc,desc) default(desc) // @Success 200 {object} v1.SessionArtifactList -// @Failure 400,401,404,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,500,503 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/sessions/{session_id}/artifacts [get] func (h *Handler) adminListSessionArtifacts(w http.ResponseWriter, r *http.Request) { @@ -122,7 +122,7 @@ func (h *Handler) adminListSessionArtifacts(w http.ResponseWriter, r *http.Reque // @Param session_id path string true "Session ID" // @Param artifact_id path string true "Artifact ID" // @Success 200 {object} v1.SessionArtifact -// @Failure 400,401,404,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,500,503 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/sessions/{session_id}/artifacts/{artifact_id} [get] func (h *Handler) adminGetSessionArtifact(w http.ResponseWriter, r *http.Request) { @@ -137,7 +137,7 @@ func (h *Handler) adminGetSessionArtifact(w http.ResponseWriter, r *http.Request // @Param session_id path string true "Session ID" // @Param artifact_id path string true "Artifact ID" // @Success 200 {object} v1.SessionArtifactDeleted -// @Failure 400,401,404,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,500,503 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/sessions/{session_id}/artifacts/{artifact_id} [delete] func (h *Handler) adminDeleteSessionArtifact(w http.ResponseWriter, r *http.Request) { @@ -152,7 +152,7 @@ func (h *Handler) adminDeleteSessionArtifact(w http.ResponseWriter, r *http.Requ // @Param session_id path string true "Session ID" // @Param artifact_id path string true "Artifact ID" // @Success 200 {file} binary -// @Failure 400,401,404,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,500,503 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/sessions/{session_id}/artifacts/{artifact_id}/content [get] func (h *Handler) adminSessionArtifactContent(w http.ResponseWriter, r *http.Request) { @@ -166,7 +166,7 @@ func (h *Handler) adminSessionArtifactContent(w http.ResponseWriter, r *http.Req // @Security DeploymentAdminAuth // @Param session_id path string true "Session ID" // @Success 200 {object} v1.SessionExecutionConfiguration -// @Failure 400,401,404,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,500,503 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/sessions/{session_id}/execution-configuration [get] func (h *Handler) adminGetSessionExecutionConfiguration(w http.ResponseWriter, r *http.Request) { @@ -180,7 +180,7 @@ func (h *Handler) adminGetSessionExecutionConfiguration(w http.ResponseWriter, r // @Security DeploymentAdminAuth // @Param session_id path string true "Session ID" // @Success 200 {object} v1.RuntimeObservation -// @Failure 400,401,404,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,500,503 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/sessions/{session_id}/runtime-observation [get] func (h *Handler) adminGetRuntimeObservation(w http.ResponseWriter, r *http.Request) { @@ -197,7 +197,7 @@ func (h *Handler) adminGetRuntimeObservation(w http.ResponseWriter, r *http.Requ // @Param end query integer true "Exclusive Unix-second end" minimum(1) maximum(9007199254740991) // @Param max_points query integer false "Maximum points per series; defaults to the lower of 120 and the advertised service maximum" minimum(2) maximum(10000) // @Success 200 {object} v1.RuntimeHistory -// @Failure 400,401,404,409,503 {object} v1.ErrorResponse +// @Failure 400,401,404,409,503 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/sessions/{session_id}/runtime-history [get] func (h *Handler) adminGetRuntimeHistory(w http.ResponseWriter, r *http.Request) { diff --git a/services/agents-api/internal/api/admin_summary.go b/services/agents-api/internal/api/admin_summary.go index ec6f4a6bd..473036858 100644 --- a/services/agents-api/internal/api/admin_summary.go +++ b/services/agents-api/internal/api/admin_summary.go @@ -66,7 +66,7 @@ func adminSummaryTime(r *http.Request, name string) (*time.Time, error) { // @Param limit query int false "Number of Projects" minimum(1) maximum(100) default(20) // @Param order query string false "Project order" Enums(asc,desc) default(desc) // @Success 200 {object} api.AdminSummaryResponse -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Router /core/v1/summary [get] func (h *Handler) adminSummary(w http.ResponseWriter, r *http.Request) { options, ok := readPage(w, r, "project_id", "group_by", "created_after", "created_before") diff --git a/services/agents-api/internal/api/admin_vaults_routes.go b/services/agents-api/internal/api/admin_vaults_routes.go index d9ec9d0fc..2a1eb20dd 100644 --- a/services/agents-api/internal/api/admin_vaults_routes.go +++ b/services/agents-api/internal/api/admin_vaults_routes.go @@ -13,7 +13,7 @@ import "net/http" // @Param status query string false "Scalar status filter" Enums(active,archived) // @Param status[] query []string false "Array status filter; combined with status as a union" collectionFormat(multi) Enums(active,archived) // @Success 200 {object} v1.VaultList -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/vaults [get] func (h *Handler) adminListVaults(w http.ResponseWriter, r *http.Request) { @@ -27,7 +27,7 @@ func (h *Handler) adminListVaults(w http.ResponseWriter, r *http.Request) { // @Security DeploymentAdminAuth // @Param vault_id path string true "Vault ID" // @Success 200 {object} v1.Vault -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/vaults/{vault_id} [get] func (h *Handler) adminGetVault(w http.ResponseWriter, r *http.Request) { @@ -41,7 +41,7 @@ func (h *Handler) adminGetVault(w http.ResponseWriter, r *http.Request) { // @Security DeploymentAdminAuth // @Param vault_id path string true "Vault ID" // @Success 200 {object} v1.VaultDeleted -// @Failure 400,401,404,413,500 {object} v1.ErrorResponse +// @Failure 400,401,404,413,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/vaults/{vault_id} [delete] func (h *Handler) adminDeleteVault(w http.ResponseWriter, r *http.Request) { @@ -60,7 +60,7 @@ func (h *Handler) adminDeleteVault(w http.ResponseWriter, r *http.Request) { // @Param status query string false "Scalar status filter" Enums(active,archived) // @Param status[] query []string false "Array status filter; combined with status as a union" collectionFormat(multi) Enums(active,archived) // @Success 200 {object} v1.CredentialList -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/vaults/{vault_id}/credentials [get] func (h *Handler) adminListCredentials(w http.ResponseWriter, r *http.Request) { @@ -75,7 +75,7 @@ func (h *Handler) adminListCredentials(w http.ResponseWriter, r *http.Request) { // @Param vault_id path string true "Vault ID" // @Param credential_id path string true "Credential ID" // @Success 200 {object} v1.Credential -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/vaults/{vault_id}/credentials/{credential_id} [get] func (h *Handler) adminGetCredential(w http.ResponseWriter, r *http.Request) { @@ -90,7 +90,7 @@ func (h *Handler) adminGetCredential(w http.ResponseWriter, r *http.Request) { // @Param vault_id path string true "Vault ID" // @Param credential_id path string true "Credential ID" // @Success 200 {object} v1.CredentialDeleted -// @Failure 400,401,404,413,500 {object} v1.ErrorResponse +// @Failure 400,401,404,413,500 {object} CoreErrorResponse // @Param project_id path string true "Project ID" // @Router /core/v1/projects/{project_id}/vaults/{vault_id}/credentials/{credential_id} [delete] func (h *Handler) adminDeleteCredential(w http.ResponseWriter, r *http.Request) { diff --git a/services/agents-api/internal/api/core_errors.go b/services/agents-api/internal/api/core_errors.go new file mode 100644 index 000000000..1994389e9 --- /dev/null +++ b/services/agents-api/internal/api/core_errors.go @@ -0,0 +1,114 @@ +package api + +import ( + "encoding/json" + "math" + "net/http" +) + +// CoreErrorResponse is the administration error envelope. Public and machine +// routes retain v1.ErrorResponse and never emit Details. +type CoreErrorResponse struct { + Error CoreAPIError `json:"error" binding:"required"` +} + +type CoreAPIError struct { + Message string `json:"message" binding:"required"` + Type string `json:"type" binding:"required"` + Code *string `json:"code" extensions:"x-nullable"` + Param *string `json:"param" extensions:"x-nullable"` + // Details contains only documented, Core-owned facts: string, finite number, + // boolean, null or string array values. Never include request echoes, secrets + // or native/provider error text. Empty or invalid details are omitted. + Details CoreErrorDetails `json:"details,omitempty" swaggertype:"object"` +} + +type CoreErrorDetails map[string]CoreErrorDetail + +// CoreErrorDetail has a closed set of constructors so nested objects, arbitrary +// errors and provider responses cannot be passed as detail values. The zero +// value represents JSON null. Callers must choose documented fixed keys and +// safe facts; a string constructor is not a redaction mechanism. +type CoreErrorDetail struct{ value any } + +func CoreErrorString(value string) CoreErrorDetail { return CoreErrorDetail{value} } +func CoreErrorNumber(value float64) CoreErrorDetail { return CoreErrorDetail{value} } +func CoreErrorBoolean(value bool) CoreErrorDetail { return CoreErrorDetail{value} } +func CoreErrorNull() CoreErrorDetail { return CoreErrorDetail{} } +func CoreErrorStrings(values ...string) CoreErrorDetail { + return CoreErrorDetail{append([]string{}, values...)} +} + +func (value CoreErrorDetail) MarshalJSON() ([]byte, error) { return json.Marshal(value.value) } + +func validCoreDetails(details CoreErrorDetails) CoreErrorDetails { + if len(details) == 0 { + return nil + } + for key, detail := range details { + if key == "" { + return nil + } + switch value := detail.value.(type) { + case nil, string, bool, []string: + case float64: + if math.IsNaN(value) || math.IsInf(value, 0) { + return nil + } + default: + return nil + } + } + return details +} + +// writeCoreError is for fixed administration diagnostics, not native error +// text. The router mark, rather than a caller's path or choice of helper, +// determines whether details can be emitted. +func writeCoreError(w http.ResponseWriter, status int, code, message string, details CoreErrorDetails, param ...string) { + writeAPIError(w, status, code, message, details, param...) +} + +func coreErrorResponses(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + next.ServeHTTP(&coreErrorWriter{w}, r) + }) +} + +type coreErrorWriter struct{ http.ResponseWriter } + +func (*coreErrorWriter) coreErrorResponse() {} +func (w *coreErrorWriter) Unwrap() http.ResponseWriter { return w.ResponseWriter } +func (w *coreErrorWriter) FlushError() error { + return http.NewResponseController(w.ResponseWriter).Flush() +} +func (w *coreErrorWriter) Flush() { _ = w.FlushError() } +func (w *coreErrorWriter) reportAPIError(code string) { reportAPIError(w.ResponseWriter, code) } + +func isCoreErrorWriter(w http.ResponseWriter) bool { + for w != nil { + if _, ok := w.(interface{ coreErrorResponse() }); ok { + return true + } + unwrapper, ok := w.(interface{ Unwrap() http.ResponseWriter }) + if !ok { + break + } + w = unwrapper.Unwrap() + } + return false +} + +func reportAPIError(w http.ResponseWriter, code string) { + for w != nil { + if observer, ok := w.(interface{ reportAPIError(string) }); ok { + observer.reportAPIError(code) + return + } + unwrapper, ok := w.(interface{ Unwrap() http.ResponseWriter }) + if !ok { + return + } + w = unwrapper.Unwrap() + } +} diff --git a/services/agents-api/internal/api/core_errors_test.go b/services/agents-api/internal/api/core_errors_test.go new file mode 100644 index 000000000..bff67fb1f --- /dev/null +++ b/services/agents-api/internal/api/core_errors_test.go @@ -0,0 +1,139 @@ +package api + +import ( + "encoding/json" + "math" + "net/http" + "net/http/httptest" + "reflect" + "strings" + "testing" + "time" + + "github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/device" + "github.com/go-chi/chi/v5" +) + +func TestCoreErrorDetailsAreScopedByRouterNotRequestPath(t *testing.T) { + shared := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writeCoreError(w, 409, "sandbox_generation_stale", "Refresh the configuration.", CoreErrorDetails{ + "current_generation": CoreErrorNumber(4), "kinds": CoreErrorStrings("docker", "microsandbox"), + }, "expected_generation") + }) + router := chi.NewRouter() + admin, err := NewDeploymentAuthenticator([]string{device.HashCredential("admin")}) + if err != nil { + t.Fatal(err) + } + (&Handler{deploymentAuth: admin}).registerCoreRoutes(router) + var core chi.Router + for _, route := range router.Routes() { + if route.Pattern == "/core/v1/*" { + core, _ = route.SubRoutes.(chi.Router) + } + } + if core == nil { + t.Fatal("Core router is missing") + } + core.Handle("/test", shared) + router.Handle("/v1/test", shared) + router.Handle("/api/v1/test", shared) + golden := "{\"error\":{\"message\":\"Refresh the configuration.\",\"type\":\"conflict_error\",\"code\":\"sandbox_generation_stale\",\"param\":\"expected_generation\"}}\n" + for _, path := range []string{"/core/v1/test", "/v1/test", "/api/v1/test"} { + out := httptest.NewRecorder() + request := httptest.NewRequest("GET", path, nil) + request.Header.Set("Authorization", "Bearer admin") + router.ServeHTTP(out, request) + if out.Code != 409 || out.Header().Get("Cache-Control") != "no-store" { + t.Fatal(path, out.Code, out.Header()) + } + if path != "/core/v1/test" { + if out.Body.String() != golden { + t.Fatalf("%s changed public/machine error: %s", path, out.Body) + } + } else { + var body map[string]map[string]any + if err := json.Unmarshal(out.Body.Bytes(), &body); err != nil { + t.Fatal(err) + } + want := map[string]any{"current_generation": float64(4), "kinds": []any{"docker", "microsandbox"}} + if !reflect.DeepEqual(body["error"]["details"], want) { + t.Fatal(body) + } + } + } + for _, path := range []string{"/core/v1/test", "/core/v1/sandbox/unknown"} { + out := httptest.NewRecorder() + router.ServeHTTP(out, httptest.NewRequest("GET", path, nil)) + if out.Code != 401 || !strings.Contains(out.Body.String(), `"code":"invalid_admin_key"`) || strings.Contains(out.Body.String(), `"details"`) { + t.Fatal("authentication did not precede operation", path, out.Code, out.Body) + } + } + // A Core-looking path cannot itself authorize the optional field. + out := httptest.NewRecorder() + shared.ServeHTTP(out, httptest.NewRequest("GET", "/core/v1/test", nil)) + if out.Body.String() != golden { + t.Fatal("request path bypassed the router mark", out.Body) + } +} + +func TestCoreDetailsTypesOmissionAndSnapshot(t *testing.T) { + values := []string{"docker"} + stringsDetail := CoreErrorStrings(values...) + values[0] = "private-request-value" + valid := CoreErrorDetails{"label": CoreErrorString("ready"), "number": CoreErrorNumber(1.5), "enabled": CoreErrorBoolean(true), "unset": CoreErrorNull(), "values": stringsDetail, "empty": CoreErrorStrings()} + for _, details := range []CoreErrorDetails{nil, {}, {"bad": CoreErrorNumber(math.NaN())}, {"bad": CoreErrorNumber(math.Inf(1))}, {"bad": CoreErrorDetail{map[string]string{"nested": "private"}}}, {"": CoreErrorString("private")}, valid} { + out := httptest.NewRecorder() + coreErrorResponses(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + writeCoreError(w, 400, "invalid_request", "Invalid request.", details) + })).ServeHTTP(out, httptest.NewRequest("GET", "/core/v1/test", nil)) + var body map[string]map[string]any + if err := json.Unmarshal(out.Body.Bytes(), &body); err != nil { + t.Fatal(err) + } + _, present := body["error"]["details"] + if present != (len(details) == len(valid)) || strings.Contains(out.Body.String(), "private") { + t.Fatal(out.Body) + } + } +} + +type detailDeadlineWriter struct { + *httptest.ResponseRecorder + deadline time.Time +} + +func (w *detailDeadlineWriter) SetWriteDeadline(value time.Time) error { + w.deadline = value + return nil +} + +func TestCoreMarkerPreservesObservationAndStreamingWrappers(t *testing.T) { + for _, markerOutside := range []bool{true, false} { + out := &detailDeadlineWriter{ResponseRecorder: httptest.NewRecorder()} + var observed []string + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + deadline := time.Unix(1234, 0) + if err := http.NewResponseController(w).SetWriteDeadline(deadline); err != nil { + t.Fatal(err) + } + writeCoreError(w, 503, "execution_unavailable", "Unavailable.", CoreErrorDetails{"retryable": CoreErrorBoolean(false)}) + if f, ok := w.(http.Flusher); !ok { + t.Fatal("Flusher was lost") + } else { + f.Flush() + } + writeError(w, 503, "execution_unavailable", "After headers.") + })) + observer := func(code string) { observed = append(observed, code) } + if markerOutside { + handler = coreErrorResponses(responseHeadersWithErrors(handler, observer)) + } else { + handler = responseHeadersWithErrors(coreErrorResponses(handler), observer) + } + handler.ServeHTTP(out, httptest.NewRequest("GET", "/core/v1/test", nil)) + if !out.Flushed || !out.deadline.Equal(time.Unix(1234, 0)) || out.Header().Get("Openai-Processing-Ms") == "" || !reflect.DeepEqual(observed, []string{"execution_unavailable"}) || !strings.Contains(out.Body.String(), `"details":{"retryable":false}`) { + t.Fatal(markerOutside, out, observed) + } + } +} diff --git a/services/agents-api/internal/api/core_metrics.go b/services/agents-api/internal/api/core_metrics.go index 7f19bf72e..b40106bae 100644 --- a/services/agents-api/internal/api/core_metrics.go +++ b/services/agents-api/internal/api/core_metrics.go @@ -24,7 +24,7 @@ func WithCoreMetrics(service CoreMetricsService) Option { // @Security DeploymentAdminAuth // @Param range query string false "Time range (default 1h)" Enums(1h,6h,24h,7d) // @Success 200 {object} coremetrics.View -// @Failure 400,401,503 {object} v1.ErrorResponse +// @Failure 400,401,503 {object} CoreErrorResponse // @Router /core/v1/metrics [get] func (h *Handler) getCoreMetrics(w http.ResponseWriter, r *http.Request) { values, err := url.ParseQuery(r.URL.RawQuery) diff --git a/services/agents-api/internal/api/core_routes.go b/services/agents-api/internal/api/core_routes.go index 38391bd8a..df1844802 100644 --- a/services/agents-api/internal/api/core_routes.go +++ b/services/agents-api/internal/api/core_routes.go @@ -14,6 +14,7 @@ func (h *Handler) registerCoreRoutes(router chi.Router) { return } router.Route("/core/v1", func(r chi.Router) { + r.Use(coreErrorResponses) r.Use(h.deploymentAuth.authenticate) r.NotFound(func(w http.ResponseWriter, _ *http.Request) { writeError(w, http.StatusNotFound, "not_found", "This Core operation does not exist.") diff --git a/services/agents-api/internal/api/environment_executor_management.go b/services/agents-api/internal/api/environment_executor_management.go index 46bf5b156..75c869718 100644 --- a/services/agents-api/internal/api/environment_executor_management.go +++ b/services/agents-api/internal/api/environment_executor_management.go @@ -52,7 +52,7 @@ func (h *Handler) registerExecutorCredentialRoutes(r chi.Router) { // @Param project_id path string true "Project UUID" // @Param environment_id path string true "Environment UUID" // @Success 200 {object} api.ExecutorCredentialList -// @Failure 401,404,500 {object} v1.ErrorResponse +// @Failure 401,404,500 {object} CoreErrorResponse // @Router /core/v1/projects/{project_id}/environments/{environment_id}/executor-credentials [get] func (h *Handler) listExecutorCredentials(w http.ResponseWriter, r *http.Request, s EnvironmentExecutorStore) { binding, ok := h.adminProjectScope(w, r) @@ -77,7 +77,7 @@ func (h *Handler) listExecutorCredentials(w http.ResponseWriter, r *http.Request // @Param environment_id path string true "Environment UUID" // @Param body body api.EnvironmentExecutorCredentialRequest true "Request" // @Success 201 {object} store.IssuedExecutorCredential -// @Failure 400,401,404,409,500 {object} v1.ErrorResponse +// @Failure 400,401,404,409,500 {object} CoreErrorResponse // @Router /core/v1/projects/{project_id}/environments/{environment_id}/executor-credentials [post] func (h *Handler) issueExecutorCredential(w http.ResponseWriter, r *http.Request, s EnvironmentExecutorStore) { // Check order: request body (400), target (404), archived Project (409), @@ -112,7 +112,7 @@ func (h *Handler) issueExecutorCredential(w http.ResponseWriter, r *http.Request // @Param environment_id path string true "Environment UUID" // @Param key_id path string true "Executor key UUID" // @Success 204 -// @Failure 401,404,500 {object} v1.ErrorResponse +// @Failure 401,404,500 {object} CoreErrorResponse // @Router /core/v1/projects/{project_id}/environments/{environment_id}/executor-credentials/{key_id} [delete] func (h *Handler) revokeExecutorCredential(w http.ResponseWriter, r *http.Request, s EnvironmentExecutorStore) { binding, ok := h.adminProjectScope(w, r) diff --git a/services/agents-api/internal/api/errors.go b/services/agents-api/internal/api/errors.go index 4ba1e2199..d28e126ff 100644 --- a/services/agents-api/internal/api/errors.go +++ b/services/agents-api/internal/api/errors.go @@ -24,9 +24,11 @@ func writeJSON(w http.ResponseWriter, status int, value any) { // Every 401 has type invalid_request_error, as every observed official 401 // does (HP-07); an empty code serializes as null. func writeError(w http.ResponseWriter, status int, code, message string, param ...string) { - if observer, ok := w.(interface{ reportAPIError(string) }); ok { - observer.reportAPIError(code) - } + writeAPIError(w, status, code, message, nil, param...) +} + +func writeAPIError(w http.ResponseWriter, status int, code, message string, details CoreErrorDetails, param ...string) { + reportAPIError(w, code) kind := "invalid_request_error" if status >= 500 { kind = "server_error" @@ -43,6 +45,10 @@ func writeError(w http.ResponseWriter, status int, code, message string, param . if len(param) > 0 { errorParam = ¶m[0] } + if isCoreErrorWriter(w) { + writeJSON(w, status, CoreErrorResponse{Error: CoreAPIError{Message: message, Type: kind, Code: errorCode, Param: errorParam, Details: validCoreDetails(details)}}) + return + } writeJSON(w, status, v1.ErrorResponse{Error: v1.APIError{Message: message, Type: kind, Code: errorCode, Param: errorParam}}) } diff --git a/services/agents-api/internal/api/harness_model_providers.go b/services/agents-api/internal/api/harness_model_providers.go index dea43de51..e5ccf9bb4 100644 --- a/services/agents-api/internal/api/harness_model_providers.go +++ b/services/agents-api/internal/api/harness_model_providers.go @@ -85,7 +85,7 @@ func knownHarness(w http.ResponseWriter, r *http.Request) (string, bool) { // @Produce json // @Security DeploymentAdminAuth // @Success 200 {object} api.CoreHarnessList -// @Failure 401,500 {object} v1.ErrorResponse +// @Failure 401,500 {object} CoreErrorResponse // @Router /core/v1/harnesses [get] func (h *Handler) listHarnesses(w http.ResponseWriter, r *http.Request, s DeploymentModelProviderStore) { providers, err := s.ListDeploymentModelProviders(r.Context()) @@ -113,7 +113,7 @@ func (h *Handler) listHarnesses(w http.ResponseWriter, r *http.Request, s Deploy // @Security DeploymentAdminAuth // @Param harness path string true "Harness" Enums(claude_sdk,codex,mcode) // @Success 200 {object} api.HarnessModelProvider -// @Failure 401,404,500 {object} v1.ErrorResponse +// @Failure 401,404,500 {object} CoreErrorResponse // @Router /core/v1/harnesses/{harness}/model-provider [get] func (h *Handler) getHarnessModelProvider(w http.ResponseWriter, r *http.Request, s DeploymentModelProviderStore) { harness, ok := knownHarness(w, r) @@ -149,7 +149,7 @@ var harnessModelProviderShape = shape{kind: objectValue, members: []member{ // @Param harness path string true "Harness" Enums(claude_sdk,codex,mcode) // @Param body body v1.ModelProviderInput true "Complete model provider bundle" // @Success 200 {object} api.HarnessModelProvider -// @Failure 400,401,404,413,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,413,500,503 {object} CoreErrorResponse // @Router /core/v1/harnesses/{harness}/model-provider [put] func (h *Handler) setHarnessModelProvider(w http.ResponseWriter, r *http.Request, s DeploymentModelProviderStore) { harness, ok := knownHarness(w, r) @@ -185,7 +185,7 @@ func (h *Handler) setHarnessModelProvider(w http.ResponseWriter, r *http.Request // @Security DeploymentAdminAuth // @Param harness path string true "Harness" Enums(claude_sdk,codex,mcode) // @Success 204 -// @Failure 401,404,500 {object} v1.ErrorResponse +// @Failure 401,404,500 {object} CoreErrorResponse // @Router /core/v1/harnesses/{harness}/model-provider [delete] func (h *Handler) deleteHarnessModelProvider(w http.ResponseWriter, r *http.Request, s DeploymentModelProviderStore) { harness, ok := knownHarness(w, r) diff --git a/services/agents-api/internal/api/installation.go b/services/agents-api/internal/api/installation.go index 1358bd0b7..726157bbc 100644 --- a/services/agents-api/internal/api/installation.go +++ b/services/agents-api/internal/api/installation.go @@ -112,7 +112,7 @@ func WithInstallation(value Installation, bindings func(context.Context) (store. // @Produce json // @Security DeploymentAdminAuth // @Success 200 {object} api.Installation -// @Failure 401,500 {object} v1.ErrorResponse +// @Failure 401,500 {object} CoreErrorResponse // @Router /core/v1/installation [get] func (h *Handler) getInstallation(w http.ResponseWriter, r *http.Request) { bindings, err := h.installationBindings(r.Context()) diff --git a/services/agents-api/internal/api/project_api_keys.go b/services/agents-api/internal/api/project_api_keys.go index 8fa4c4847..84b329876 100644 --- a/services/agents-api/internal/api/project_api_keys.go +++ b/services/agents-api/internal/api/project_api_keys.go @@ -108,7 +108,7 @@ func adminCatalogPage(r *http.Request) (string, int, bool, error) { // @Param limit query int false "Page size (1-100)" // @Param order query string false "asc or desc by Project ID" // @Success 200 {object} store.ProjectPage -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Router /core/v1/projects [get] func (h *Handler) listProjects(w http.ResponseWriter, r *http.Request) { after, limit, ascending, err := adminCatalogPage(r) @@ -131,7 +131,7 @@ func (h *Handler) listProjects(w http.ResponseWriter, r *http.Request) { // @Security DeploymentAdminAuth // @Param body body api.ProjectRequest true "Project display name" // @Success 201 {object} store.Project -// @Failure 400,401,409,500 {object} v1.ErrorResponse +// @Failure 400,401,409,500 {object} CoreErrorResponse // @Router /core/v1/projects [post] func (h *Handler) createProject(w http.ResponseWriter, r *http.Request) { raw, ok := readJSONBodyLimit(w, r, 4096, "Project request is too large.") @@ -161,7 +161,7 @@ func (h *Handler) createProject(w http.ResponseWriter, r *http.Request) { // @Param project_id path string true "Project UUID" // @Param body body api.ProjectRequest true "Project display name" // @Success 200 {object} store.Project -// @Failure 400,401,404,409,500 {object} v1.ErrorResponse +// @Failure 400,401,404,409,500 {object} CoreErrorResponse // @Router /core/v1/projects/{project_id} [post] func (h *Handler) renameProject(w http.ResponseWriter, r *http.Request) { binding, ok := h.adminProjectScope(w, r) @@ -191,7 +191,7 @@ func (h *Handler) renameProject(w http.ResponseWriter, r *http.Request) { // @Security DeploymentAdminAuth // @Param project_id path string true "Project UUID" // @Success 200 {object} store.Project -// @Failure 401,404,409,500 {object} v1.ErrorResponse +// @Failure 401,404,409,500 {object} CoreErrorResponse // @Router /core/v1/projects/{project_id}/archive [post] func (h *Handler) archiveProject(w http.ResponseWriter, r *http.Request) { binding, ok := h.adminProjectScope(w, r) @@ -215,7 +215,7 @@ func (h *Handler) archiveProject(w http.ResponseWriter, r *http.Request) { // @Param limit query int false "Page size (1-100)" // @Param order query string false "asc or desc by key ID" // @Success 200 {object} store.ProjectAPIKeyPage -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Router /core/v1/projects/{project_id}/keys [get] func (h *Handler) listProjectAPIKeys(w http.ResponseWriter, r *http.Request) { binding, ok := h.adminProjectScope(w, r) @@ -243,7 +243,7 @@ func (h *Handler) listProjectAPIKeys(w http.ResponseWriter, r *http.Request) { // @Param project_id path string true "Project UUID" // @Param body body api.ProjectAPIKeyRequest true "Key display name" // @Success 201 {object} store.IssuedProjectAPIKey -// @Failure 400,401,404,409,500 {object} v1.ErrorResponse +// @Failure 400,401,404,409,500 {object} CoreErrorResponse // @Router /core/v1/projects/{project_id}/keys [post] func (h *Handler) createProjectAPIKey(w http.ResponseWriter, r *http.Request) { binding, ok := h.adminProjectScope(w, r) @@ -274,7 +274,7 @@ func (h *Handler) createProjectAPIKey(w http.ResponseWriter, r *http.Request) { // @Param project_id path string true "Project UUID" // @Param key_id path string true "API key UUID" // @Success 200 {object} api.SandboxMutationResponse -// @Failure 401,404,409,500 {object} v1.ErrorResponse +// @Failure 401,404,409,500 {object} CoreErrorResponse // @Router /core/v1/projects/{project_id}/keys/{key_id} [delete] func (h *Handler) revokeProjectAPIKey(w http.ResponseWriter, r *http.Request) { binding, ok := h.adminProjectScope(w, r) diff --git a/services/agents-api/internal/api/sandbox_deployment_setup.go b/services/agents-api/internal/api/sandbox_deployment_setup.go index 210bd2a8b..c9eecbe76 100644 --- a/services/agents-api/internal/api/sandbox_deployment_setup.go +++ b/services/agents-api/internal/api/sandbox_deployment_setup.go @@ -71,7 +71,7 @@ func WithSandboxDeploymentChanges( // @Accept json // @Param body body api.SandboxDeploymentInput true "Deployment selection" // @Success 200 {object} store.RuntimeDeploymentView -// @Failure 400,401,409,500,503 {object} v1.ErrorResponse +// @Failure 400,401,409,500,503 {object} CoreErrorResponse // @Router /core/v1/sandbox/deployment [post] func (h *Handler) initializeSandboxDeployment(w http.ResponseWriter, r *http.Request) { raw, ok := readJSONBody(w, r) @@ -106,7 +106,7 @@ func (h *Handler) initializeSandboxDeployment(w http.ResponseWriter, r *http.Req // @Accept json // @Param body body api.SandboxDeploymentChangeInput true "Replacement deployment selection" // @Success 200 {object} store.RuntimeDeploymentView -// @Failure 400,401,409,500,503 {object} v1.ErrorResponse +// @Failure 400,401,409,500,503 {object} CoreErrorResponse // @Router /core/v1/sandbox/deployment [put] func (h *Handler) updateSandboxDeployment(w http.ResponseWriter, r *http.Request) { raw, ok := readJSONBody(w, r) @@ -141,7 +141,7 @@ func (h *Handler) updateSandboxDeployment(w http.ResponseWriter, r *http.Request // @Accept json // @Param body body store.SandboxMaintenanceRequest true "Maintenance state" // @Success 200 {object} store.RuntimeDeploymentView -// @Failure 400,401,409,500,503 {object} v1.ErrorResponse +// @Failure 400,401,409,500,503 {object} CoreErrorResponse // @Router /core/v1/sandbox/deployment/maintenance [patch] func (h *Handler) setSandboxMaintenance(w http.ResponseWriter, r *http.Request) { raw, ok := readJSONBody(w, r) diff --git a/services/agents-api/internal/api/sandbox_manager.go b/services/agents-api/internal/api/sandbox_manager.go index 6dd20649f..c24b9197a 100644 --- a/services/agents-api/internal/api/sandbox_manager.go +++ b/services/agents-api/internal/api/sandbox_manager.go @@ -70,7 +70,7 @@ func (h *Handler) registerSandboxManagerRoutes(r chi.Router) { // @Produce json // @Security DeploymentAdminAuth // @Success 200 {object} store.RuntimeDeploymentView -// @Failure 400,401,404,409,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,409,500,503 {object} CoreErrorResponse // @Router /core/v1/sandbox/deployment [get] func (h *Handler) sandboxDeployment(w http.ResponseWriter, r *http.Request) { value, err := h.sandboxStore.GetRuntimeDeployment(r.Context()) @@ -87,7 +87,7 @@ func (h *Handler) sandboxDeployment(w http.ResponseWriter, r *http.Request) { // @Produce json // @Security DeploymentAdminAuth // @Success 200 {object} api.SandboxNodeList -// @Failure 400,401,404,409,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,409,500,503 {object} CoreErrorResponse // @Router /core/v1/sandbox/nodes [get] func (h *Handler) sandboxNodes(w http.ResponseWriter, r *http.Request) { value, err := h.sandboxStore.ListRuntimeNodes(r.Context()) @@ -107,7 +107,7 @@ func (h *Handler) sandboxNodes(w http.ResponseWriter, r *http.Request) { // @Accept json // @Param body body store.RuntimeNodeUpdate true "Request" // @Success 200 {object} api.SandboxMutationResponse -// @Failure 400,401,404,409,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,409,500,503 {object} CoreErrorResponse // @Router /core/v1/sandbox/nodes/{node_id} [patch] func (h *Handler) updateSandboxNode(w http.ResponseWriter, r *http.Request) { raw, ok := readJSONBody(w, r) @@ -134,7 +134,7 @@ func (h *Handler) updateSandboxNode(w http.ResponseWriter, r *http.Request) { // @Security DeploymentAdminAuth // @Param node_id path string true "Sandbox node UUID" // @Success 200 {object} api.SandboxMutationResponse -// @Failure 400,401,404,409,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,409,500,503 {object} CoreErrorResponse // @Router /core/v1/sandbox/nodes/{node_id} [delete] func (h *Handler) removeSandboxNode(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "node_id") @@ -152,7 +152,7 @@ func (h *Handler) removeSandboxNode(w http.ResponseWriter, r *http.Request) { // @Security DeploymentAdminAuth // @Param node_id path string true "Sandbox node UUID" // @Success 200 {object} api.SandboxAllocationList -// @Failure 400,401,404,409,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,409,500,503 {object} CoreErrorResponse // @Router /core/v1/sandbox/nodes/{node_id}/allocations [get] func (h *Handler) sandboxAllocations(w http.ResponseWriter, r *http.Request) { value, err := h.sandboxStore.ListNodeRuntimeAllocations(r.Context(), chi.URLParam(r, "node_id")) @@ -171,7 +171,7 @@ func (h *Handler) sandboxAllocations(w http.ResponseWriter, r *http.Request) { // @Accept json // @Param body body api.SandboxEnrollmentTokenRequest true "Request" // @Success 201 {object} api.SandboxEnrollmentToken -// @Failure 400,401,404,409,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,409,500,503 {object} CoreErrorResponse // @Router /core/v1/sandbox/enrollment-tokens [post] func (h *Handler) createSandboxEnrollment(w http.ResponseWriter, r *http.Request) { raw, ok := readJSONBody(w, r) diff --git a/services/agents-api/internal/api/sandbox_node_detail.go b/services/agents-api/internal/api/sandbox_node_detail.go index 3d47db1eb..b7e6a581c 100644 --- a/services/agents-api/internal/api/sandbox_node_detail.go +++ b/services/agents-api/internal/api/sandbox_node_detail.go @@ -18,7 +18,7 @@ import ( // @Param node_id path string true "Sandbox node UUID" // @Param range query string false "Time range (default 1h)" Enums(1h,6h,24h) // @Success 200 {object} store.RuntimeNodeDetail -// @Failure 400,401,404,500,503 {object} v1.ErrorResponse +// @Failure 400,401,404,500,503 {object} CoreErrorResponse // @Router /core/v1/sandbox/nodes/{node_id} [get] func (h *Handler) sandboxNodeDetail(w http.ResponseWriter, r *http.Request) { values, err := url.ParseQuery(r.URL.RawQuery) diff --git a/services/agents-api/internal/api/write_audit.go b/services/agents-api/internal/api/write_audit.go index eb3d88e88..fe2e6b528 100644 --- a/services/agents-api/internal/api/write_audit.go +++ b/services/agents-api/internal/api/write_audit.go @@ -65,7 +65,7 @@ func (h *Handler) writeAuditScope(w http.ResponseWriter, r *http.Request, allowe // @Param resource_type query string true "Resource type" // @Param resource_ids query string true "Comma-separated public resource IDs, maximum 100" // @Success 200 {object} api.ResourceOwnerList -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Router /core/v1/projects/{project_id}/resource-owners [get] func (h *Handler) getResourceOwners(w http.ResponseWriter, r *http.Request) { values, tenant, ok := h.writeAuditScope(w, r, "resource_type", "resource_ids") @@ -108,7 +108,7 @@ func (h *Handler) getResourceOwners(w http.ResponseWriter, r *http.Request) { // @Param limit query int false "Page size, 1-100, default 50" // @Param after query string false "Opaque next_cursor from the preceding page" // @Success 200 {object} store.WriteOperationPage -// @Failure 400,401,404,500 {object} v1.ErrorResponse +// @Failure 400,401,404,500 {object} CoreErrorResponse // @Router /core/v1/projects/{project_id}/write-operations [get] func (h *Handler) listWriteOperations(w http.ResponseWriter, r *http.Request) { values, tenant, ok := h.writeAuditScope(w, r, "key_id", "resource_type", "resource_id", "created_after", "created_before", "limit", "after") diff --git a/services/core-console/core_errors.go b/services/core-console/core_errors.go new file mode 100644 index 000000000..a2a7e85b0 --- /dev/null +++ b/services/core-console/core_errors.go @@ -0,0 +1,33 @@ +package main + +import ( + "net/http" + "strings" +) + +// Console failures have fixed safe text, not transport errors or request values. +// Core's own error responses pass through the proxy without reinterpretation. +type consoleCoreAPIError struct { + Message string `json:"message"` + Type string `json:"type"` + Code string `json:"code"` + Param *string `json:"param"` +} + +func consoleCoreError(w http.ResponseWriter, status int, code, message string) { + kind := "invalid_request_error" + if status >= 500 { + kind = "server_error" + } + authJSON(w, status, struct { + Error consoleCoreAPIError `json:"error"` + }{ + Error: consoleCoreAPIError{Message: message, Type: kind, Code: code}, + }) +} + +// The namespace controls local error formatting only. It does not authorize a +// request or classify a bare/retired path as a proxyable Core operation. +func consoleCoreNamespace(r *http.Request) bool { + return r.URL.Path == "/core" || strings.HasPrefix(r.URL.Path, "/core/") +} diff --git a/services/core-console/core_errors_test.go b/services/core-console/core_errors_test.go new file mode 100644 index 000000000..ec2aed1b9 --- /dev/null +++ b/services/core-console/core_errors_test.go @@ -0,0 +1,78 @@ +package main + +import ( + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "strings" + "sync/atomic" + "testing" +) + +func assertConsoleCoreError(t *testing.T, body string, code string, status int) { + t.Helper() + var envelope struct { + Error map[string]any `json:"error"` + } + if err := json.Unmarshal([]byte(body), &envelope); err != nil { + t.Fatal(err, body) + } + kind := "invalid_request_error" + if status >= 500 { + kind = "server_error" + } + if len(envelope.Error) != 4 || envelope.Error["code"] != code || envelope.Error["param"] != nil || envelope.Error["type"] != kind { + t.Fatal(body) + } +} + +type failingCoreTransport struct{ calls int } + +func (transport *failingCoreTransport) RoundTrip(*http.Request) (*http.Response, error) { + transport.calls++ + return nil, errors.New("private provider URL https://private.invalid/?api_key=private-secret") +} + +func TestCoreProxyFailureEnvelopeIsSafeAndNeverRetries(t *testing.T) { + c := coreKeyConsoleConfig(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Error("unexpected real transport") })) + h := startConsole(t, c) + cookie := signIn(t, h) + transport := &failingCoreTransport{} + h.proxy.Transport = transport + out := authRequest(h, "GET", "/core/v1/projects", "", cookie) + if out.Code != 502 || transport.calls != 1 || strings.Contains(out.Body.String(), "private") || out.Header().Get("Content-Type") != "application/json" { + t.Fatal(out.Code, transport.calls, out.Body) + } + assertConsoleCoreError(t, out.Body.String(), "core_unreachable", 502) +} + +func TestCoreProxyPreservesUpstreamEnvelopeAndAuthShape(t *testing.T) { + const upstream = `{"error":{"message":"Refresh the configuration.","type":"conflict_error","code":"sandbox_generation_stale","param":"expected_generation","details":{"current_generation":3}}}` + var calls atomic.Int32 + c := coreKeyConsoleConfig(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls.Add(1) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(409) + _, _ = w.Write([]byte(upstream)) + })) + h := startConsole(t, c) + cookie := signIn(t, h) + out := authRequest(h, "GET", "/core/v1/sandbox/deployment", "", cookie) + if out.Code != 409 || out.Body.String() != upstream || calls.Load() != 1 { + t.Fatal(out.Code, out.Body, calls.Load()) + } + for _, path := range []string{"/core", "/core/", "/core/v1", "/core/retired", "/v1/agents", "/api/v1/sandbox-node/configuration"} { + out := authRequest(h, "GET", path, "", cookie) + if out.Code != 404 || calls.Load() != 1 { + t.Fatal("non-operation became proxyable", path, out.Code, calls.Load()) + } + } + request := httptest.NewRequest("GET", "/console/auth", nil) + request.Host = "private-host.invalid" + out = httptest.NewRecorder() + h.ServeHTTP(out, request) + if out.Code != 403 || out.Body.String() != "{\"error\":\"Forbidden\"}\n" { + t.Fatal("auth shape changed", out.Code, out.Body) + } +} diff --git a/services/core-console/server.go b/services/core-console/server.go index b301bdeb2..ed5e54ff4 100644 --- a/services/core-console/server.go +++ b/services/core-console/server.go @@ -93,7 +93,7 @@ func newConsole(c config) (*console, error) { return nil }, ErrorHandler: func(w http.ResponseWriter, _ *http.Request, _ error) { - http.Error(w, "Core is unavailable", http.StatusBadGateway) + consoleCoreError(w, http.StatusBadGateway, "core_unreachable", "Core is unavailable") }, } return h, nil @@ -130,11 +130,19 @@ func (h *console) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } if !h.sameOrigin(r) { - authError(w, http.StatusForbidden, "Forbidden") + if consoleCoreNamespace(r) { + consoleCoreError(w, http.StatusForbidden, "console_origin_rejected", "Forbidden") + } else { + authError(w, http.StatusForbidden, "Forbidden") + } return } if !safePath(r.URL.Path) || r.URL.IsAbs() || r.Method == http.MethodConnect || r.Method == http.MethodTrace || r.Header.Get("Upgrade") != "" { - http.Error(w, "Invalid request", http.StatusBadRequest) + if consoleCoreNamespace(r) { + consoleCoreError(w, http.StatusBadRequest, "console_request_invalid", "Invalid request") + } else { + http.Error(w, "Invalid request", http.StatusBadRequest) + } return } if r.URL.Path == "/console/auth" || strings.HasPrefix(r.URL.Path, "/console/auth/") { @@ -146,7 +154,11 @@ func (h *console) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } if !h.auth.authenticated(r) { - authError(w, http.StatusUnauthorized, "Sign in to the console") + if consoleCoreNamespace(r) { + consoleCoreError(w, http.StatusUnauthorized, "console_sign_in_required", "Sign in to the console") + } else { + authError(w, http.StatusUnauthorized, "Sign in to the console") + } return } if r.URL.Path == "/console/config" && r.Method == http.MethodGet { diff --git a/services/core-console/server_test.go b/services/core-console/server_test.go index 5782cb0b7..7eff1f119 100644 --- a/services/core-console/server_test.go +++ b/services/core-console/server_test.go @@ -107,6 +107,8 @@ func TestAuthenticationAndCrossSiteAdmission(t *testing.T) { if response.StatusCode != tc.status { t.Fatalf("status = %d, body = %s", response.StatusCode, body) } + code := map[int]string{401: "console_sign_in_required", 403: "console_origin_rejected", 400: "console_request_invalid"}[tc.status] + assertConsoleCoreError(t, body, code, tc.status) if strings.Contains(body, "private-") { t.Fatal("rejection exposed a credential") } @@ -178,6 +180,7 @@ func TestProxyRejectsRedirectWithoutFollowingOrExposingIt(t *testing.T) { http.Redirect(w, r, destination.URL+"/?token=private-core-key", http.StatusTemporaryRedirect) })) response, body := responseBody(t, server, consoleRequest(t, server, "GET", "/core/v1/projects")) + assertConsoleCoreError(t, body, "core_unreachable", 502) if response.StatusCode != 502 || response.Header.Get("Location") != "" || strings.Contains(body, "private-core-key") || destinationCalls.Load() != 0 { t.Fatal("upstream redirect escaped the fixed proxy") } From 80e213bb95df7e42ad85c811d91b0cbc7134bb27 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Sun, 27 Sep 2026 19:19:44 +0000 Subject: [PATCH 2/2] Integrate operator documentation site with Core error contracts --- .gitignore | 5 + Makefile | 6 +- apps/docs/FOLLOW-UP.md | 24 + apps/docs/README.md | 48 + apps/docs/app/[lang]/[[...slug]]/page.tsx | 78 + apps/docs/app/[lang]/docs-providers.tsx | 77 + apps/docs/app/[lang]/layout.tsx | 54 + apps/docs/app/global.css | 103 + apps/docs/app/icon.png | Bin 0 -> 7963 bytes apps/docs/app/layout.config.tsx | 11 + apps/docs/app/not-found.tsx | 69 + apps/docs/app/sitemap.ts | 48 + apps/docs/components/api-page.tsx | 13 + apps/docs/components/locale-link.tsx | 24 + apps/docs/content/docs/admin-api.mdx | 160 + apps/docs/content/docs/admin-api.zh.mdx | 10 + apps/docs/content/docs/agents-and-tools.mdx | 124 + .../docs/content/docs/agents-and-tools.zh.mdx | 10 + .../content/docs/api-reference/agents.mdx | 75 + .../content/docs/api-reference/artifacts.mdx | 33 + .../core/administrator-projects.mdx | 17 + .../docs/api-reference/core/agents.mdx | 29 + .../docs/api-reference/core/artifacts.mdx | 33 + .../core/core-administration.mdx | 56 + .../docs/api-reference/core/credentials.mdx | 29 + .../core/deployment-model-providers.mdx | 43 + .../core/environment-templates.mdx | 29 + .../core/execution-configuration.mdx | 27 + .../core/executor-credentials.mdx | 40 + .../content/docs/api-reference/core/files.mdx | 29 + .../content/docs/api-reference/core/index.mdx | 32 + .../content/docs/api-reference/core/items.mdx | 23 + .../content/docs/api-reference/core/meta.json | 25 + .../api-reference/core/runtime-history.mdx | 26 + .../core/runtime-observations.mdx | 23 + .../api-reference/core/sandbox-manager.mdx | 61 + .../docs/api-reference/core/sessions.mdx | 29 + .../docs/api-reference/core/skills.mdx | 49 + .../content/docs/api-reference/core/turns.mdx | 25 + .../docs/api-reference/core/vaults.mdx | 29 + .../docs/api-reference/core/write-audit.mdx | 26 + .../docs/api-reference/credentials.mdx | 74 + .../api-reference/environment-templates.mdx | 56 + .../docs/api-reference/environments.mdx | 63 + .../content/docs/api-reference/events.mdx | 33 + .../docs/content/docs/api-reference/files.mdx | 48 + .../docs/content/docs/api-reference/index.mdx | 24 + .../docs/content/docs/api-reference/items.mdx | 26 + .../docs/api-reference/machine/index.mdx | 14 + .../docs/api-reference/machine/meta.json | 7 + .../api-reference/machine/sandbox-node.mdx | 31 + .../docs/content/docs/api-reference/meta.json | 21 + .../content/docs/api-reference/sessions.mdx | 239 + .../content/docs/api-reference/skills.mdx | 51 + .../content/docs/api-reference/subagents.mdx | 49 + .../docs/content/docs/api-reference/turns.mdx | 28 + .../content/docs/api-reference/vaults.mdx | 50 + .../content/docs/bootstrap-projects-keys.mdx | 101 + .../docs/bootstrap-projects-keys.zh.mdx | 10 + apps/docs/content/docs/concepts.mdx | 88 + apps/docs/content/docs/concepts.zh.mdx | 10 + apps/docs/content/docs/configure.mdx | 253 + apps/docs/content/docs/configure.zh.mdx | 10 + apps/docs/content/docs/console.mdx | 74 + apps/docs/content/docs/console.zh.mdx | 10 + .../content/docs/environments-and-files.mdx | 218 + .../docs/environments-and-files.zh.mdx | 10 + apps/docs/content/docs/execution-model.mdx | 111 + apps/docs/content/docs/execution-model.zh.mdx | 10 + apps/docs/content/docs/hosted-providers.mdx | 309 + .../docs/content/docs/hosted-providers.zh.mdx | 10 + apps/docs/content/docs/index.mdx | 28 + apps/docs/content/docs/index.zh.mdx | 10 + apps/docs/content/docs/install.mdx | 533 ++ apps/docs/content/docs/install.zh.mdx | 10 + apps/docs/content/docs/meta.json | 27 + apps/docs/content/docs/meta.zh.json | 27 + apps/docs/content/docs/observability.mdx | 282 + apps/docs/content/docs/observability.zh.mdx | 10 + apps/docs/content/docs/public-api.mdx | 105 + apps/docs/content/docs/public-api.zh.mdx | 10 + apps/docs/content/docs/quickstart.mdx | 149 + apps/docs/content/docs/quickstart.zh.mdx | 10 + .../content/docs/self-hosted-execution.mdx | 169 + .../content/docs/self-hosted-execution.zh.mdx | 10 + apps/docs/content/docs/sessions.mdx | 97 + apps/docs/content/docs/sessions.zh.mdx | 10 + apps/docs/content/docs/troubleshooting.mdx | 407 + apps/docs/content/docs/troubleshooting.zh.mdx | 10 + apps/docs/content/guide-sources.json | 61 + apps/docs/lib/i18n.ts | 10 + apps/docs/lib/legacy-redirects.test.ts | 55 + apps/docs/lib/legacy-redirects.ts | 70 + apps/docs/lib/locale-link.test.ts | 69 + apps/docs/lib/locale-link.ts | 53 + apps/docs/lib/openapi.ts | 45 + apps/docs/lib/site.ts | 86 + apps/docs/lib/source.ts | 9 + apps/docs/middleware.ts | 62 + apps/docs/next-env.d.ts | 6 + apps/docs/next.config.mjs | 17 + apps/docs/openapi/core-api.yaml | 7130 +++++++++++++++++ apps/docs/openapi/public-api.yaml | 5693 +++++++++++++ apps/docs/openapi/runtime-api.yaml | 331 + apps/docs/openapi/sources.json | 51 + apps/docs/package.json | 46 + apps/docs/postcss.config.mjs | 7 + apps/docs/public/images/architecture.svg | 10 + .../web/public/onboarding/monitor-en.webp | Bin 0 -> 43958 bytes apps/docs/scripts/check-browser.mjs | 44 + apps/docs/scripts/check-python.mjs | 20 + apps/docs/scripts/check-python.test.mjs | 30 + apps/docs/scripts/check-site.mjs | 81 + apps/docs/scripts/contracts.mjs | 91 + apps/docs/scripts/contracts.test.mjs | 85 + apps/docs/scripts/generate-api-reference.mjs | 38 + apps/docs/scripts/generate-guides.mjs | 57 + apps/docs/scripts/guides.json | 124 + apps/docs/scripts/test_contract_routes.py | 28 + apps/docs/scripts/verify-api-copy.mjs | 25 + .../scripts/verify-contract-freshness.mjs | 24 + apps/docs/scripts/verify-contract-routes.py | 227 + apps/docs/scripts/verify-docs-facts.mjs | 27 + apps/docs/scripts/verify-links.mjs | 132 + apps/docs/scripts/verify-prerender.mjs | 84 + apps/docs/source.config.ts | 9 + apps/docs/tsconfig.json | 25 + package.json | 5 +- pnpm-lock.yaml | 3588 ++++++++- pnpm-workspace.yaml | 1 + scripts/name-allowlist.json | 45 + 131 files changed, 23838 insertions(+), 357 deletions(-) create mode 100644 apps/docs/FOLLOW-UP.md create mode 100644 apps/docs/README.md create mode 100644 apps/docs/app/[lang]/[[...slug]]/page.tsx create mode 100644 apps/docs/app/[lang]/docs-providers.tsx create mode 100644 apps/docs/app/[lang]/layout.tsx create mode 100644 apps/docs/app/global.css create mode 100644 apps/docs/app/icon.png create mode 100644 apps/docs/app/layout.config.tsx create mode 100644 apps/docs/app/not-found.tsx create mode 100644 apps/docs/app/sitemap.ts create mode 100644 apps/docs/components/api-page.tsx create mode 100644 apps/docs/components/locale-link.tsx create mode 100644 apps/docs/content/docs/admin-api.mdx create mode 100644 apps/docs/content/docs/admin-api.zh.mdx create mode 100644 apps/docs/content/docs/agents-and-tools.mdx create mode 100644 apps/docs/content/docs/agents-and-tools.zh.mdx create mode 100644 apps/docs/content/docs/api-reference/agents.mdx create mode 100644 apps/docs/content/docs/api-reference/artifacts.mdx create mode 100644 apps/docs/content/docs/api-reference/core/administrator-projects.mdx create mode 100644 apps/docs/content/docs/api-reference/core/agents.mdx create mode 100644 apps/docs/content/docs/api-reference/core/artifacts.mdx create mode 100644 apps/docs/content/docs/api-reference/core/core-administration.mdx create mode 100644 apps/docs/content/docs/api-reference/core/credentials.mdx create mode 100644 apps/docs/content/docs/api-reference/core/deployment-model-providers.mdx create mode 100644 apps/docs/content/docs/api-reference/core/environment-templates.mdx create mode 100644 apps/docs/content/docs/api-reference/core/execution-configuration.mdx create mode 100644 apps/docs/content/docs/api-reference/core/executor-credentials.mdx create mode 100644 apps/docs/content/docs/api-reference/core/files.mdx create mode 100644 apps/docs/content/docs/api-reference/core/index.mdx create mode 100644 apps/docs/content/docs/api-reference/core/items.mdx create mode 100644 apps/docs/content/docs/api-reference/core/meta.json create mode 100644 apps/docs/content/docs/api-reference/core/runtime-history.mdx create mode 100644 apps/docs/content/docs/api-reference/core/runtime-observations.mdx create mode 100644 apps/docs/content/docs/api-reference/core/sandbox-manager.mdx create mode 100644 apps/docs/content/docs/api-reference/core/sessions.mdx create mode 100644 apps/docs/content/docs/api-reference/core/skills.mdx create mode 100644 apps/docs/content/docs/api-reference/core/turns.mdx create mode 100644 apps/docs/content/docs/api-reference/core/vaults.mdx create mode 100644 apps/docs/content/docs/api-reference/core/write-audit.mdx create mode 100644 apps/docs/content/docs/api-reference/credentials.mdx create mode 100644 apps/docs/content/docs/api-reference/environment-templates.mdx create mode 100644 apps/docs/content/docs/api-reference/environments.mdx create mode 100644 apps/docs/content/docs/api-reference/events.mdx create mode 100644 apps/docs/content/docs/api-reference/files.mdx create mode 100644 apps/docs/content/docs/api-reference/index.mdx create mode 100644 apps/docs/content/docs/api-reference/items.mdx create mode 100644 apps/docs/content/docs/api-reference/machine/index.mdx create mode 100644 apps/docs/content/docs/api-reference/machine/meta.json create mode 100644 apps/docs/content/docs/api-reference/machine/sandbox-node.mdx create mode 100644 apps/docs/content/docs/api-reference/meta.json create mode 100644 apps/docs/content/docs/api-reference/sessions.mdx create mode 100644 apps/docs/content/docs/api-reference/skills.mdx create mode 100644 apps/docs/content/docs/api-reference/subagents.mdx create mode 100644 apps/docs/content/docs/api-reference/turns.mdx create mode 100644 apps/docs/content/docs/api-reference/vaults.mdx create mode 100644 apps/docs/content/docs/bootstrap-projects-keys.mdx create mode 100644 apps/docs/content/docs/bootstrap-projects-keys.zh.mdx create mode 100644 apps/docs/content/docs/concepts.mdx create mode 100644 apps/docs/content/docs/concepts.zh.mdx create mode 100644 apps/docs/content/docs/configure.mdx create mode 100644 apps/docs/content/docs/configure.zh.mdx create mode 100644 apps/docs/content/docs/console.mdx create mode 100644 apps/docs/content/docs/console.zh.mdx create mode 100644 apps/docs/content/docs/environments-and-files.mdx create mode 100644 apps/docs/content/docs/environments-and-files.zh.mdx create mode 100644 apps/docs/content/docs/execution-model.mdx create mode 100644 apps/docs/content/docs/execution-model.zh.mdx create mode 100644 apps/docs/content/docs/hosted-providers.mdx create mode 100644 apps/docs/content/docs/hosted-providers.zh.mdx create mode 100644 apps/docs/content/docs/index.mdx create mode 100644 apps/docs/content/docs/index.zh.mdx create mode 100644 apps/docs/content/docs/install.mdx create mode 100644 apps/docs/content/docs/install.zh.mdx create mode 100644 apps/docs/content/docs/meta.json create mode 100644 apps/docs/content/docs/meta.zh.json create mode 100644 apps/docs/content/docs/observability.mdx create mode 100644 apps/docs/content/docs/observability.zh.mdx create mode 100644 apps/docs/content/docs/public-api.mdx create mode 100644 apps/docs/content/docs/public-api.zh.mdx create mode 100644 apps/docs/content/docs/quickstart.mdx create mode 100644 apps/docs/content/docs/quickstart.zh.mdx create mode 100644 apps/docs/content/docs/self-hosted-execution.mdx create mode 100644 apps/docs/content/docs/self-hosted-execution.zh.mdx create mode 100644 apps/docs/content/docs/sessions.mdx create mode 100644 apps/docs/content/docs/sessions.zh.mdx create mode 100644 apps/docs/content/docs/troubleshooting.mdx create mode 100644 apps/docs/content/docs/troubleshooting.zh.mdx create mode 100644 apps/docs/content/guide-sources.json create mode 100644 apps/docs/lib/i18n.ts create mode 100644 apps/docs/lib/legacy-redirects.test.ts create mode 100644 apps/docs/lib/legacy-redirects.ts create mode 100644 apps/docs/lib/locale-link.test.ts create mode 100644 apps/docs/lib/locale-link.ts create mode 100644 apps/docs/lib/openapi.ts create mode 100644 apps/docs/lib/site.ts create mode 100644 apps/docs/lib/source.ts create mode 100644 apps/docs/middleware.ts create mode 100644 apps/docs/next-env.d.ts create mode 100644 apps/docs/next.config.mjs create mode 100644 apps/docs/openapi/core-api.yaml create mode 100644 apps/docs/openapi/public-api.yaml create mode 100644 apps/docs/openapi/runtime-api.yaml create mode 100644 apps/docs/openapi/sources.json create mode 100644 apps/docs/package.json create mode 100644 apps/docs/postcss.config.mjs create mode 100644 apps/docs/public/images/architecture.svg create mode 100644 apps/docs/public/images/source/apps/web/public/onboarding/monitor-en.webp create mode 100644 apps/docs/scripts/check-browser.mjs create mode 100644 apps/docs/scripts/check-python.mjs create mode 100644 apps/docs/scripts/check-python.test.mjs create mode 100644 apps/docs/scripts/check-site.mjs create mode 100644 apps/docs/scripts/contracts.mjs create mode 100644 apps/docs/scripts/contracts.test.mjs create mode 100644 apps/docs/scripts/generate-api-reference.mjs create mode 100644 apps/docs/scripts/generate-guides.mjs create mode 100644 apps/docs/scripts/guides.json create mode 100644 apps/docs/scripts/test_contract_routes.py create mode 100644 apps/docs/scripts/verify-api-copy.mjs create mode 100644 apps/docs/scripts/verify-contract-freshness.mjs create mode 100644 apps/docs/scripts/verify-contract-routes.py create mode 100644 apps/docs/scripts/verify-docs-facts.mjs create mode 100644 apps/docs/scripts/verify-links.mjs create mode 100644 apps/docs/scripts/verify-prerender.mjs create mode 100644 apps/docs/source.config.ts create mode 100644 apps/docs/tsconfig.json diff --git a/.gitignore b/.gitignore index 9012af483..c8059bfec 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,8 @@ __pycache__/ /.agents/ /.issue-agent/ /ISSUE_AGENT.md + +# Documentation site build output. +/apps/docs/.next/ +/apps/docs/.source/ +/apps/docs/tsconfig.tsbuildinfo diff --git a/Makefile b/Makefile index 6d6b7e414..b1e3112bf 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ SWAG_VERSION ?= v1.16.4 help: @printf '%s\n' 'make build-agents-api Build standalone Core commands' 'make build-daemon Build the execution daemon' 'make check Run Core, persistence and runtime checks' 'See README.md for runtime prerequisites and deployment.' -check: check-names check-distribution check-database check-sqlc check-go check-microsandbox-provider check-agents-api check-claude-sdk check-web check-mcode-harness check-agents-executor +check: check-docs check-names check-distribution check-database check-sqlc check-go check-microsandbox-provider check-agents-api check-claude-sdk check-web check-mcode-harness check-agents-executor @printf 'OpenAgentCore checks passed.\n' .PHONY: check-names @@ -145,3 +145,7 @@ build-e2b-provider: check-e2b-provider: PYTHONDONTWRITEBYTECODE=1 $${OAC_TEST_E2B_SDK_PYTHON:-python3} -m unittest discover -s services/agents-api/deploy/e2b -p '*_test.py' PYTHONDONTWRITEBYTECODE=1 $${OAC_TEST_E2B_SDK_PYTHON:-python3} -m unittest discover -s services/agents-api/tools/e2b-provider -p '*_test.py' + +.PHONY: check-docs +check-docs: node-deps + pnpm check:docs diff --git a/apps/docs/FOLLOW-UP.md b/apps/docs/FOLLOW-UP.md new file mode 100644 index 000000000..232f913c1 --- /dev/null +++ b/apps/docs/FOLLOW-UP.md @@ -0,0 +1,24 @@ +# Feature integration follow-up + +This candidate documents the actual maintenance and drain model at source +`75bf4484ba957344ae374354858bbdfe104f5c4f`. It is not final documentation for design 50. +The Core error-envelope foundation is included; the later configuration validators and +diagnostic endpoints are not. Do not replace current procedures with the planned +behavior before the features land. + +| Feature dependency | Site pages to reconcile | Source and verification work | +| --- | --- | --- | +| Reset replaces maintenance (design 50 PR-R) | configure, hosted-providers, troubleshooting, admin-api, observability, API reference/core | Regenerate the canonical guides and management contract; describe reset admission, cleanup, failures, archive preconditions and offline-node limits from merged code. Remove retired maintenance instructions only after their replacement exists. | +| Online generations and E2B changes (design 50 PR-G) | configure, hosted-providers, troubleshooting, execution-model, API reference/core | Document actual generation/rollout states, the supported change matrix and existing-sandbox behavior. Verify PUT examples against the final contract. | +| Node update protocol (design 50 PR-N) | hosted-providers, troubleshooting, API reference/machine | Explain the implemented update command, old-node handling and ownership preservation. Do not claim an update command now. | +| Later configuration/error changes (design 40 and remaining installer work) | install, configure, troubleshooting, quickstart | Regenerate current configuration fields and revise diagnostics against merged code; keep secrets out of examples. | +| Final feature assembly | every Chinese reading note, execution-model diagram, all generated references | Review translations against regenerated English, verify source provenance and links, run contract/route/copy/fact checks, typecheck/build, local browser review and the repository gate. | + +English guides are rendered from repository sources. Chinese pages are explicitly +labelled reading notes, not complete translations; expand them only against the final +source procedures. API tag pages use the default-language fallback. Release publication, +remote deployment and preview replacement are outside this reconciliation task. + +The source Web guide still carries a pre-rename screenshot. This site copies the +approved current Web onboarding Monitor image instead, with its own source hash; +refresh that mapping when the canonical guide image is updated. diff --git a/apps/docs/README.md b/apps/docs/README.md new file mode 100644 index 000000000..a526c4fa0 --- /dev/null +++ b/apps/docs/README.md @@ -0,0 +1,48 @@ +# OpenAgentCore documentation site + +A separate Next.js/Fumadocs documentation app. It starts no Core, database or Runtime. +English is served at `/`; Chinese reading notes at `/zh`, with English fallback for +API tag pages. The existing preview is not part of this checkout. + +The application reference reads the upstream-constrained public schema. Administration +and machine references read their own local generated contracts. All three repository +files currently declare Swagger 2.0; rendering supports Swagger 2 and OpenAPI 3. +The documentation projection uses OpenAPI 3.1 null unions (including nullable references +and compositions). Normalization changes documentation servers and bearer presentation, preserving operation +prose, parameters, response schemas and credential boundaries. Examples use a reserved +domain. The reference is read-only and must not collect keys or send execution requests. + +English guides are generated from the canonical repository docs, not maintained as a +second installation manual. Edit those sources first, update the Chinese reading notes +in [guides.json](scripts/guides.json), then regenerate. Source/output hashes fail when +copies drift; operation and route checks detect missing or mixed API surfaces. + +From the repository root: + +```sh +pnpm install --frozen-lockfile +pnpm --dir apps/docs generate +pnpm --dir apps/docs verify +pnpm --dir apps/docs test +pnpm --dir apps/docs typecheck +pnpm --dir apps/docs build +pnpm --dir apps/docs start --port 4275 +``` + +The route verifier uses the declared `js-yaml` dependency and Python standard library +only; no ambient PyYAML installation is required. Python checks honor +`OAC_TEST_OFFICIAL_SDK_PYTHON` when set, otherwise `python3`, and run with `-S` +to keep site packages out of the gate. + +`make check-docs` runs the focused gate and is included in `make check`. After starting +the built site, run `pnpm --dir apps/docs check:site http://127.0.0.1:4275` and inspect +both locales in a browser with `pnpm --dir apps/docs check:browser http://127.0.0.1:4275`. +The check uses installed Playwright Chromium (or `DOCS_BROWSER_EXECUTABLE`), checks +for credential/request controls and external traffic, and accepts only a local origin. +Set `DOCS_SITE_ORIGIN` only when preparing actual publication. +Generation links to the recorded source revision; update that revision after rebasing +onto later canonical guide changes. Never point samples at an actual deployment by default. + +[Feature follow-up inventory](FOLLOW-UP.md) lists the reset and online-generation +paragraphs that must be reconciled after those implementations land. This candidate +cannot establish final documentation acceptance for those unfinished features. diff --git a/apps/docs/app/[lang]/[[...slug]]/page.tsx b/apps/docs/app/[lang]/[[...slug]]/page.tsx new file mode 100644 index 000000000..941dd8097 --- /dev/null +++ b/apps/docs/app/[lang]/[[...slug]]/page.tsx @@ -0,0 +1,78 @@ +import type { Metadata } from "next" +import { notFound } from "next/navigation" +import { ImageZoom } from "fumadocs-ui/components/image-zoom" +import defaultMdxComponents from "fumadocs-ui/mdx" +import { DocsBody, DocsDescription, DocsPage, DocsTitle } from "fumadocs-ui/page" +import { APIPage } from "@/components/api-page" +import { LocaleLink } from "@/components/locale-link" +import { i18n, type Lang } from "@/lib/i18n" +import { docsAlternates } from "@/lib/site" +import { source } from "@/lib/source" + +function asLang(value: string): Lang { + if (!(i18n.languages as readonly string[]).includes(value)) notFound() + return value as Lang +} + +function pageSlug(slug?: string[]) { + return slug ?? [] +} + +export default async function DocsPageRoute({ + params, +}: { + params: Promise<{ lang: string; slug?: string[] }> +}) { + const { lang: rawLang, slug } = await params + const lang = asLang(rawLang) + const page = source.getPage(pageSlug(slug), lang) + if (!page) notFound() + + const MDX = page.data.body + return ( + + {page.data.title} + {page.data.description} + + , + }} + /> + + + ) +} + +export function generateStaticParams() { + return source.generateParams() +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ lang: string; slug?: string[] }> +}): Promise { + const { lang: rawLang, slug } = await params + const slugs = pageSlug(slug) + const page = source.getPage(slugs, asLang(rawLang)) + if (!page) return {} + return { + title: page.data.title, + description: page.data.description, + // Declare the translations as alternates of one article, and name the + // canonical URL, so search engines do not index the English and Chinese + // pages as duplicates of each other. + alternates: docsAlternates(slugs), + } +} diff --git a/apps/docs/app/[lang]/docs-providers.tsx b/apps/docs/app/[lang]/docs-providers.tsx new file mode 100644 index 000000000..f7b40a7e4 --- /dev/null +++ b/apps/docs/app/[lang]/docs-providers.tsx @@ -0,0 +1,77 @@ +"use client" + +import Image from "next/image" +import Link from "next/link" +import { useParams, usePathname, useRouter } from "next/navigation" +import { FrameworkProvider, type Framework } from "fumadocs-core/framework" +import { RootProvider } from "fumadocs-ui/provider/base" +import { useCallback, type ReactNode } from "react" +import { i18n, type Lang } from "@/lib/i18n" + +const locales = [ + { name: "English", locale: "en" }, + { name: "中文", locale: "zh" }, +] + +const translations = { + en: { + toc: "On this page", + chooseLanguage: "Choose language", + nextPage: "Next page", + previousPage: "Previous page", + }, + zh: { + toc: "本页目录", + chooseLanguage: "选择语言", + nextPage: "下一页", + previousPage: "上一页", + }, +} as const + +function isLocale(segment: string | undefined): boolean { + return Boolean(segment) && (i18n.languages as readonly string[]).includes(segment as string) +} + +function pathForLocale(pathname: string, locale: string) { + const segments = pathname.split("/").filter(Boolean) + // Drop a leading locale segment, whichever language it names — matching on a + // literal "en"/"zh" pair here would silently break when a language is added. + if (isLocale(segments[0])) segments.shift() + const rest = segments.join("/") + if (locale === i18n.defaultLanguage) return rest ? `/${rest}` : "/" + return rest ? `/${locale}/${rest}` : `/${locale}` +} + +export function DocsProviders({ lang, children }: { lang: Lang; children: ReactNode }) { + const router = useRouter() + const pathname = pathForLocale(usePathname(), lang) + // Navigation must use the public path during both SSR and hydration. + const usePublicPathname = useCallback(() => pathname, [pathname]) + + return ( + + router.push(pathForLocale(pathname, locale)), + }} + > + {children} + + + ) +} diff --git a/apps/docs/app/[lang]/layout.tsx b/apps/docs/app/[lang]/layout.tsx new file mode 100644 index 000000000..91e648c52 --- /dev/null +++ b/apps/docs/app/[lang]/layout.tsx @@ -0,0 +1,54 @@ +import "../global.css" +import type { Metadata } from "next" +import { notFound } from "next/navigation" +import type { ReactNode } from "react" +import { DocsLayout } from "fumadocs-ui/layouts/docs" +import { baseOptions } from "../layout.config" +import { source } from "@/lib/source" +import { i18n, type Lang } from "@/lib/i18n" +import { DocsProviders } from "./docs-providers" + +export const metadata: Metadata = { + title: { + template: "%s | OpenAgentCore Docs", + default: "OpenAgentCore Docs", + }, + description: "Documentation for OpenAgentCore.", +} + +export function generateStaticParams() { + return i18n.languages.map((lang) => ({ lang })) +} + +export default async function LangLayout({ + params, + children, +}: { + params: Promise<{ lang: string }> + children: ReactNode +}) { + const { lang: rawLang } = await params + if (!(i18n.languages as readonly string[]).includes(rawLang)) notFound() + const lang = rawLang as Lang + + return ( + + {/* Browser extensions may add attributes before React hydrates the body. */} + + + + {children} + + + + + ) +} diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css new file mode 100644 index 000000000..40503ed92 --- /dev/null +++ b/apps/docs/app/global.css @@ -0,0 +1,103 @@ +@import "tailwindcss"; +@import "fumadocs-ui/css/neutral.css"; +@import "fumadocs-ui/css/preset.css"; +@import "fumadocs-openapi/css/preset.css"; + +:root { + --color-fd-primary: hsl(240 5.9% 10%); +} + +/* Keep the active navigation item readable in dark mode. The neutral theme + defines a light primary color for dark surfaces; this override must be more + specific than the root token declared above. */ +.dark { + --color-fd-primary: hsl(0 0% 98%); + --color-fd-primary-foreground: hsl(240 5.9% 10%); +} + +body { + font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", + "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; +} + +/* Medium weight maps to regular in common CJK fonts. */ +.prose :where(strong):not(:where(.not-prose, .not-prose *)) { + font-weight: 700; +} + +.prose pre { + min-width: 0; + width: 100%; +} + +/* Code blocks wrap instead of scrolling sideways. */ +.prose pre, +.prose pre code { + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +/* Inline code wraps only when a token cannot fit on a line of its own. + `overflow-wrap: anywhere` also reduces the element's min-content width to + roughly one character, and the automatic table layout then treats a column + as shrinkable to that width: a five-column table collapsed the first column + to 49px and split `environment.type` across five lines. `break-word` breaks + a long token as a last resort without affecting intrinsic sizing, so a + column keeps at least its longest parameter. */ +.prose :not(pre) > code { + white-space: pre-wrap; + overflow-wrap: break-word; +} + +/* Give every table column a readable floor. The minimum content width of a + column that holds only Chinese text is about one character, while a column + holding a parameter such as `OAC_DATABASE_URL` cannot shrink below + that parameter. The automatic layout satisfies the wider columns first and + can crush the Chinese one to about four characters per line. Five columns of + 7rem still fit the narrowest content area the layout produces. */ +.prose table :is(th, td) { + min-width: 7rem; +} + +/* Make the page-tree separators read as navigation groups, not page links. */ +#nd-sidebar [data-radix-scroll-area-viewport] p { + width: 100%; + margin-top: 1.25rem; + margin-bottom: 0.5rem; + padding: 0.35rem 0.5rem; + border-bottom: 1px solid var(--color-fd-border); + color: var(--color-fd-muted-foreground); + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0; + line-height: 1.25rem; +} + +#nd-sidebar [data-radix-scroll-area-viewport] p:first-of-type { + margin-top: 0; +} + +#nd-sidebar [data-radix-scroll-area-viewport] a[href^="/"] { + min-height: 2.25rem; + font-size: 0.875rem; +} + +/* Keep the language menu compact inside the sidebar and on narrow screens. */ +[role="dialog"][data-side] { + width: min(11rem, calc(100vw - 2rem)); + min-width: 0 !important; + /* Radix anchors this bottom-left menu at x=0; keep it off the viewport edge. */ + margin-left: 0.75rem; +} + +[role="dialog"][data-side] button { + width: 100%; +} + +/* Text selection: tint the background with the theme's primary color and keep + the original text color, so contrast is inherited from body text in both + themes. Forcing --color-fd-primary-foreground here previously produced white + text on a mid-gray highlight in light mode. */ +::selection { + background: color-mix(in srgb, var(--color-fd-primary) 22%, transparent); +} diff --git a/apps/docs/app/icon.png b/apps/docs/app/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..83ad57715d733063ffd94ceb4686ab64350aea10 GIT binary patch literal 7963 zcmbVRRa6uJ)7_<8TDnUFlXE&9XgR1TekQa9gva8Z=!B>DpVF(uZWU=5WThIsjP(qWywz5I*qhk4cs2OC}Aj>`wVE3-Qbe>_v;_wlV$#y0F|wZ zAhkAyucQ;)`F*u;lK`yY`Ivsmg^kOBUR)oN8zs+gxv&>CK19L)6oL4z#w6}tlLuj=i0wp9ny z2cPbk)h#JM#%x{=+gQvHBL&t5Z^%~%tdKgA)PikvE`sp%e#9!+Hh)_me<@KCt7qtjOC7UJ{@AQh2gM}IC1?P;KH8y{^quR$Sh{k z4bl&E0Q?_fkl9ELrPZ0`BPhZ2T{(eX-Yg56gBnLR0*W|FzId~z64LMFm4frUd{GeRuzhuQH+fRIb_nVDPgA~gkI-)^8XiRpBkgawEO90ySZwhy19}#kI9u3@U8qR}; zKFLo|YKg}(GzeNI&kHm^Cyyjm@=z%}p6z=KZY|U184k}CawkXx#$p8kPV*jIzQ!P( zcrs6xg-7h2Hccuip`x70+N46Y7F{k!cXOKa!&OHv3R;Qx&KW{xp-KdxufR}}SIzHZ z&4F430l!c#|IR`ee3$kk){ZGFVx&*T_9Wt=9Z7EsPbN3{_Y=mnXF*A>XRH9q25N6M86|H9T;7H zZPzubqP|0g{w%?PJ7-e$`^oO+8{Il0qQ={K>^)jPMt_bk6Kf$~Vi~^wZIdWeDCp;X zlw$KJh+W172LKm5&oZR+!Cp2Pa{>HUEW%j)@WN(AkeJrBtI4=K=2rz9!P3`c5JHq@7y{_||*gL1p z<}GbDkBC^!<4rIbIfFO6Sl$Gb^D^Rh|55Q=4|-O(@HXehE(_h@^+BZVB^Tid6R z>m7HTpAj#Rh-~0i!OjGzsPH%1eK+ukI>oByVM7_TZ2_l3;Uen$q`5clA}9ZTIkqBe zF$O^fJT|E;5~QP1>FdI4l+eib!|er(!v+C0nr4?x|D!9VtfvwqbZc*q?jhcq+_-4ak^PM_wS=sYHY zodiY2x}<&JS9&$Tn?doXijC1Qb<@|QhI@29tI?$)BBySRPsux|QFpzr`FvDmFpwBz zs=Q8~eEmH zAN|=pe3j%y4+WE*?>PtjuTsX>YQM+IEZOkZuJ!sbLV9UbbXRzUHjPo&lv8s}0**hz z%R{0}Wa9#B2&-3{c!%UwK}_9l^}?i)Op8f=Z$Qc2zvnqwS_rENU$yY%BXeewF42vZ z$H(taWOrvZbA#EjP+mipDGi53Ro;u^<|sXV`!{C=Fw(nV)6aiw{|X6675#Wy5C={e($$u-~)u z-7-}n`FT;x%iIqMz0zz;d`bi`=@Hu)% z<>uT+_41M#+EeyfDe+)(U;CY#qHD`JgJ;bRPDj$6r~{VTDABdKUf9{vEQ0?}m-cZU zpfLUo=qvu73z`3@7xXMks9t5g4IjnD9?OdcV{4u-P#>v`#5^xtKlss?K6u6Ep^y9I zRhs{JKnoUXlX4@rfC}wBg&Jwi`|&xYrQz{MAj&pjDpz$^LLapH@^Wfu;rU$_H=!+P zoB$p1quL)-td)VyE4?n~g_^P+cIFmV%dlVW`uO!LRK*H39xq1}+yjw3on$yDX>|@x zz<*9)60q`9BW1=|yQeg0x{diKPvaYt_dUAjPFl@u6&X@fkf|RpGqS#tIrNaWCv&cn zB*P8fro1&iD9qU;@z^Wx&V?ezx;sMLZ7|PAHw$FmyJShU5slJ&K6AfB+*ZMLZ!YON zDWJse!L87~e7iQmA(^U~Op4Xl10w5G%%^bun<_Esh4ElMCVm^EU3oBL`Dl&YiwoOm z1CSMoD82PIC!c`Jd^eFuFa6VInJ8UkT7x6TS$<@M$Q~8h7fEC0oPi-ZDiQHXq?O3R zhiVnnout1HfA-gS7`4dJX(9E!QR%#*StwPt#^@#OJVi8FGaqXS`|!L11!65jii@m( z%GY#kC^WN0E=|51xXQdsDJl_OXZCeHk1Tfja*f3Ed^8(t^J()xn6I4sucr+b&OU)= zJnR+O0`c5VPX(UYM|w|(2W~9~$O$BQH-N(Si0aNGS@2gP;9Bl5I6D#?*pH*0_P?k1-zG3pB`Kd<7rFk7zW_|}@ zL?&XdbT5-w7f|28fQ1htN%dyD?V^=~ik*HFtxGr)!7Hs86>{xi)ae}3d)QcFxJM;> zOib06bBV$On~UY!*Ar6riHMB`;Q%0H0o9sKLQ)l>eTm~*dvWek{p9XpM)B}$G23?A z;O!@35R3G9B{y3FPAqyB66yIMZYN(=yQK`_MeZa*^R>=pxt*ujS6f;<(jT@H(|_*- zBRP<*MN*olGJQ3PNm0xUAQM`0r{FooPE9JQ1UokyBa7tQPyOXx=KXl}!XkA1NAI?6 zKQVQsIArj*SFqhx1jLe+)rJpvvn zRFZV1wT}aU6_qv}SohCO);F6WTuq@_a-GbFot#_ks+Ak_1Z^aCmkgqRB%I)aKEvKZ zx7L+Px7}BBrq>TfRvS8uNT^fcWe24?_|QS&i)s9!W;jtQ`2;~$f@9ki08_GRQ!0Cm z*8Ii#aU%VnQX<(;I|JlfVOEJuVXdaj1MQG3Lo?YZF5W{YFW|wmc04`)7wf$3v)&jF zdXb3ro{PVqw$Y%mtQncF_b`3>IsYCCp)GRcT>#7 zdo;^#H+?E$5>X%_2W|KaTW#PU3vUsJ>6s`@KBSk*mO0tN%rLW>$|oFKE4edlTN6-; zMQ=+p7ds{)i0Z04@g#I=2{U;%Yg!k}*N@LF8kz|zF=)9ox^~f8 zZBw_w_0~g#n6NajhyTbb0=7|jT8aH{JJx484yAO^2K5_UGj7l6T%ME2ISksJF zX0-om$4lr6JG}cVE*r+6gFVYr#ur@Y?gzLE_;_8~c12ZM6$Zx$jS?2XV z%;}@dv-p(4VPKsbJyMkRHESWOovByBY7Zwb`qhFOX!Y}1o7BrKzgM8yYP9xkN~V+{ zj$FE~1-81!$u#1Xf!gFG0&$51lD<8MC( z+nVB_%sbJ3e8ECOUW*d)nWuMJE^nG_h@_}5dTFs&1tKxZq{YMiSL)Ze3)SfnoW|0r zMeGkSXaGQwq7^tE@p=i}gk#Hz5qorrxBqbnrU9dKZA(qtwFd}n( zNEQI4J+q@kVI6BvOeF=xq%YQ9^%Nx6X*^xknvfoRN6!>iDMjqV#e!3u5l(5*y2r-v z)E8NSMb03aZGM(nt{*NeNKYyvxGiAj+ZB9Qd$EOAlcY-L6`A6#d6sGQQ#G~Nj?~Br zJO&4=#85qxE3=3g+7n?=q^Jezlx(WiU8Z_3ELZY>w6*re&3vU~SC?i5h&T@Ew*r)O z6Yj+ny)vEJ3H`{CU%Tmnd42eBCy3<=OM!Y~6WOu%z=K?Lp`PpnoEZUVZ$J6<}HT z^?Z3jn}R`o%EQ-@ks0?ygn;{VOCJc z?~J5r>zFuLK3i=lQ8x{T@q6W;E0Ag5co;Lt);euAeVWZ*WvQtaxl~sIXlj3wAZ4*` z3P{!4T{NB$pW?u+K{wf8Oe)GnYU5K!0%7m?>iJ4lnL`p7OlQa(ARpf#b!2L{R2i+&e>;usP=x)em+(uewxX3FDlA zrbcETm`S0LVA~gDH}prOB80RN*+4(sZ{jkM$Js~<=$)3>91G!hkn-qf^{*`3%4#}W zvZ1ikyT_Ug*f+L;_BQk#?Nh~I@IhQ}xn$0dp@z0GiEF*-f_ERG(rZ#FDvJtLu zh9D0Wat7k_#b>uSI(zkJdxi29B=g&3!FnON-1m6uJ@0QN86fRL)6H|&MUp|vf6Z*^=^WYfhS2U8nTwdzwQg8h zT;7DWjxQEmX)}ZHRr(KlV0rS_20TAZ2YrAst?y1Q zISV1)>hN^*TB1zJrP7^id%wG@c>m?Tt}uq_c<$w`E|@1Yb*Gq&<-6^fWm3d${@zM* zgBs__$>lSoByU2iap1R>$%3>s!z{xi$3ngJB5vYr_@4ev-H^p(;DKFv#A~%QPf{ml zXUuIwBUHQj<^%})#Xy%$F=6&aQSvzx-ulF2$b!)Rm^KTxza7QGEo2s?o<1vo(Sgd) zQXO>Ai+Q`M|5w7GQ|PARS3rr=j89bY_ajdO(FEWgLh4}myTuF!x2$7E_+9~Bk%)&h z4%`dDi$maOZAq(OZKhm^F~y~Dd$!3X0r7m9kf(QaJdlB#ject&I!tZ=e|4?scJFMNqOBb9 zg5F$8W=fNAwrm@+!Wdg-CmY7aR=b;F&pD%#=A_}m@NI5hpGoLvWJ*&Y+|FR z&<$RY8=9;4obdJ*Bz3FXXE2HU_e;a<{KUd-$=_w~*}sqNmM66+2w2%Y^tsO?B?_y= z{vgA`R+S-(r{3V0UzrPDNf^`?bg2inj*93^6so*8+6FSvD=iim_0eZhZqvwEauR$i zG9?Kjx3$s&2>tYE!C6{==eb!iH{6+*&-jdFE}m_tH+! zd-L|!vrboYl;W_o3+79x{;TStVn}?VBE~SCM6TSniaLFvVEtp@n;G^Q<_lqM{9yr? ziaq(lOE@n`UDvKi#wF9fqEo8zGK5A*kne>Rk$uxMbuYbA*f0kZCwoeNpI!Z z*LiGDPL-W~-#`9o@g;5MG&(nn0KVu}ZtZ`r=k~(S_aRhVBVmCPW@%B901KtV*5mZZ zU#2I1`AuK=+sAv~T(fC=Q4%R^*?s>)cJ3Fqk@7Ft%Cw#zKXrMI+vir;H|D&3{SIC> zp2WC(C=Iq%J@@y)ueC8ln3Faa4*lGz6u1*_mm2b@qO%B{J7+jQ$XP@L6Vh)d7o8=b zdQntRRV~Gzmkr-y8ufTPR$^94OvZkt+^)Dzpe}N?et0a#@_9Lq+fBc1W2mv)rHh-B zDRefQ=DNzBcj^)_9t}c)%E{mOYu35tp<7DkXk=p_@E+DAQ$;kR(+TrsOkn_s?jy0}GT*iY1R+M%=LztLOBFw!e`5n(L|YYzWwc1(9Kw5BX={8`616 z!H{w?c*-wlD6)93o*d=~y;cS!_HUq;=8!)Uzh(SfSXyp#$>bm_Op}0*yt;lEQ^b0M z`*P{M_2f@3qu+AMM-?%R8HT{Q7}bU@=hJf{?73Fc#0(+<6xTl;?Dy)SnU9k@jv&@7 zyK$@|o3yd90jh*Ys%22x+6SSIM49zwsfsuZYqB;8ULUOIJ>REy75pdYnyCJkE^3M<-4n=+&uQsGP&cS zNjKU^%7}apvUgMm5){d%wn7y23ws0feBt(gsj#G4jN>4XYf8App00+(5t<%<4Os+& zyzN=ZoPcpPIC#ERm58H zN|bhqg*8=>_O$XRR|uve<&CE5o9zbE(u3(&>d&odeqg1B$oV+{CaqB<5(Ej_jpC5N zRK0X-qG=W{`hBK>_4$t`R>c)Ij5T=r)_5hC^FroGv&L@~sA(w&tKLjc(6(XwzyqV2 zBh=*^OF|+qh$oO~?@V9TtFoq>003*9M!a#N=)~RcxjO-S+1GDNxlsG z+#tRl?d&ZP{d4(&hV$W$4CKHygUzaZiZiQhNcSE2ny!yIdb6Hg-bE-WjmJjZaKoxh zJEE&mp^fEMvk*67$k!Nq=cM@B6apF9q#NnsNRwpkuJG{G0J&XZ1qe8exwgG)cYo9r^N(pCj!#tKwFZ0W%r&Tlf=rm!;)sh zo{jiuSmR`lwbs|DNvXBwmSpPC-4n7iJ9Y&ntC4d?b48LTES~%SV?g zM+FB7>2XAzv|iuYq)_>)eGIK`o)8Xm{-GQ7cIHluxRXklhCDM_tmpc>DodS}%I+&eH>{VT5u-JN{lc2U?%^1d97N9aFDR+oe z6-0#TuV+N9r#;2M~8Tq#7U%9R7EQ_#^4y bCp4xh_}M^@)f@7ErZ+$x{2ElLWEJrr2_Ik8 literal 0 HcmV?d00001 diff --git a/apps/docs/app/layout.config.tsx b/apps/docs/app/layout.config.tsx new file mode 100644 index 000000000..68f3bf2c9 --- /dev/null +++ b/apps/docs/app/layout.config.tsx @@ -0,0 +1,11 @@ +import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared" + +export const baseOptions: BaseLayoutProps = { + i18n: true, + nav: { + title: "OpenAgentCore Docs", + }, + searchToggle: { + enabled: false, + }, +} diff --git a/apps/docs/app/not-found.tsx b/apps/docs/app/not-found.tsx new file mode 100644 index 000000000..fa0686507 --- /dev/null +++ b/apps/docs/app/not-found.tsx @@ -0,0 +1,69 @@ +"use client" + +import Link from "next/link" +import { usePathname } from "next/navigation" +import { i18n } from "@/lib/i18n" + +const COPY = { + en: { + title: "Page not found", + body: "The page you are looking for does not exist, or it moved when the manual was reorganised.", + back: "Back to the manual", + }, + zh: { + title: "页面不存在", + body: "你访问的页面不存在,或已随手册改版移动到别处。", + back: "返回手册首页", + }, +} as const + +/** + * 404 for the whole site. Two things about its placement are deliberate. + * + * It lives at the app root, not at `app/[lang]/`. A `not-found` boundary inside + * the segment that also holds the root layout is never compiled as a boundary, + * so the version that used to sit there was dead code: every 404 got Next's + * built-in notice instead. + * + * It is a client component that reads the language from the URL. Taking the + * language from the request on the server — `headers()`, or any other + * request-scoped API — opts *every sibling route* out of static generation, + * because the boundary belongs to the `[lang]/[[...slug]]` render tree. That is + * not a small regression: it made all 102 docs pages re-render per request, and + * the generated API reference pages carry whole OpenAPI surfaces, so a single + * navigation took two to three seconds instead of thirty milliseconds. + * + * The cost of the trade is that this notice arrives with hydration rather than + * in the first response, and outside the docs chrome: without a root layout + * Next renders a root `not-found` in its own shell. A 404 is `noindex` and a + * dead end, so paying there to keep every real page prerendered is the right + * way round. + * + * If this moves back to the server, `pnpm build` must still write prerendered + * `.html` files under `.next/server/app/en/` — the build log reporting + * `● (SSG)` is not sufficient, it says that either way. + */ +export default function NotFound() { + const pathname = usePathname() + const first = pathname.split("/").filter(Boolean)[0] + const locale = + first && (i18n.languages as readonly string[]).includes(first) + ? first + : i18n.defaultLanguage + + const text = COPY[locale as keyof typeof COPY] ?? COPY.en + const home = locale === i18n.defaultLanguage ? "/" : `/${locale}` + + return ( +
+

{text.title}

+

{text.body}

+ + {text.back} + +
+ ) +} diff --git a/apps/docs/app/sitemap.ts b/apps/docs/app/sitemap.ts new file mode 100644 index 000000000..0600f3c8f --- /dev/null +++ b/apps/docs/app/sitemap.ts @@ -0,0 +1,48 @@ +import type { MetadataRoute } from "next" +import { i18n } from "@/lib/i18n" +import { absoluteDocsUrl } from "@/lib/site" +import { source } from "@/lib/source" + +/** + * Sitemap for the manual, built from Fumadocs' page list at build time. + * + * One entry per logical page: every language variant of a page is declared as + * an hreflang alternate of the same entry, so a search engine reads the English + * and Chinese pages as one article in two languages rather than as duplicates. + */ +export default function sitemap(): MetadataRoute.Sitemap { + // Collapse the language variants into one group per slug. + const bySlug = new Map>() + + for (const { language, pages } of source.getLanguages()) { + for (const page of pages) { + const slugKey = page.slugs.join("/") + const languages = bySlug.get(slugKey) ?? new Map() + languages.set(language, page.url) + bySlug.set(slugKey, languages) + } + } + + const entries: MetadataRoute.Sitemap = [] + + for (const languages of bySlug.values()) { + // Canonical is the default language when it exists, otherwise the first + // translation available. + const canonicalRelative = + languages.get(i18n.defaultLanguage) ?? languages.values().next().value + if (!canonicalRelative) continue + + const alternates: Record = {} + for (const [lang, relative] of languages) { + alternates[lang] = absoluteDocsUrl(relative) + } + alternates["x-default"] = absoluteDocsUrl(canonicalRelative) + + entries.push({ + url: absoluteDocsUrl(canonicalRelative), + alternates: { languages: alternates }, + }) + } + + return entries +} \ No newline at end of file diff --git a/apps/docs/components/api-page.tsx b/apps/docs/components/api-page.tsx new file mode 100644 index 000000000..3976f2a65 --- /dev/null +++ b/apps/docs/components/api-page.tsx @@ -0,0 +1,13 @@ +import { APIPage as OpenAPIPage, type ApiPageProps } from "fumadocs-openapi/ui" +import { loadSurface } from "@/lib/openapi" + +// Registered as `APIPage` for the MDX components map. The generated pages name +// their API surface by id; the contract itself is loaded through the server +// instance so the rendered schema and the generated prose stay in step. +export async function APIPage({ + document, + ...props +}: Omit & { document: string }) { + // References never collect credentials or dispatch requests from the browser. + return +} diff --git a/apps/docs/components/locale-link.tsx b/apps/docs/components/locale-link.tsx new file mode 100644 index 000000000..136081f81 --- /dev/null +++ b/apps/docs/components/locale-link.tsx @@ -0,0 +1,24 @@ +import type { AnchorHTMLAttributes, ReactNode } from "react" +import { i18n } from "@/lib/i18n" +import { prefixLocale } from "@/lib/locale-link" + +type LocaleLinkProps = AnchorHTMLAttributes & { + lang: string + children?: ReactNode +} + +/** + * MDX anchor override for the docs routes. + * + * The active language is passed in by the route, which knows it from the URL, + * so no client-side context is required and this stays a server component. It + * renders the same plain anchor Fumadocs uses by default, after adding the + * locale prefix — see `prefixLocale`. + */ +export function LocaleLink({ lang, href, children, ...rest }: LocaleLinkProps) { + return ( + + {children} + + ) +} diff --git a/apps/docs/content/docs/admin-api.mdx b/apps/docs/content/docs/admin-api.mdx new file mode 100644 index 000000000..1e9b830cc --- /dev/null +++ b/apps/docs/content/docs/admin-api.mdx @@ -0,0 +1,160 @@ +--- +title: "Core administration API" +description: "Management operations and their server-side credential boundary." +--- + +Core Web is an administrator console over `/core/v1`, authorized by the Core key: +resource inspection, Project and key management, credential issuance, audit, usage +and sandbox operations. It never calls `/v1` or `/api/v1`, and has no Agent +execution, copy or arbitrary asset editing operation. + +Core request failures use the [Core administration error envelope](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/core-errors.md), +including optional typed safe details. Console sign-in endpoints retain their +separate error shape described below. + +## Browser to console + +The browser uses the console's own origin and signs in with the +[Core key](/troubleshooting#core-key): + +| Method and route | Request | Result | +| --- | --- | --- | +| `GET /console/auth` | No body | `200 {"mode":"login"}` or `200 {"mode":"authenticated"}` | +| `POST /console/auth/login` | `Content-Type: application/json`, body `{"core_key":"…"}`; other members are rejected | `200 {"mode":"authenticated"}` and an HttpOnly, SameSite=Strict session cookie (Secure over HTTPS) | +| `POST /console/auth/logout` | No credential payload | `200 {"mode":"login"}`; clears the cookie and the server-side session | +| `GET /console/config` | Signed-in session | `node_installer`, `node_installer_sha256`, `self_hosted_installer`, `self_hosted_installer_sha256`, and `node_artifacts`: the providers (`docker`, `microsandbox`) whose node assets this console holds | + +Sign-in errors use the console's `{"error": "…"}` envelope: 400 for a malformed +body, 401 for a wrong key, 415 for a non-JSON body, 429 with `Retry-After` when +failed attempts are limited or sign-in is busy, and 503 when the console cannot +start a session. The console compares the submitted key with its configured Core +key in constant time and never logs or returns it. Only failed attempts count +toward the limit; the correct key signs in even while failures are limited. The +console refuses to start with a Core key shorter than 32 characters. Sessions live only in the console's memory; a console restart +or Core key rotation requires signing in again. There are no console accounts, +usernames, passwords, account setup or Basic authentication. + +Use same-origin browser requests and cookies. Mutations require the same-origin +request checks; never put the Core key in JavaScript or browser storage. + +## Console to Core + +After sign-in and the same-origin checks, the console server forwards every +`/core/v1/*` request with its private Core key as the Bearer credential; Core +alone decides whether the route exists. It removes browser Authorization and +forwarding-sensitive headers, and sets `X-Core-Console-Actor: console`. Core +records the header as the audit `actor_label`. The label is caller-asserted and +display-only: direct Core key scripts normally send none, which records an empty +label, but could set any value. Never use it for authorization or as proof of +origin. + +Web calls Core only through the typed Core clients in `packages/agents-client`: +[AdminClient](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/packages/agents-client/src/admin-client.ts) (`/core/v1`), +`SandboxAdminClient` (`/core/v1/sandbox`) and `CoreMetricsClient` +(`/core/v1/metrics`). See [the complete administrator reference](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/admin-api.md) +for methods, fields, filters, pagination and response shapes. +Routes below are relative to `/core/v1`: + +| Workflow | Routes | +| --- | --- | +| Projects | `GET/POST /projects`, `POST /projects/{id}`, `POST /projects/{id}/archive` | +| Project keys | `GET/POST /projects/{id}/keys`, `DELETE /projects/{id}/keys/{key_id}` | +| Resource lists/details/deletion | `/projects/{id}/agents`, `/sessions`, `/environment-templates`, `/skills`, `/files`, `/vaults`, including the documented nested reads | +| Asset ownership | `GET /projects/{id}/resource-owners` with batched resource IDs | +| Key operation history | `GET /projects/{id}/write-operations` with key/resource/time filters | +| Executor credentials | `GET/POST /projects/{id}/environments/{environment_id}/executor-credentials`, `DELETE …/executor-credentials/{key_id}` ([contract](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/environment-executor-credentials.md)) | +| Deployment model providers | `GET /harnesses`, `GET/PUT/DELETE /harnesses/{harness}/model-provider`; the key is write-only ([contract](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/model-execution.md#deployment-defaults)) | +| Usage and health | `GET /summary`, `/sandbox/runtime-observations`, `/metrics` | +| Administrator audit | `GET /audit-log`; deployment-wide entries have `project_id: null` | + +A Project UUID in a management path selects the target; it is not a credential. +API-key plaintext is returned only by successful issuance, so display it once and +never cache it. Reconcile uncertain issuance before explicitly issuing another key. +Deleted/revoked resources retain their audit records. Historical unknown ownership +stays null. Session usage grouped by key belongs to the Session's creation key; +it is operational attribution, not per-key billing. + +## Sandbox administration + +The [deployment configuration contract](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/sandbox-deployment.md), +[nodes and sandbox backends reference](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/HOSTED-SANDBOX-MANAGER.md) +and [generated OpenAPI](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/core.openapi.yaml) +define deployment and node operations: + +- `GET/POST/PUT /core/v1/sandbox/deployment` and + `PATCH /core/v1/sandbox/deployment/maintenance`. +- `GET /core/v1/sandbox/nodes`, `PATCH/DELETE /core/v1/sandbox/nodes/{node_id}`, + and `GET /core/v1/sandbox/nodes/{node_id}/allocations`. +- `POST /core/v1/sandbox/enrollment-tokens` for a one-time node installation command. + Its non-secret `enrollment_id` reappears on the node that command registers. + +PostgreSQL owns one provider, per-sandbox resource specification and immutable +Runtime selection. POST initializes it; PUT replaces the complete selection using +`expected_generation`. Requests carry `resources` and, for Docker/microsandbox, +`runtime`; safe responses return `specification` and `specification_digest`. +Response `resources.allocations` and `resources.pending` are cleanup counts, not +CPU, memory or disk settings. E2B accepts a write-only key and exact template build +instead of a node Runtime release, and provisions without a node installation. E2B +may omit `resources` to adopt the validated build's CPU and memory; responses show +the build as read at selection time in `e2b.template_build`. Microsandbox responses +return its idle `suspension` policy; other providers return null. + +Provider, resource and Runtime changes all require global maintenance and verified +cleanup of retained/pending resources. Core validates the candidate before commit; +a rejection preserves the previous configuration. A changed commit advances the +generation and retires old nodes and enrollment tokens atomically. Explicitly resume +after success. Neither switching nor editing configuration deletes resources or +migrates existing Sessions. During maintenance, administrators can explicitly +[archive each retained hosted Session](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/admin-api.md#administrative-session-archive) +through the Core administrator API at the current generation, then read its +resource disposition and recheck deployment counts. Archive preserves history +and persisted Files/Artifacts; unpersisted workspace contents are lost and the +original Session cannot resume. This API does not add a console archive control. +Public Environment Templates, the `/v1` contract and +caller-owned `self_hosted` provisioning remain unchanged. + +`GET /api/v1/sandbox-node/configuration` uses an enrollment Bearer token, or a +retained node Bearer credential with `X-OAC-Node-ID`. This read does not consume +enrollment. Retained matching nodes can read their configuration during maintenance. +Installers must verify the returned generation, specification digest and Runtime +before registration; local files cannot override the saved limits. A mismatch +returns `sandbox_specification_mismatch` without replacing node state. + +Node configuration, enrollment, identity and connection routes live under +`/api/v1/sandbox-node`, beside the daemon's `/api/v1/agent-daemon`. The reverse +proxy sends `/api/v1` directly to Core; the console returns 404 for it and never +forwards machine traffic. These routes use their own credentials, do not inherit a +browser login and gain no management authority. E2B credentials are absent +from node configuration and safe deployment views. + +## Frontend handoff and errors + +Frontend screen implementation is owned by the separate frontend task. The +backend provides the `/core/v1` contract, the typed Core clients and the console +proxy; backend tests do not qualify the screens. + +The console returns 404 for `/v1`, `/api/v1` and old `/console/api-keys` routes, +even with an explicit application or machine Bearer credential. Invalid +Origin/Host requests are rejected; unavailable Core or rejected upstream redirects +return 502. Console authentication uses its own error envelope. Management resource +errors and deletion constraints are documented in the administrator reference and +generated schema; do not interpret every empty or failed read as an absent resource. + +## Core metrics + +`GET /core/v1/metrics?range=1h|6h|24h|7d` returns Core process, execution +queue/slots, PostgreSQL and background-job measurements. It requires the Core key, +rejects arbitrary query filters and never grants +Agent execution access. See the [exact measurement contract](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/core-metrics.md) +for complete buckets, null values, units and process-local retention. Frontend +implementation is maintained separately; this backend change does not modify +Agent metrics or the public Agent API. + +## Node host history + +The Core key reads a node and its host history through +`GET /core/v1/sandbox/nodes/{node_id}?range=1h|6h|24h`. See the +[node host history contract](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/node-host-history.md) +for nullable observations, freshness and aggregation. The node list is unchanged. + +[Repository source](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/docs/api/web-management.md) diff --git a/apps/docs/content/docs/admin-api.zh.mdx b/apps/docs/content/docs/admin-api.zh.mdx new file mode 100644 index 000000000..a35483936 --- /dev/null +++ b/apps/docs/content/docs/admin-api.zh.mdx @@ -0,0 +1,10 @@ +--- +title: "Core administration API · 中文导读" +description: "当前版本的中文导读;完整操作步骤见英文正文。" +--- + +管理员脚本通过 Core 的回环端口调用 /core/v1,并使用 Core key。公网入口的 /core/v1 由 Web 管理会话处理;不能把它当成应用 API 或机器连接的代理。 + +此页为中文导读,完整且与当前源码同步的步骤请阅读[英文正文](/en/admin-api)。 + +[安装](/install) · [配置](/configure) · [API 与凭据](/public-api) · [节点](/hosted-providers) · [应用示例](/quickstart) diff --git a/apps/docs/content/docs/agents-and-tools.mdx b/apps/docs/content/docs/agents-and-tools.mdx new file mode 100644 index 000000000..ed238409b --- /dev/null +++ b/apps/docs/content/docs/agents-and-tools.mdx @@ -0,0 +1,124 @@ +--- +title: "Agents, tools and harnesses" +description: "Native harness tools, shared declarations and the limits of execution support." +--- + +Assessed against Core `206474a4c10901442b1faa094281bfb5559082b5` on 2026-09-22. +The contract remains [OpenAI Python 3.13.0 at `d7c41ef`](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/upstream.json), Beta +`agents=v1`. This matrix records the bounded execution/tools milestone. It does not +establish complete protocol compatibility; the remaining limits below stay open. + +**Implemented** means current source and controlled checks support the operation. +**Qualified** additionally identifies a real public Core/PostgreSQL/daemon/native +model workflow in the evidence register below. **Unsupported** describes a current +admission/native limitation, not a restriction in the official contract. +**Unverified** means evidence does not establish the stated semantics or profile. +Qualification never extends automatically to another model, placement or combination. + +## Operation matrix + +All function rows refer to application-defined functions. Native workspace tools +and Environment Plugin MCP have separate inventories and qualification. + +| Operation | Implemented behavior and real qualification | Unsupported or unverified boundary | +| --- | --- | --- | +| Initial message input, `sessions.create` | String input and ordered user-message arrays share atomic admission. Codex/Claude text and inline image execution: M1/M2; ordinary MiniMax text: M1/P1. [Input contract](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/message-input.md), [initial parser](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/api/session_initial_input.go). | Whitespace-only text is admitted verbatim for Codex and rejected at admission for Claude SDK and MiniMax Code ([text content](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/message-input.md#text-content)). Empty parts beside text (SES-08) and local size-limit parity need upstream evidence. Images are only qualified for inline PNG/JPEG on `none` and Core-managed Docker `openai_hosted`; MiniMax, self-hosted images and remote URLs remain gaps. | +| Prepared and active messages, `sessions.events.create` | Ordered `input_text`/`input_image` arrays retain original content and distinct public user Items. HTTP 202 confirms persistence; native receipts establish application. Initial/prepared/active Docker PNG/JPEG: M2; active PNG on `none`: M1. [Shared admission](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/api/inputs.go). | Codex flattens native messages with blank-line separators. Claude can fold or queue native turns; it does not promise Codex's same-native-turn behavior. Public durability does not prove native consumption. Claude SDK and MiniMax Code reject messages without an image or non-whitespace text at admission (`unsupported_or_invalid_configuration`); Codex delivers them unchanged. | +| Structured output, `agent.text.format` | Save/inherit/freeze `{type:json_schema,schema:...}`. Claude SDK object-root, single Agent, medium verbosity, ordinary functions returning text: S1 (`none`) and S2 (Docker, including prepared/active input and Files/Artifacts). Native final text is retained unchanged; its stream follows the official message sequence with the whole text in one `output_text.delta` ([item serialization](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/history-events-usage.md#item-serialization-2026-09-23)). [Contract](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/structured-output.md), [profile](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/engine/claude.go). | An explicit non-object root type is a protocol error for every harness ([validation](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/official-semantics-alignment.md#agent-configuration-validation--september-23)). Codex/MiniMax, schemas without an object root, schema numbers changed by binary64, self-hosted, Skills/Plugins, MCP, Subagent and discovery combinations reject execution. No output repair, coercion or extra model loop. Arbitrary schema dialects are unverified. | +| Function configuration, saved/inline Agents | Required name/description/schema; `defer_loading` defaults false. Protocol errors, repeated names and explicit non-object root types reject with the official fields ([validation](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/official-semantics-alignment.md#agent-configuration-validation--september-23)). Saved references resolve into an immutable Session snapshot. Codex/Claude real calls: F1/F2/M2/S1/S2. [Parser](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/api/function_configuration.go), [saved tools](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/api/saved_tools.go). | MiniMax public functions reject. Claude requires an explicit object root. Local nonblank/512-byte name and 64-definition Session bounds are compatibility gaps. Saving configuration alone does not qualify execution. | +| Function-result admission, `events.create` | Required `turn_id`, `call_id`, `success`; optional nullable `error` and `output`. Output is string or ordered text/image content. Scoped atomic batches retain field presence, original content and retry identity in storage; public result Items and events always carry `output` and `error`, null when not submitted ([item serialization](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/history-events-usage.md#item-serialization-2026-09-23)). Same result retries are accepted; changed results and results after cancellation return 409 `conflict_error`, including after terminal state. In the caller's Session, an unknown call or a call of another Turn returns 400 `invalid_request_error` without changing the pending action; missing and foreign Sessions stay 404 ([error rows](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/official-semantics-alignment.md#session-input-conflicts-and-result-targets--september-23)). F1/F2/M2 plus [controlled SDK/raw checks](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/tests/official_function_inputs.py). [Parser](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/api/function_inputs.go), [Store](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/store/function_inputs.go). | Admission is separate from application and public Item publication. Invalid or unqualified content cannot consume a pending call. Core messages omit the call and executor IDs that official messages name; hosted defaults and publication timing remain unverified. | +| Function text results and native application | Codex waits for a matching live root dynamic-tool completion; Claude waits for a matching live root native tool result. Text/error results, retry/conflict, cancellation and cold continuation: F1/F2. [Receipt contract](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/function-result-images.md). | Transport writes alone do not confirm application. Confirmation is not provider consumption, crash recovery or exactly-once external effects. No automatic result replay. | +| Function image results | Successful ordered inline PNG/JPEG with text, large PNG and image-only JPEG: Codex/Claude `none` F1/F2; Docker M2. Public Items retain submitted bytes. Codex receipt regression: F2. | Claude rejects failed images and remote references before persistence; native resizing may change its image bytes. MiniMax functions and self-hosted image workflows are unqualified. Other Codex image/error/reference combinations cannot be inferred from successful-inline evidence. | +| Deferred function discovery | Type-only `tool_search` plus mixed eager/deferred functions: Claude SDK 0.3.269/native 2.1.269, Kimi K3, single Agent, medium, `none`, text results; text and PNG input D1. Native provider observations establish lazy schema loading. [Contract](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/tool-search.md). | A repeated `tool_search` is a protocol error. Codex/MiniMax discovery, search-only/missing-search, workspace, MCP, structured-output and Subagent combinations remain gaps. Opaque native policy changes lack a reliable pre-input deferral signal. Saved tools include `tool_search`; the pinned Session response union excludes it. Exact hosted projection is unverified. | +| Explicit disabled search/PTC | Saved and inline `web_search.mode=disabled` and `programmatic_tool_calling.enabled=false`; shared native controls on initial and cold execution. All three harnesses on `none`: P1, including native inventory/control evidence. [Contract](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/tool-policy.md), [parser](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/api/disabled_tools.go). | Unsupported explicit enablement rejects at Session admission; saved Agents keep every pinned search mode as resource data (TV-05), and Sessions from such Agents reject unless they replace the tools. A repeated `web_search` is a protocol error. Omission retains approved native behavior, which does not establish official default-on PTC parity. Enabled search and default/error parity remain gaps; unrelated native utilities are not implicitly removed. | +| Agent service-origin MCP | Implemented Codex/Claude HTTP `none` profiles, anonymous/static bearer, scoped Vault selection and native `mcp_call` Items. [Configuration and qualification limits](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/README.md#http-mcp-execution), [profiles](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/engine/profile.go). | This closure batch does not requalify MCP/provider combinations. MiniMax, self-hosted/hosted service-origin MCP, OAuth, nonempty inline headers/metadata and other transports/origins reject. Claude requires a static connected inventory; original MCP-envelope fidelity and continuing server health are unverified. | +| Environment Plugin MCP/native tools | Separate [Docker Plugin transport matrix](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/environment-templates.md#environment-origin-mcp-plugins) and [V1 deployment evidence](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/user-managed-runtime-v1.md). Native tools stay within the existing colocated Runtime. | Plugin MCP is not Agent service-origin MCP or a public function action. No new optional cross-product is qualified here. Native utility inventories need not be identical. | +| Required action: `function_call` | Session GET/list and Session SSE expose persisted `{arguments,call_id,name,turn_id,type}`. Actions remain pending until native application or cancellation/terminal settlement. [Projection](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/store/function_state.go), [confirmation](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/store/function_results.go); real handling F1/F2/M2/S1/S2/D1; explicit client disconnect/query/reconnect: F3. | Function-call history is not pending-state authority. Client reconnect does not replay events or redo external application effects. | +| Required action: `environment_connection` | Offline waiting input exposes `{environment_id,type}` before Turn creation. Connect the exact Session Environment through our scoped daemon enrollment. Three-harness Docker/E2B execution: E1; explicit initial pending-input/query/connection/native completion: E2; expiry: [controlled initial-input test](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/tests/official_self_hosted_initial.py). | Idle offline Sessions without pending input request nothing. Registration alone is not connectivity or native readiness. Stock `exec-server`/Noise transport is outside the approved V1 route. Exact upstream registration/action-removal timing remains unverified. | +| Stream disconnect and execution loss | SSE is live-only. Reconnect, buffer events, retrieve Session/Turn/Items and deduplicate Item IDs. Worker recovery fails previously claimed work without replay; queued work can remain. [Recovery contract](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/README.md#public-execution-admission), [connection reconciliation](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/store/environment_connection_recovery.go). | Client disconnect and daemon/Core loss are different cases. F3 qualifies client-stream loss and continued pending function handling; F2 qualifies native process loss with an unapplied saved result. Neither promises replay or recovery of unknown external effects. | + +## Required-action recovery contract + +The fixed [Session union](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/types/beta/agent_session.py) +defines both variants. The official [Functions](https://developers.openai.com/api/docs/guides/agents-api/tools/functions) +and [Manage sessions](https://developers.openai.com/api/docs/guides/agents-api/sessions/manage) +guides, checked on 2026-09-22, direct clients to retrieve `required_actions` after +restart or stream loss. A historical `function_call` Item alone cannot establish +that a result is still pending. The fixed SDK governs field/type compatibility; +current prose does not silently change that pin. + +For a pending function, use the returned Session, Turn and call identity. If the +application already performed the function, keep and submit that saved result; +do not run an external effect again merely because an action remains visible. +Core's acknowledgement boundary is native application. For an environment action, +connect its exact Environment using the existing enrollment authorization. +[Connection observations](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/store/environment_connections.go) +fence stale generations; transport connection can clear pre-Turn activity before +native preparation. Neither registration nor an action disappearing proves that +a model ran successfully. Query the matching Turn and Items for its outcome. + +Two timing questions remain open. Repeated `requires_action` notification order +and acknowledgement timing are local implementation choices, not proven upstream +semantics. Also, an admitted result that is cancelled before native observation +can remain internally saved without a public output Item or `item.added`. +[Item publication](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/internal/store/item_projection.go) and +[the existing coverage record](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/README.md#public-function-configuration) preserve +this gap. Successful retry cannot manufacture the missing observation or establish +that the model consumed the result. + +## Evidence register + +Real records below use fixed SDK 3.13.0 and raw HTTP through Core, dedicated +PostgreSQL, daemon and native harness. Evidence is private under `~/.parsar/remediation/` on `zju_a100_2` and the +development host. Paths containing `validated/` or `server-evidence/` identify +downloaded local mirrors; summary files may also live only on the development +host. Locators identify retained evidence without publishing credentials or +private launch configuration. Revisions identify the accepted change, +not a claim that every historical workflow was rerun at the current baseline. + +| ID | Exact evidence and accepted scope | +| --- | --- | +| M1 | `20260922/message-image-input/public-codex-reviewed.log` (59.41s), `public-claude-reviewed.log` (79.52s), `public-mcode-network-fixed.log` (48.95s); [PR #12](https://github.com/MiniMax-AI/parsar-core/pull/12), merge `37a2923`. Codex/Claude Kimi K3 PNG initial/active inputs, receipts, retries, cancellation, cold continuation and isolation; MiniMax M2.7 ordinary text regression. | +| M2 | `20260922/workspace-images/validation-summary.json`; Codex `public-run-_lr5uiy_`, Claude `public-run-sb65p9e9`; candidate `2c295116d66ed9aafc808ec49a8cb6dcb5c674c2`, merge `1adb2489bc3415039440224e9a7905c53e68a557` ([#17](https://github.com/MiniMax-AI/parsar-core/pull/17)). Kimi K3, Codex 0.153.4, Claude SDK 0.3.269/native 2.1.269; Docker seven-Turn image/workspace workflow. Codex's receipt conclusion is superseded by F2. | +| F1 | `20260922/function-result-images/validation-summary.json`, `validated/function-image-public-2567456666/public.json`; candidate `d3cdb225bc7628b968b76c7e1b400101894ce8cd`, merge `fd23f169e1ede1b2f1c39a1d9dcce71886e3c006` ([#16](https://github.com/MiniMax-AI/parsar-core/pull/16)). Claude SDK 0.3.269/native 2.1.269 with Kimi K3 on `none`; success PNG/JPEG, failed text, retries, cancellation, history continuation. Earlier Codex transport-only evidence does not qualify current receipts. | +| F2 | `20260922/function-result-receipts/validation-summary.json`, `candidate-public-owner-sdk.log`; hosted `public-run-4fn70foy` (104.13s), `none` `function-image-public-945075091` (83.918s). Candidate `6dceb98e1c56469fcc70cd82025ce77fc870ed3d`, merge `206474a4c10901442b1faa094281bfb5559082b5` ([#20](https://github.com/MiniMax-AI/parsar-core/pull/20)); real Kimi, Codex 0.153.4. Qualifies native receipts, not completed provider consumption. | +| F3 | `20260922/execution-tools-closure/pending-actions/validation-summary.json`; Codex `codex/public-run-8gexf5a8` (65.58s), Claude `claude_sdk/public-run-5f2tr7e_` (74.33s), production source `206474a`. The same [public verifier](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/services/agents-api/tests/official_pending_actions_native.py) checks disconnected-client pending queries, success/error/cancel, original results, target isolation, retry/conflict and no implicit call replay with real Kimi on Docker. No Core restart or native-loss injection is claimed by these runs. | +| S1 | `20260922/structured-output/acceptance-summary.json`, `structured-public-1905281777/public.json`; real candidate `7f533d46c472f3cdf7c16ce9471c225a2ba7eded`, merge `8716e699dbc34904499c94b6b0b03857bbebfaad` ([#11](https://github.com/MiniMax-AI/parsar-core/pull/11)). Claude/Kimi `none` schema/function execution and cold continuation. Schema-specific active steering was not separately qualified here. | +| S2 | `20260922/hosted-structured-output/validation-summary.json`, `validated/public-inline.log`, `public-run-qvq3csf1` (186.09s); candidate `f6d2c3f2a1dc2ff5f177213bd2b20d78496011d0`, merge `aa85d8ecc60e0a8b7f2494b73caa81216ee197e2` ([#18](https://github.com/MiniMax-AI/parsar-core/pull/18)). Claude SDK 0.3.269/native 2.1.269, real Kimi; Docker saved/inline schema, prepared/active input, native files and four completed structured answers. | +| D1 | `20260922/deferred-tools/tool-search-public-2039155387/public.json`, `public-image-isolated-tests.log` (86.71s); merge `178507ae7a63d4068e1e82bef9cd256ba398ae00` ([#13](https://github.com/MiniMax-AI/parsar-core/pull/13)). Claude SDK 0.3.269/native 2.1.269/Kimi K3 `none`; text results with initial/active PNGs, cancellation and cold continuation. `claude-native-1790052750` separately records provider schema inventories and native feasibility. | +| P1 | `20260922/tool-policy/acceptance.json`, `server-evidence/tool-policy-{codex-2495445746,claude_sdk-2539346823,mcode-3747575401}/public.json`; candidate `eeb432c7495265ae3a9309e32f5b4323611c3245`, merge `4b8754a6eda6696d9b18eb8d583953ac16c6800f` ([#14](https://github.com/MiniMax-AI/parsar-core/pull/14)). Codex/Claude Kimi K3, MiniMax M2.7; four `none` Sessions per harness, native disable controls and cold continuation. | +| E1 | `20260921/self-hosted-onboarding/{source-candidate.json,source-verified.json,live,live-e2b}`; candidate `8f0cd2530d7b58cb7fb3ea124a1fc43dacecca36`. [Exact Docker/E2B run paths and limits](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/user-managed-runtime-v1.md#evidence-and-verification-boundaries): Codex Kimi K3 Responses, Claude Kimi K3 Anthropic-compatible API, MiniMax M2.7, immutable Linux amd64 Runtime builds. Native execution, restart/history, cancellation, Files/Artifacts and isolation; shared credential lifecycle evidence is not a separate live rotation run for every E2B profile. | +| E2 | `20260922/execution-tools-closure/environment-actions/result.json` contains Codex/Claude Kimi passes (13 checks each) and the retained MiniMax direct-network failure; `mcode-relay-retry/result.json` separately passes MiniMax M2.7 (13 checks) after correcting provider routing. Source `206474a`; same shared SDK/raw workflow, isolated Core/PostgreSQL and user-managed Docker. Covers initial creation SSE, client disconnect, exact pending Environment/no Turn or Items, scoped enrollment, one actual completed model Turn and creation retries preserving history. This is connection-action qualification, not another full lifecycle or E2B regression. | + +Controlled tests establish parser, projection, atomicity and race behavior; native +feasibility probes establish only feasibility. Neither replaces the real records. +Failed setup, provider and assertion runs remain failures with their original +scope. The unchanged gate requirements and independent review apply to this batch. + +## Milestone closure and remaining limits + +1. **Principal function recovery:** F3 directly verifies live client-stream loss + while a function is pending on Codex and Claude, authoritative queries, exact + targeting, rejection without mutation, original results, retry/conflict, + application, cancellation and isolation. F2 separately establishes truthful + native-loss settlement without replay. No active execution recovery is promised. +2. **Principal environment recovery:** E2 explicitly verifies pending + `environment_connection` recovery and single initial execution on all three + harnesses. E1 retains broader deployment/history/isolation qualification. + Registration, connection and preparation remain distinct; no exact upstream + timing or arbitrary-provider qualification is inferred. +3. **Recorded nonblocking timing:** an accepted result cancelled before native + application may lack a public result Item. The submitted data remains durable; + F2 confirms that native loss does not turn unknown delivery into Applied. The + fixed sources do not establish the publication point for this interleaving. + Keep it queued rather than inventing a public state or rewriting safe receipt + ownership. This limited timing question is not accepted protocol parity and + does not invalidate F3's principal pending-result workflow. + +Approved native capability differences (including default PTC), unsupported optional +combinations, arbitrary provider parity and low-frequency error/default edge cases +remain separate limitations. They do not authorize silently relabeling an unproved +principal workflow as deferred. OAuth, full Usage, release publication, new tool +kinds and new execution loops are outside this closure batch. + +[Repository source](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/execution-tools.md) diff --git a/apps/docs/content/docs/agents-and-tools.zh.mdx b/apps/docs/content/docs/agents-and-tools.zh.mdx new file mode 100644 index 000000000..5ed1f6f95 --- /dev/null +++ b/apps/docs/content/docs/agents-and-tools.zh.mdx @@ -0,0 +1,10 @@ +--- +title: "Agents, tools and harnesses · 中文导读" +description: "当前版本的中文导读;完整操作步骤见英文正文。" +--- + +保存 Agent 只保存配置,不启动执行。工具声明由所选原生 harness 执行;Codex、Claude Code 与 MiniMax Code 的能力和限制不同。x_agents_core 保留原有扩展字段名称。 + +此页为中文导读,完整且与当前源码同步的步骤请阅读[英文正文](/en/agents-and-tools)。 + +[安装](/install) · [配置](/configure) · [API 与凭据](/public-api) · [节点](/hosted-providers) · [应用示例](/quickstart) diff --git a/apps/docs/content/docs/api-reference/agents.mdx b/apps/docs/content/docs/api-reference/agents.mdx new file mode 100644 index 000000000..25bd137d4 --- /dev/null +++ b/apps/docs/content/docs/api-reference/agents.mdx @@ -0,0 +1,75 @@ +--- +title: Agents +description: >- + Agents. Application API: Project API key. Public schema constrained by the + pinned OpenAI Agents API baseline; documented x_agents_core fields remain Core + extensions. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + 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. + - content: >- + 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. + - content: >- + Reads the saved resource owned by the authenticated tenant, + independently of execution Sessions. + - content: >- + 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. + - content: >- + 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. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/artifacts.mdx b/apps/docs/content/docs/api-reference/artifacts.mdx new file mode 100644 index 000000000..012154e5f --- /dev/null +++ b/apps/docs/content/docs/api-reference/artifacts.mdx @@ -0,0 +1,33 @@ +--- +title: Artifacts +description: >- + Artifacts. Application API: Project API key. Public schema constrained by the + pinned OpenAI Agents API baseline; documented x_agents_core fields remain Core + extensions. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + 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. + - content: >- + Deletes the published copy without modifying its original workspace + file. Already admitted content reads may finish; later reads reject. + - content: >- + Streams stored bytes after tenant and Session authorization, including + after Environment expiration. Exact upstream headers and Range + behavior remain unverified. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/administrator-projects.mdx b/apps/docs/content/docs/api-reference/core/administrator-projects.mdx new file mode 100644 index 000000000..81b0dd3ca --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/administrator-projects.mdx @@ -0,0 +1,17 @@ +--- +title: Administrator Projects +description: >- + Administrator Projects. Core administration API: Core key held by Web’s server + or an operator script. Generated local management contract. This is not part + of the public OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/agents.mdx b/apps/docs/content/docs/api-reference/core/agents.mdx new file mode 100644 index 000000000..3efbe8afb --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/agents.mdx @@ -0,0 +1,29 @@ +--- +title: Agents +description: >- + Agents. Core administration API: Core key held by Web’s server or an operator + script. Generated local management contract. This is not part of the public + OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/artifacts.mdx b/apps/docs/content/docs/api-reference/core/artifacts.mdx new file mode 100644 index 000000000..94f4020b7 --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/artifacts.mdx @@ -0,0 +1,33 @@ +--- +title: Artifacts +description: >- + Artifacts. Core administration API: Core key held by Web’s server or an + operator script. Generated local management contract. This is not part of the + public OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/core-administration.mdx b/apps/docs/content/docs/api-reference/core/core-administration.mdx new file mode 100644 index 000000000..af6d73f49 --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/core-administration.mdx @@ -0,0 +1,56 @@ +--- +title: Core Administration +description: >- + Core Administration. Core administration API: Core key held by Web’s server or + an operator script. Generated local management contract. This is not part of + the public OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Newest-first cursor pagination of safe metadata. Actor + labels are unverified console labels, not authorization identities. + Request bodies and secrets are never recorded. + - content: >- + Core key only; available before any sandbox deployment exists. Reports + the public URL that applications, nodes, sandboxes and self-hosted + executors use, the API base URL, Core's source commit and installation + ID, the installer's settings snapshot with where to change it, and + what is bound to the current public URL. Sensitive settings report + only whether they are configured. + - content: >- + Core key only. Complete UTC buckets; unknown measurements are null. + Samples are process-local and are not backfilled after a restart. + - content: >- + Core key only. Reports actual resource disposition, including expiry + and failure cleanup. This is not archive provenance and does not + assert active Turn settlement. Read this after an uncertain archive + response; never infer released from a missing sandbox alone. + - content: >- + Core key only. Requires maintenance and the current deployment + generation. Permanently closes execution, requests cancellation and + releases sandbox/snapshots through existing cleanup. Session history + and persisted files/artifacts remain; unpersisted workspace contents + are lost. A cleanup_pending response is not proof of resource release. + Does not affect caller-managed Runtime. + - content: >- + Core key only. Each observation is labelled with its owning Project + ID. Uses the existing read-only Runtime sampler, with bounded + concurrency and no execution or provisioning. A provider with a batch + metrics read, such as E2B, samples the page's running sandboxes in one + bounded request. + - content: >- + Core key only. after/limit/order paginate Projects. Agent grouping + returns groups within those spaces. Date bounds filter Session + creation, not current asset counts. Usage sums only non-null public + Session usage; coverage includes every selected Session. Each Project + is read in a consistent database snapshot. Totals are not billing + records. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/credentials.mdx b/apps/docs/content/docs/api-reference/core/credentials.mdx new file mode 100644 index 000000000..f28389c4e --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/credentials.mdx @@ -0,0 +1,29 @@ +--- +title: Credentials +description: >- + Credentials. Core administration API: Core key held by Web’s server or an + operator script. Generated local management contract. This is not part of the + public OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/deployment-model-providers.mdx b/apps/docs/content/docs/api-reference/core/deployment-model-providers.mdx new file mode 100644 index 000000000..5f8f685ff --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/deployment-model-providers.mdx @@ -0,0 +1,43 @@ +--- +title: Deployment Model Providers +description: >- + Deployment Model Providers. Core administration API: Core key held by Web’s + server or an operator script. Generated local management contract. This is not + part of the public OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Returns every harness this build supports, in name + order. enabled and default are read-only views of the process + configuration (OAC_DEFAULT_HARNESS and OAC_HARNESSES). model_provider + is the harness's deployment default, stored in Core, or null. Keys are + never returned; api_key_configured reports that one is set. + - content: >- + Core key only. Returns the safe view; the key is never returned. 404 + when the harness does not exist or has no deployment default. + - content: >- + Core key only. Idempotent; each successful request is audited. + Sessions that already froze the default keep it. Afterwards new + openai_hosted Sessions for this harness need a Session or Agent + bundle. + - content: >- + Core key only. The body is the complete x_agents_core.model_provider + bundle, including the write-only api_key; there is no partial update + and bundles are never merged. The provider is validated for this + harness: an HTTPS base_url without credentials, query or fragment, the + harness's protocol (responses for codex, anthropic for claude_sdk and + mcode) and, for mcode, positive context_window and max_output_tokens. + New openai_hosted and none Sessions that resolve no Session or Agent + bundle freeze this default into their encrypted snapshot; existing + Sessions never change. self_hosted Sessions never use it. The key is + encrypted and never returned. Each write records an administrator + audit entry without the key. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/environment-templates.mdx b/apps/docs/content/docs/api-reference/core/environment-templates.mdx new file mode 100644 index 000000000..1f2754d5f --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/environment-templates.mdx @@ -0,0 +1,29 @@ +--- +title: Environment Templates +description: >- + Environment Templates. Core administration API: Core key held by Web’s server + or an operator script. Generated local management contract. This is not part + of the public OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/execution-configuration.mdx b/apps/docs/content/docs/api-reference/core/execution-configuration.mdx new file mode 100644 index 000000000..207d504b0 --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/execution-configuration.mdx @@ -0,0 +1,27 @@ +--- +title: Execution configuration +description: >- + Execution configuration. Core administration API: Core key held by Web’s + server or an operator script. Generated local management contract. This is not + part of the public OpenAI API. +full: true +_openapi: + method: GET + route: /core/v1/projects/{project_id}/sessions/{session_id}/execution-configuration + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only; the Project ID selects the target space and does not + authenticate. Returns the committed model, harness and safe provider + selection with recorded sources. This read never decrypts credentials, + resolves current defaults or probes execution health. Deployment + defaults frozen after they moved into Core show their safe view; older + deployment selections remain redacted. Historical provenance and + missing provider projections are explicitly unknown/unavailable. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/executor-credentials.mdx b/apps/docs/content/docs/api-reference/core/executor-credentials.mdx new file mode 100644 index 000000000..326c5bed2 --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/executor-credentials.mdx @@ -0,0 +1,40 @@ +--- +title: Executor Credentials +description: >- + Executor Credentials. Core administration API: Core key held by Web’s server + or an operator script. Generated local management contract. This is not part + of the public OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Returns metadata of the credentials restricted to this + Environment, oldest first; secrets are never listed. The Environment + must be a self_hosted Environment of the Project whose Session exists; + otherwise 404. + - content: >- + Core key only. Returns a connect-only secret once, restricted to + daemon enrollment and connection for this Environment, with the + Project's principal as its execution principal. Repeating an issuance + key_id returns 409 executor_credential_exists; after an uncertain + response, list the credentials and rotate that key_id explicitly. + Rotation keeps the key's Environment, invalidates the old secret and + restores a revoked key; rotating an unknown key_id returns 404. In an + archived Project, issuance and rotation return 409 project_archived. + The Environment must be a self_hosted Environment of the Project whose + Session exists; otherwise 404. Each write records an administrator + audit entry without the secret. + - content: >- + Core key only. Revokes one credential restricted to this Environment; + repeated revocation is safe and it also works in an archived Project. + Revocation denies future enrollment and connection but does not stop + executor-owned compute. The Environment must be a self_hosted + Environment of the Project whose Session exists; otherwise 404. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/files.mdx b/apps/docs/content/docs/api-reference/core/files.mdx new file mode 100644 index 000000000..186f103bb --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/files.mdx @@ -0,0 +1,29 @@ +--- +title: Files +description: >- + Files. Core administration API: Core key held by Web’s server or an operator + script. Generated local management contract. This is not part of the public + OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/index.mdx b/apps/docs/content/docs/api-reference/core/index.mdx new file mode 100644 index 000000000..a80a06dbc --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/index.mdx @@ -0,0 +1,32 @@ +--- +title: Core administration API +description: /core/v1 — Core key held by Web’s server or an operator script. +--- + +Generated local management contract. This is not part of the public OpenAI API. + +**Credential:** Core key held by Web’s server or an operator script. Examples use reserved `example.com` origins. This reference does not send requests or collect credentials. + +Operator scripts use Core’s loopback port. The public entry routes management through Web, which requires its signed-in session and supplies the Core key on the server. + +[API namespaces and credentials](/public-api) · [Application reference](/api-reference) · [Administration reference](/api-reference/core) · [Machine reference](/api-reference/machine) + +- [core-administration](/api-reference/core/core-administration) +- [deployment-model-providers](/api-reference/core/deployment-model-providers) +- [administrator-projects](/api-reference/core/administrator-projects) +- [agents](/api-reference/core/agents) +- [environment-templates](/api-reference/core/environment-templates) +- [executor-credentials](/api-reference/core/executor-credentials) +- [files](/api-reference/core/files) +- [write-audit](/api-reference/core/write-audit) +- [sessions](/api-reference/core/sessions) +- [artifacts](/api-reference/core/artifacts) +- [execution-configuration](/api-reference/core/execution-configuration) +- [items](/api-reference/core/items) +- [runtime-history](/api-reference/core/runtime-history) +- [runtime-observations](/api-reference/core/runtime-observations) +- [turns](/api-reference/core/turns) +- [skills](/api-reference/core/skills) +- [vaults](/api-reference/core/vaults) +- [credentials](/api-reference/core/credentials) +- [sandbox-manager](/api-reference/core/sandbox-manager) diff --git a/apps/docs/content/docs/api-reference/core/items.mdx b/apps/docs/content/docs/api-reference/core/items.mdx new file mode 100644 index 000000000..7589bc49b --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/items.mdx @@ -0,0 +1,23 @@ +--- +title: Items +description: >- + Items. Core administration API: Core key held by Web’s server or an operator + script. Generated local management contract. This is not part of the public + OpenAI API. +full: true +_openapi: + method: GET + route: /core/v1/projects/{project_id}/sessions/{session_id}/items + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/meta.json b/apps/docs/content/docs/api-reference/core/meta.json new file mode 100644 index 000000000..87f295cfb --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/meta.json @@ -0,0 +1,25 @@ +{ + "title": "Core administration API", + "pages": [ + "index", + "core-administration", + "deployment-model-providers", + "administrator-projects", + "agents", + "environment-templates", + "executor-credentials", + "files", + "write-audit", + "sessions", + "artifacts", + "execution-configuration", + "items", + "runtime-history", + "runtime-observations", + "turns", + "skills", + "vaults", + "credentials", + "sandbox-manager" + ] +} diff --git a/apps/docs/content/docs/api-reference/core/runtime-history.mdx b/apps/docs/content/docs/api-reference/core/runtime-history.mdx new file mode 100644 index 000000000..5b7e2da6f --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/runtime-history.mdx @@ -0,0 +1,26 @@ +--- +title: Runtime history +description: >- + Runtime history. Core administration API: Core key held by Web’s server or an + operator script. Generated local management contract. This is not part of the + public OpenAI API. +full: true +_openapi: + method: GET + route: /core/v1/projects/{project_id}/sessions/{session_id}/runtime-history + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only; the Project ID selects the target space and does not + authenticate. Returns stored Runtime observations for one Session. End + is exclusive; the server selects a bounded resolution. Responses + contain at most 1,000 series, 10,000 points per coverage/series array, + and 100,000 total coverage plus series points. It never reads or + changes live compute. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/runtime-observations.mdx b/apps/docs/content/docs/api-reference/core/runtime-observations.mdx new file mode 100644 index 000000000..b9a2917ae --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/runtime-observations.mdx @@ -0,0 +1,23 @@ +--- +title: Runtime observations +description: >- + Runtime observations. Core administration API: Core key held by Web’s server + or an operator script. Generated local management contract. This is not part + of the public OpenAI API. +full: true +_openapi: + method: GET + route: /core/v1/projects/{project_id}/sessions/{session_id}/runtime-observation + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only; the Project ID selects the target space and does not + authenticate. Returns one read-only current Runtime observation. It + never provisions, renews, restarts, pauses or stops compute. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/sandbox-manager.mdx b/apps/docs/content/docs/api-reference/core/sandbox-manager.mdx new file mode 100644 index 000000000..f05ee6363 --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/sandbox-manager.mdx @@ -0,0 +1,61 @@ +--- +title: Sandbox Manager +description: >- + Sandbox Manager. Core administration API: Core key held by Web’s server or an + operator script. Generated local management contract. This is not part of the + public OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Does not grant project resource access. Responses + contain only explicit safe fields. E2B template_build values are those + Core read when the selection was saved; this read does not call E2B. + - content: >- + Selects a provider, enforced resource limits and pinned Runtime + release. Core derives the deployment's core_url from the installation + public URL and rejects a core_url member with 400. E2B returns 409 + sandbox_configuration_error while the public URL is loopback. E2B + credentials are write-only. E2B may omit resources to adopt the + validated template build's CPU and memory, returned in + specification.resources. Exact retries return the existing selection; + differing selections and file-managed deployments reject. This does + not create compute or execute work. + - content: >- + Requires maintenance, the current generation and verified cleanup of + all old resources. Credentials are write-only. E2B may omit resources + to adopt the validated template build's CPU and memory. A core_url + member is rejected with 400; the address comes from the installation + public URL. Historical records are retained; old node credentials and + enrollments are retired. Explicitly resume after success. Never + automatically retry an uncertain write. + - content: >- + Requires the current generation. Maintenance preserves execution, + cleanup and history. Resume requires successfully activated provider + configuration. + - content: >- + Core key only. Does not grant project resource access. Responses + contain only explicit safe fields. + - content: >- + Core key only. Does not grant project resource access. Responses + contain only explicit safe fields. + - content: >- + Core key only. Complete UTC buckets. Missing host measurements and + offline history are null; reads never sample or backfill. + - content: >- + Core key only. Does not grant project resource access. Responses + contain only explicit safe fields. + - content: >- + Core key only. Does not grant project resource access. Responses + contain only explicit safe fields. + - content: >- + Core key only. Does not grant project resource access. Responses + contain only explicit safe fields. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/sessions.mdx b/apps/docs/content/docs/api-reference/core/sessions.mdx new file mode 100644 index 000000000..174d997f9 --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/sessions.mdx @@ -0,0 +1,29 @@ +--- +title: Sessions +description: >- + Sessions. Core administration API: Core key held by Web’s server or an + operator script. Generated local management contract. This is not part of the + public OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/skills.mdx b/apps/docs/content/docs/api-reference/core/skills.mdx new file mode 100644 index 000000000..1521c7fb9 --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/skills.mdx @@ -0,0 +1,49 @@ +--- +title: Skills +description: >- + Skills. Core administration API: Core key held by Web’s server or an operator + script. Generated local management contract. This is not part of the public + OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/turns.mdx b/apps/docs/content/docs/api-reference/core/turns.mdx new file mode 100644 index 000000000..d29b6ce94 --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/turns.mdx @@ -0,0 +1,25 @@ +--- +title: Turns +description: >- + Turns. Core administration API: Core key held by Web’s server or an operator + script. Generated local management contract. This is not part of the public + OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/vaults.mdx b/apps/docs/content/docs/api-reference/core/vaults.mdx new file mode 100644 index 000000000..552e8e9de --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/vaults.mdx @@ -0,0 +1,29 @@ +--- +title: Vaults +description: >- + Vaults. Core administration API: Core key held by Web’s server or an operator + script. Generated local management contract. This is not part of the public + OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. + - content: >- + Core key only. Reuses the public resource projection and operation + rules; the Project ID selects the target space and does not + authenticate. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/core/write-audit.mdx b/apps/docs/content/docs/api-reference/core/write-audit.mdx new file mode 100644 index 000000000..70eed9da8 --- /dev/null +++ b/apps/docs/content/docs/api-reference/core/write-audit.mdx @@ -0,0 +1,26 @@ +--- +title: Write Audit +description: >- + Write Audit. Core administration API: Core key held by Web’s server or an + operator script. Generated local management contract. This is not part of the + public OpenAI API. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Core key only. The Project ID path selects its space. Returns null for + resources without recorded creation provenance, including historical + and foreign resources. No key secret is returned. + - content: >- + Core key only. Reverse chronological keyset pagination over committed + writes. Creation records remain; other records follow configured + retention. The key path selects its independent space, never a + caller-supplied tenant. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/credentials.mdx b/apps/docs/content/docs/api-reference/credentials.mdx new file mode 100644 index 000000000..b75f46360 --- /dev/null +++ b/apps/docs/content/docs/api-reference/credentials.mdx @@ -0,0 +1,74 @@ +--- +title: Credentials +description: >- + Credentials. Application API: Project API key. Public schema constrained by + the pinned OpenAI Agents API baseline; documented x_agents_core fields remain + Core extensions. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + 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. + - content: >- + 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. + - content: >- + 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. + - content: >- + 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. + - content: >- + 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. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/environment-templates.mdx b/apps/docs/content/docs/api-reference/environment-templates.mdx new file mode 100644 index 000000000..5e68bd5c2 --- /dev/null +++ b/apps/docs/content/docs/api-reference/environment-templates.mdx @@ -0,0 +1,56 @@ +--- +title: Environment Templates +description: >- + Environment Templates. Application API: Project API key. Public schema + constrained by the pinned OpenAI Agents API baseline; documented x_agents_core + fields remain Core extensions. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + 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. + - content: >- + 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, + system/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. No compute + is allocated. Exact hosted error/retry semantics remain unverified. + - content: >- + Returns safe tenant-owned configuration metadata without allocating + compute. Missing and foreign resources return the same not-found + response. + - content: >- + 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. + - content: >- + Deletes the tenant-owned reusable configuration without changing or + deleting existing Sessions and their frozen configuration. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/environments.mdx b/apps/docs/content/docs/api-reference/environments.mdx new file mode 100644 index 000000000..287d4db3f --- /dev/null +++ b/apps/docs/content/docs/api-reference/environments.mdx @@ -0,0 +1,63 @@ +--- +title: Environments +description: >- + Environments. Application API: Project API key. Public schema constrained by + the pinned OpenAI Agents API baseline; documented x_agents_core fields remain + Core extensions. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + 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. + - content: >- + 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. + - content: >- + 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. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/events.mdx b/apps/docs/content/docs/api-reference/events.mdx new file mode 100644 index 000000000..227e260ff --- /dev/null +++ b/apps/docs/content/docs/api-reference/events.mdx @@ -0,0 +1,33 @@ +--- +title: Events +description: >- + Events. Application API: Project API key. Public schema constrained by the + pinned OpenAI Agents API baseline; documented x_agents_core fields remain Core + extensions. +full: true +_openapi: + method: GET + route: /agents/sessions/{session_id}/events + toc: [] + structuredData: + headings: [] + contents: + - content: >- + 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. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/files.mdx b/apps/docs/content/docs/api-reference/files.mdx new file mode 100644 index 000000000..21b1c0709 --- /dev/null +++ b/apps/docs/content/docs/api-reference/files.mdx @@ -0,0 +1,48 @@ +--- +title: Files +description: >- + Files. Application API: Project API key. Public schema constrained by the + pinned OpenAI Agents API baseline; documented x_agents_core fields remain Core + extensions. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + 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. + - content: >- + 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. + - content: >- + 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. + - content: >- + 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. + - content: >- + 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. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/index.mdx b/apps/docs/content/docs/api-reference/index.mdx new file mode 100644 index 000000000..e2bee0bf0 --- /dev/null +++ b/apps/docs/content/docs/api-reference/index.mdx @@ -0,0 +1,24 @@ +--- +title: Application API +description: /v1 — Project API key. +--- + +Public schema constrained by the pinned OpenAI Agents API baseline; documented x_agents_core fields remain Core extensions. + +**Credential:** Project API key. Examples use reserved `example.com` origins. This reference does not send requests or collect credentials. + +[API namespaces and credentials](/public-api) · [Application reference](/api-reference) · [Administration reference](/api-reference/core) · [Machine reference](/api-reference/machine) + +- [agents](/api-reference/agents) +- [environments](/api-reference/environments) +- [environment-templates](/api-reference/environment-templates) +- [sessions](/api-reference/sessions) +- [artifacts](/api-reference/artifacts) +- [events](/api-reference/events) +- [items](/api-reference/items) +- [subagents](/api-reference/subagents) +- [turns](/api-reference/turns) +- [files](/api-reference/files) +- [skills](/api-reference/skills) +- [vaults](/api-reference/vaults) +- [credentials](/api-reference/credentials) diff --git a/apps/docs/content/docs/api-reference/items.mdx b/apps/docs/content/docs/api-reference/items.mdx new file mode 100644 index 000000000..1c010f4b0 --- /dev/null +++ b/apps/docs/content/docs/api-reference/items.mdx @@ -0,0 +1,26 @@ +--- +title: Items +description: >- + Items. Application API: Project API key. Public schema constrained by the + pinned OpenAI Agents API baseline; documented x_agents_core fields remain Core + extensions. +full: true +_openapi: + method: GET + route: /agents/sessions/{session_id}/items + toc: [] + structuredData: + headings: [] + contents: + - content: >- + 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`". +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/machine/index.mdx b/apps/docs/content/docs/api-reference/machine/index.mdx new file mode 100644 index 000000000..db719d415 --- /dev/null +++ b/apps/docs/content/docs/api-reference/machine/index.mdx @@ -0,0 +1,14 @@ +--- +title: Machine connection API +description: /api/v1 — Route-specific node enrollment, node, daemon, or executor credential. +--- + +Generated local machine contract. These connections reach Core directly, never through Web. + +**Credential:** Route-specific node enrollment, node, daemon, or executor credential. Examples use reserved `example.com` origins. This reference does not send requests or collect credentials. + +This schema covers node configuration, enrollment and identity. Daemon WebSockets and executor connection details are described in the [machine overview](/public-api#machine-connection-api). + +[API namespaces and credentials](/public-api) · [Application reference](/api-reference) · [Administration reference](/api-reference/core) · [Machine reference](/api-reference/machine) + +- [sandbox-node](/api-reference/machine/sandbox-node) diff --git a/apps/docs/content/docs/api-reference/machine/meta.json b/apps/docs/content/docs/api-reference/machine/meta.json new file mode 100644 index 000000000..717a2b47d --- /dev/null +++ b/apps/docs/content/docs/api-reference/machine/meta.json @@ -0,0 +1,7 @@ +{ + "title": "Machine connection API", + "pages": [ + "index", + "sandbox-node" + ] +} diff --git a/apps/docs/content/docs/api-reference/machine/sandbox-node.mdx b/apps/docs/content/docs/api-reference/machine/sandbox-node.mdx new file mode 100644 index 000000000..7ab0fde2a --- /dev/null +++ b/apps/docs/content/docs/api-reference/machine/sandbox-node.mdx @@ -0,0 +1,31 @@ +--- +title: Sandbox Node +description: >- + Sandbox Node. Machine connection API: Route-specific node enrollment, node, + daemon, or executor credential. Generated local machine contract. These + connections reach Core directly, never through Web. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Authenticates with an unconsumed enrollment token, or a retained node + credential with X-OAC-Node-ID. Does not consume the token or expose + E2B credentials. Node files cannot override this specification. + - content: >- + Node machine connection. Consumes a one-use enrollment token; grants + no project or administrator access. Responses contain only explicit + safe fields. core_url is required and must equal the installation + public URL; a different address gets 409 sandbox_node_address_mismatch + and leaves the token unused. + - content: >- + Node machine connection. Authenticates with the retained node + credential; grants no project or administrator access. Responses + contain only explicit safe fields. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/meta.json b/apps/docs/content/docs/api-reference/meta.json new file mode 100644 index 000000000..f96a25397 --- /dev/null +++ b/apps/docs/content/docs/api-reference/meta.json @@ -0,0 +1,21 @@ +{ + "title": "Application API", + "pages": [ + "index", + "agents", + "environments", + "environment-templates", + "sessions", + "artifacts", + "events", + "items", + "subagents", + "turns", + "files", + "skills", + "vaults", + "credentials", + "core", + "machine" + ] +} diff --git a/apps/docs/content/docs/api-reference/sessions.mdx b/apps/docs/content/docs/api-reference/sessions.mdx new file mode 100644 index 000000000..2aa51289f --- /dev/null +++ b/apps/docs/content/docs/api-reference/sessions.mdx @@ -0,0 +1,239 @@ +--- +title: Sessions +description: >- + Sessions. Application API: Project API key. Public schema constrained by the + pinned OpenAI Agents API baseline; documented x_agents_core fields remain Core + extensions. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + 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. + - content: >- + The optional Core model_provider bundle resolves from the Session + override, saved Agent defaults, then, for openai_hosted and none, the + deployment default of the resolved harness; self_hosted never uses the + deployment default and none accepts only it. openai_hosted and + self_hosted Sessions that resolve no bundle return 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. Other MCP origins and + native OAuth login remain unsupported. The self_hosted profile + requires Codex, an absolute workspace_directory and empty + capability_directories, with optional non-deferred function tools and + HTTP MCP using service origin, optionally authenticated by the + attached Vault rules. 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 openai_hosted 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. + Saved-Agent retries and inline requests using Vault attachments or + credential references retain caller intent independently of later + resource changes; new hosted inline requests also freeze caller intent + before deployment defaults resolve; unrelated non-hosted inline + retries preserve resolved/default equivalences, and their resolved + hash leaves out any deployment default. Provider keys enter retry + hashes only as fingerprints keyed by the credential key. Unknown + historical creators reject retries; known creators without recorded + intent retain resolved-snapshot retry rules. 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 and Core-managed Docker openai_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. Basic + Codex and Claude SDK openai_hosted creation requires an explicitly + configured managed provider. The Claude workspace profile supports + non-deferred function tools with text or successful inline PNG/JPEG + results alongside native workspace tools; HTTP MCP remains + unsupported. Idle Sessions provision automatically; initial + provisioning has no caller connection action. Network defaults to + enabled; disabled and restricted exact ASCII hostnames are supported. + Restricted policy requires 1–100 allowed domains. Unsupported hostname + forms and startup installations are rejected. Confidential env, + system/npm/Python packages and ordered setup commands use the shared + initialization lifecycle; requested network applies after setup. + 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 environment:none function profile, + 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. + - content: >- + 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. + - content: >- + 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. + - content: >- + 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. + - content: >- + 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. The + supported self_hosted profile accepts text-only messages; qualified + Codex and Claude SDK openai_hosted profiles also accept inline + PNG/JPEG. 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 openai_hosted, 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 openai_hosted + accept ordered inline PNG/JPEG image messages. Self-hosted profiles + and other engines remain text-only; remote image URLs are unsupported. + Image references are retained unchanged without service-side + downloads. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/skills.mdx b/apps/docs/content/docs/api-reference/skills.mdx new file mode 100644 index 000000000..8757bc526 --- /dev/null +++ b/apps/docs/content/docs/api-reference/skills.mdx @@ -0,0 +1,51 @@ +--- +title: Skills +description: >- + Skills. Application API: Project API key. Public schema constrained by the + pinned OpenAI Agents API baseline; documented x_agents_core fields remain Core + extensions. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + 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. + - content: >- + 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. + - content: >- + Returns tenant-owned metadata without decrypting contents or starting + Runtime. No Beta header is required. + - content: >- + Changes only the tenant-owned default pointer; immutable versions and + existing Session snapshots remain unchanged. + - content: >- + Deletes tenant-owned source bundles. Existing Session installation + snapshots remain independent. + - content: >- + 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. + - content: >- + 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. + - content: >- + 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. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/subagents.mdx b/apps/docs/content/docs/api-reference/subagents.mdx new file mode 100644 index 000000000..e2f3346ee --- /dev/null +++ b/apps/docs/content/docs/api-reference/subagents.mdx @@ -0,0 +1,49 @@ +--- +title: Subagents +description: >- + Subagents. Application API: Project API key. Public schema constrained by the + pinned OpenAI Agents API baseline; documented x_agents_core fields remain Core + extensions. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + 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. + - content: >- + 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. + - content: >- + 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`". + - content: >- + 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. + - content: >- + 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. + - content: >- + 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`". +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/turns.mdx b/apps/docs/content/docs/api-reference/turns.mdx new file mode 100644 index 000000000..e3dac307c --- /dev/null +++ b/apps/docs/content/docs/api-reference/turns.mdx @@ -0,0 +1,28 @@ +--- +title: Turns +description: >- + Turns. Application API: Project API key. Public schema constrained by the + pinned OpenAI Agents API baseline; documented x_agents_core fields remain Core + extensions. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + 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. + - content: >- + 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. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/api-reference/vaults.mdx b/apps/docs/content/docs/api-reference/vaults.mdx new file mode 100644 index 000000000..3bb1698f4 --- /dev/null +++ b/apps/docs/content/docs/api-reference/vaults.mdx @@ -0,0 +1,50 @@ +--- +title: Vaults +description: >- + Vaults. Application API: Project API key. Public schema constrained by the + pinned OpenAI Agents API baseline; documented x_agents_core fields remain Core + extensions. +full: true +_openapi: + toc: [] + structuredData: + headings: [] + contents: + - content: >- + 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. + - content: >- + 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. + - content: >- + 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. + - content: >- + 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. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + \ No newline at end of file diff --git a/apps/docs/content/docs/bootstrap-projects-keys.mdx b/apps/docs/content/docs/bootstrap-projects-keys.mdx new file mode 100644 index 000000000..31aef04ce --- /dev/null +++ b/apps/docs/content/docs/bootstrap-projects-keys.mdx @@ -0,0 +1,101 @@ +--- +title: "Sign in and issue application keys" +description: "Keep the Core key on the management side and issue Project API keys through Web." +--- + +`services/core-console` serves built Web assets, signs administrators in with the +Core key and forwards every signed-in, same-origin `/core/v1/*` request to Core. The +backend contract and the React screens that use it are implemented; the console has +no execution controls. + +## Connection model + +The browser calls same-origin `/core/v1` through `AdminClient` and +`CoreMetricsClient`, and the `/core/v1/sandbox` management routes through the +sandbox client. The console server forwards each signed-in `/core/v1/*` request by +prefix to its configured Core upstream, with the Core key (`OAC_WEB_CORE_KEY_FILE`) +as the upstream credential; Core alone decides whether the route exists. Browser +code must never receive that credential. + +Applications call Core's `/v1` directly with their own Project API keys and the +public API's route-specific headers. Nodes and Runtime daemons call Core's `/api/v1` +directly with their own machine credentials. The console returns 404 for `/v1` and +`/api/v1`, even with an explicit Bearer token. Deployment routing must send both to Core. +The console endpoint and the public application endpoint serve different purposes, +even if they share a host. + +Use the [installation guide](/install) for deployment and the +[operations guide](/troubleshooting) for storage, upgrades and node +management. A Core, Web and PostgreSQL installation may have zero execution nodes. +Opening the console neither allocates compute nor invokes a model. Deployment +sandbox management selects E2B, Docker or microsandbox independently of an +application's caller-managed `self_hosted` Runtime, including its own E2B setup. + +## Server configuration and login + +The installer generates these from its `config.json` and `secrets/`; set them +yourself only for a Web you run without the installer. + +| Setting | Purpose | +| --- | --- | +| `OAC_WEB_ADDR` | Console listener address | +| `OAC_WEB_ORIGIN` | Exact browser-facing origin used for host and origin checks | +| `OAC_WEB_UPSTREAM` | Core HTTP(S) origin, without credentials, query or resource path | +| `OAC_WEB_CORE_KEY_FILE` | Absolute path to the private regular file containing the Core key | +| `OAC_WEB_DIST` | Absolute directory containing the built Web assets | + +The console exposes `GET /console/auth` and `POST /console/auth/login` and +`/logout`. The administrator signs in with the deployment's Core key, which the +installer writes to `secrets/core.key` under the installation directory (by default +`~/.oac/core/secrets/core.key`; see [Core key](/troubleshooting#core-key)). +The server compares it in constant time and answers with a same-origin session +cookie held only in its memory; the key is never logged or returned, and the +browser does not store it. A console restart or a Core key rotation requires +signing in again. There are no console accounts, usernames or setup step, and the +Core key cannot call `/v1`. `GET /console/config` provides safe console +configuration to an authenticated browser. + +Use TLS for remote browser access and loopback listeners for local development. +Preserve the host/origin checks and the forwarding rules. The console refuses +requests with an `Upgrade` header, CONNECT and TRACE, and any path that `safePath` +rejects: an encoded `%`, dot segments, empty segments or backslashes. Before +forwarding, it strips the browser's Authorization, Cookie, Origin and Referer headers +and overwrites the actor header (`X-Core-Console-Actor`) with `console`, so a browser +cannot choose the audit actor label the service reports. The label is caller-declared +and display only. Keep deployment, application, node and provider credentials out of +`VITE_*`, browser storage, source files, URLs and logs. + +## Projects and application keys + +Installation creates no Project or application key. An administrator creates a +Project and issues named keys on the console's **Projects and keys** page, which +uses the [administrator API](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/contracts/agents-api/admin-api.md), or with that +API directly. +The Project owns one tenant and one principal; its keys share assets and permissions. +Writes retain each key's provenance. All Projects and application keys live in +PostgreSQL, independently of deployment configuration. + +Issuance returns plaintext once; Core stores its digest. Deliver the new key to the +application privately. Rotate by issuing a replacement in the same Project and +revoking the old key. Archive disables every key while retaining assets and admitted +work. Ordinary metadata reads cannot recover plaintext. + +## Verification and diagnosis + +1. Core `/healthz` proves process liveness only. +2. Console login followed by `GET /core/v1/projects` proves the authenticated + browser-to-console and console-to-Core path. +3. A Project key must work on its public resources and fail on management routes. + The Core key must fail on `/v1`; `/v1` through the console stays 404. +4. Cross-origin management writes must be rejected. Audit actor labels must ignore + a forged browser header. +5. Runtime observations and history report execution state separately from the + sandbox deployment read (`GET /core/v1/sandbox/deployment`). Neither a login nor + a successful deployment read proves model or sandbox readiness. + +A console login failure belongs to console authentication. An upstream 401 on a +management request points to the console's Core key or Core connection. A resource +deletion conflict must remain visible; it does not authorize an execution call. +Node and daemon `/api/v1` routes and native Runtime interfaces retain their own authentication. + +[Repository source](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/docs/web/core-connection.md) diff --git a/apps/docs/content/docs/bootstrap-projects-keys.zh.mdx b/apps/docs/content/docs/bootstrap-projects-keys.zh.mdx new file mode 100644 index 000000000..1c1dd494e --- /dev/null +++ b/apps/docs/content/docs/bootstrap-projects-keys.zh.mdx @@ -0,0 +1,10 @@ +--- +title: "Sign in and issue application keys · 中文导读" +description: "当前版本的中文导读;完整操作步骤见英文正文。" +--- + +使用 Core key 登录 Web。应用开发者在项目中获取 Project API key,设置 OPENAI_BASE_URL 与 OPENAI_API_KEY。Core key 不能用来调用 /v1,应用 key 也不能调用管理或机器接口。 + +此页为中文导读,完整且与当前源码同步的步骤请阅读[英文正文](/en/bootstrap-projects-keys)。 + +[安装](/install) · [配置](/configure) · [API 与凭据](/public-api) · [节点](/hosted-providers) · [应用示例](/quickstart) diff --git a/apps/docs/content/docs/concepts.mdx b/apps/docs/content/docs/concepts.mdx new file mode 100644 index 000000000..cd2b5bd00 --- /dev/null +++ b/apps/docs/content/docs/concepts.mdx @@ -0,0 +1,88 @@ +--- +title: "Concepts and ownership" +description: "Projects, credentials and the boundary between applications, administration and execution." +--- + +OpenAgentCore is an open-source implementation of the OpenAI Agents API. Its public +contract follows the repository's pinned upstream baseline; documented native +harness differences remain explicit. Core extensions must not silently change +upstream resource shapes or execution semantics. + +## Three namespaces, three credentials + +Core serves three namespaces, each with one kind of caller and credential: + +- `/v1`: applications with a Project API key. Exactly the pinned official routes; + Core-only fields live only in `x_agents_core`. +- `/core/v1`: Core Web's server and operator scripts with the Core key. Everything + about operating the deployment, including credential issuance. +- `/api/v1`: nodes, Runtime daemons and self-hosted executors with machine + credentials issued through `/core/v1`; each works only on its own routes. + +Core enforces the separation: the Core key cannot call `/v1`, API keys cannot call +`/core/v1`, and neither works on `/api/v1`. Web calls only `/core/v1` and keeps +the Core key on its server; users sign in to Web with it. + +This is a separation of API authority, not a claim that a deployment administrator +cannot possess application credentials. An administrator can issue an API key and +use it separately. The console itself has no impersonation or execution operation. +API-key plaintext is returned once on issuance, never on reads. + +## Projects own assets + +A Project owns one execution tenant and its assets. Multiple named API keys belong +to the Project and share its principal, permissions and resources. Each write keeps +its actual key identity for provenance. There are no Core users, roles, memberships +or read-only keys. Names are labels; stable IDs identify Projects and keys. + +Projects and application API keys live only in PostgreSQL. The console and +management API use the same records. Configuration contains deployment settings, +not application credentials or Project definitions. Issue a new key in the same +Project and revoke the previous one to rotate credentials. Revocation affects only +that key and preserves assets, provenance and accepted execution. Explicit Project +archive revokes every key and blocks new issuance in that Project. +Administrators can still inspect and delete resources in archived Projects. + +Store only API-key digests and necessary metadata. Return plaintext once, on +issuance, and never replay it after an uncertain response. Project/key mutations +and administrator audit commit together. Database authentication failures fail +closed. The Core key is managed separately, in deployment configuration. + +Parsar product identities, workspaces, business permissions and collaboration +remain outside Core. Parsar is an ordinary API-key holder in a Project. + +## Resource isolation and administrator authority + +Agent API reads, writes and references are scoped to the authenticated key's Project. +A foreign resource remains indistinguishable from an absent resource under the +existing public operation's rules. Deployment nodes, configured model endpoints +and startup settings are deployment infrastructure, not shared business assets. + +Administrators can inspect resources and execution history, delete resources under +the same rules as their public deletion operations, manage Projects and API keys, +issue node enrollment tokens and self-hosted executor credentials, and query +operational counts and usage. They cannot use management endpoints to +create, copy or edit arbitrary assets, start a Session, send an event, cancel +work, or read stored credentials. Project keys share all assets within their +Project; nothing is shared or copied across Projects. + +## Secrets and evidence + +Credential values, model credentials and confidential template initialization are +write-only through resource APIs, including administrator reads. Public metadata +projections are reused by the management API. This does not redact arbitrary +user-authored conversation text, Skill source or Artifact content: administrators +who inspect those records see their recorded contents. + +Public writes retain their actual API-key provenance. Administrator writes retain +a separate audit identity and target Project. The console's fixed actor label is +display-only; Core trusts the Core key, not that forwarded label. Audit failure rolls +back the business transaction. Reads are not audited. Resources from the removed +copy operation keep their `admin_copy` ownership, distinct from historical unknown +ownership. No request bodies, secrets or file contents enter audit records. + +Do not add product users, RBAC, cross-Project shared assets, administrator execution, or old +private-protocol compatibility to this management model. Existing public +execution and Runtime ownership rules remain in [CONTRIBUTING.md](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/CONTRIBUTING.md). + +[Repository source](https://github.com/MiniMax-AI/parsar-core/blob/75bf4484ba957344ae374354858bbdfe104f5c4f/docs/design-principles.md) diff --git a/apps/docs/content/docs/concepts.zh.mdx b/apps/docs/content/docs/concepts.zh.mdx new file mode 100644 index 000000000..c0fc452ec --- /dev/null +++ b/apps/docs/content/docs/concepts.zh.mdx @@ -0,0 +1,10 @@ +--- +title: "Concepts and ownership · 中文导读" +description: "当前版本的中文导读;完整操作步骤见英文正文。" +--- + +Project 拥有应用资源和执行身份。同一项目的 API keys 共享资源;Core key 只用于管理。Core 负责执行事实,Web 不复制执行所有权。 + +此页为中文导读,完整且与当前源码同步的步骤请阅读[英文正文](/en/concepts)。 + +[安装](/install) · [配置](/configure) · [API 与凭据](/public-api) · [节点](/hosted-providers) · [应用示例](/quickstart) diff --git a/apps/docs/content/docs/configure.mdx b/apps/docs/content/docs/configure.mdx new file mode 100644 index 000000000..11653093f --- /dev/null +++ b/apps/docs/content/docs/configure.mdx @@ -0,0 +1,253 @@ +--- +title: "Configuration reference" +description: "The operator config.json, generated files and database-owned runtime settings." +--- + +Every setting of a Core installation has exactly one home. There are two kinds: + +| Kind | Examples | Home | Change it with | Takes effect | +| --- | --- | --- | --- | --- | +| [Process settings](#process-settings-configjson) | Public URL, ports, logging, harnesses, execution concurrency, audit retention, OAuth origins, database pool, Runtime history export | `config.json` in the installation directory (default `~/.oac/core`) | Edit the file, then run `oac apply` | `oac apply` restarts the services that read the changed settings | +| [Runtime settings](#runtime-settings-web) | Sandbox backend and size, nodes, Projects and keys, default models, executor credentials | Core's PostgreSQL database | Web, or the Core API (`/core/v1`) with the Core key | At once, without a restart | + +Web's **System** page shows both: the installation's addresses, the process settings +read-only under **Startup settings** with the path of `config.json` and the apply +command, the default models, and the sandbox configuration. Secrets live in +[`secrets/`](#secrets-and-identity), one copy each. Each setting is set in one place; +the files in `generated/` are only derived from `config.json`. No configuration file +defines Projects or API keys. + +## Process settings: config.json + +The installer writes every setting that applies to the installation's +[mode](/install#modes), so the file shows each value. Installer flags +such as `--public-url`, `--core-port` and `--web-port` only seed it. To change a +setting, edit the file and apply it: + +```sh +~/.oac/core/oac apply --dry-run # show the changed settings, files and restarts +~/.oac/core/oac apply +``` + +### How oac apply works + +1. It validates `config.json` and changes nothing if a value is invalid. `mode` and + `native_core` are fixed after installation; to change them, install into a new + directory. +2. It writes the files Core, Web and Compose read into `generated/`: `compose.json`, + `core.env`, `core-key-digests.json`, `settings.json` and, when used, + `runtime-history.json` and the native Core unit. Don't edit them. A generated file + edited by hand stops `apply` until you move the change into `config.json` and run + `oac apply --discard-edits`, which keeps the edited copy as + `generated/.edited-