From 5c02339e559f4bb10ed32ffa907600e73896f06f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 16:17:15 +0800 Subject: [PATCH 1/2] fix: align session and resource contracts with QCA docs --- CHANGELOG.md | 23 ++ docs/api/reference.md | 443 +++++++++++++++++++----- forward/documentation_alignment_test.go | 179 ++++++++++ forward/environment.go | 21 +- forward/params.go | 5 + forward/session.go | 45 ++- forward/sessionevent.go | 147 +++++--- managed/documentation_alignment_test.go | 87 +++++ managed/environment.go | 81 +++-- managed/memorystorememory.go | 8 + managed/memorystorememoryversion.go | 10 +- managed/session.go | 19 +- managed/sessionthread.go | 25 +- managed/vault.go | 3 + 14 files changed, 903 insertions(+), 193 deletions(-) create mode 100644 forward/documentation_alignment_test.go create mode 100644 managed/documentation_alignment_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 7494e1e..f05b67e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ existing `0.1.0` release; earlier development prereleases are not listed. ## [Unreleased] +### Added + +- Add typed Forward Session budgets across create/update responses and events, including omission, explicit null clearing and decimal-string limits. +- Add Forward Session `config.model` overrides and model `speed` configuration, plus optional approval evaluation details, Outcome/status event fields and cumulative top-level Credits on `session.usage`. +- Add `startup_script` to Forward/Managed Environment models and Managed cloud/self-hosted parameters. It runs on sandbox start/resume; the external worker controls self-hosted execution. +- Add Managed Memory `version`, version `redacted`, and Vault creation-response `credentials` fields. Vault creation still does not accept credentials in its request. + +### Fixed + +- Decode Forward event `evaluated_permission` as a string and align Managed Thread/coordinator nested model effort and context-window fields. +- Add the current Memory version `updated` operation and list-item metadata/version fields; retain the legacy `Modified` constant as deprecated. + +### Breaking changes + +- Forward `SessionEvent.EvaluatedPermission` and the Managed Thread/coordinator union model `Effort` fields now use strings instead of object types. + +### Migration + +- Compare `event.EvaluatedPermission` directly to `"allow"`, `"ask"` or `"deny"`; automatic approval details are under `event.Evaluation.EvaluatedPermission`. +- Read nested model `Effort` directly as a string. Legacy Managed model `Speed` and Thread `Usage` remain available but deprecated; current Thread responses omit usage. +- Use `param.NullStruct[forward.SessionBudgetParam]()` to clear a budget and `param.NullStruct[forward.ModelConfigUnionParam]()` to clear an update's model override. Omit the model on create to inherit. +- Treat `session.usage` Credits as cumulative snapshots: replace the previous values rather than adding successive events. + ### Changed - Remove resource-specific sections and inventories from the README to keep it focused on SDK setup and shared client behavior. diff --git a/docs/api/reference.md b/docs/api/reference.md index ae46612..85b995a 100644 --- a/docs/api/reference.md +++ b/docs/api/reference.md @@ -796,6 +796,12 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" - [func \(r \*Session\) UnmarshalJSON\(data \[\]byte\) error](<#Session.UnmarshalJSON>) - [type SessionArchiveParams](<#SessionArchiveParams>) - [func \(r SessionArchiveParams\) URLQuery\(\) \(url.Values, error\)](<#SessionArchiveParams.URLQuery>) +- [type SessionBudget](<#SessionBudget>) + - [func \(r SessionBudget\) RawJSON\(\) string](<#SessionBudget.RawJSON>) + - [func \(r \*SessionBudget\) UnmarshalJSON\(data \[\]byte\) error](<#SessionBudget.UnmarshalJSON>) +- [type SessionBudgetParam](<#SessionBudgetParam>) + - [func \(r SessionBudgetParam\) MarshalJSON\(\) \(\[\]byte, error\)](<#SessionBudgetParam.MarshalJSON>) + - [func \(r \*SessionBudgetParam\) UnmarshalJSON\(data \[\]byte\) error](<#SessionBudgetParam.UnmarshalJSON>) - [type SessionCancelParams](<#SessionCancelParams>) - [func \(r SessionCancelParams\) URLQuery\(\) \(url.Values, error\)](<#SessionCancelParams.URLQuery>) - [type SessionConfig](<#SessionConfig>) @@ -1003,6 +1009,12 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" - [type Tool](<#Tool>) - [func \(r Tool\) RawJSON\(\) string](<#Tool.RawJSON>) - [func \(r \*Tool\) UnmarshalJSON\(data \[\]byte\) error](<#Tool.UnmarshalJSON>) +- [type ToolApprovalDecision](<#ToolApprovalDecision>) + - [func \(r ToolApprovalDecision\) RawJSON\(\) string](<#ToolApprovalDecision.RawJSON>) + - [func \(r \*ToolApprovalDecision\) UnmarshalJSON\(data \[\]byte\) error](<#ToolApprovalDecision.UnmarshalJSON>) +- [type ToolApprovalEvaluation](<#ToolApprovalEvaluation>) + - [func \(r ToolApprovalEvaluation\) RawJSON\(\) string](<#ToolApprovalEvaluation.RawJSON>) + - [func \(r \*ToolApprovalEvaluation\) UnmarshalJSON\(data \[\]byte\) error](<#ToolApprovalEvaluation.UnmarshalJSON>) - [type ToolConfig](<#ToolConfig>) - [func \(r ToolConfig\) RawJSON\(\) string](<#ToolConfig.RawJSON>) - [func \(r \*ToolConfig\) UnmarshalJSON\(data \[\]byte\) error](<#ToolConfig.UnmarshalJSON>) @@ -4719,14 +4731,17 @@ func (r *EnvironmentBindingInfo) UnmarshalJSON(data []byte) error ```go type EnvironmentConfig struct { - SetupScript string `json:"setup_script"` - Type string `json:"type"` - Packages EnvironmentConfigPackages `json:"packages"` - JSON struct { - SetupScript respjson.Field - Type respjson.Field - Packages respjson.Field - ExtraFields map[string]respjson.Field + // Runs on sandbox start/resume; self-hosted execution depends on the worker. + StartupScript string `json:"startup_script"` + SetupScript string `json:"setup_script"` + Type string `json:"type"` + Packages EnvironmentConfigPackages `json:"packages"` + JSON struct { + StartupScript respjson.Field + SetupScript respjson.Field + Type respjson.Field + Packages respjson.Field + ExtraFields map[string]respjson.Field // contains filtered or unexported fields } `json:"-"` } @@ -7936,10 +7951,13 @@ ModelConfig preserves either a model ID string or an object from the API. ID is ```go type ModelConfig struct { + // Inference speed: standard or high, subject to model capabilities. + Speed string `json:"speed"` ID string `json:"id"` Effort string `json:"effort"` ContextWindow int64 `json:"context_window"` JSON struct { + Speed respjson.Field ID respjson.Field Effort respjson.Field ContextWindow respjson.Field @@ -7974,6 +7992,8 @@ func (r *ModelConfig) UnmarshalJSON(data []byte) error ```go type ModelConfigParam struct { + // Inference speed: standard or high, subject to model capabilities. + Speed param.Opt[string] `json:"speed,omitzero"` ID string `json:"id" api:"required"` Effort param.Opt[string] `json:"effort,omitzero"` ContextWindow param.Opt[int64] `json:"context_window,omitzero"` @@ -10716,6 +10736,8 @@ Revoke revokes a Forward SAT binding belonging to the same Service Account. Use ```go type Session struct { + // Use JSON.Budget to distinguish absent, null (unlimited), and an object. + Budget SessionBudget `json:"budget" api:"nullable"` OutcomeEvaluations []SessionOutcomeEvaluation `json:"outcome_evaluations"` // Session ID. ID string `json:"id"` @@ -10739,6 +10761,7 @@ type Session struct { CreatedAt time.Time `json:"created_at" format:"date-time"` UpdatedAt time.Time `json:"updated_at" format:"date-time"` JSON struct { + Budget respjson.Field OutcomeEvaluations respjson.Field ID respjson.Field Type respjson.Field @@ -10801,6 +10824,73 @@ func (r SessionArchiveParams) URLQuery() (url.Values, error) + +## type [SessionBudget]() + + + +```go +type SessionBudget struct { + Type string `json:"type"` + MaxCreditCost string `json:"max_credit_cost"` + JSON struct { + Type respjson.Field + MaxCreditCost respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(SessionBudget\) [RawJSON]() + +```go +func (r SessionBudget) RawJSON() string +``` + + + + +### func \(\*SessionBudget\) [UnmarshalJSON]() + +```go +func (r *SessionBudget) UnmarshalJSON(data []byte) error +``` + + + + +## type [SessionBudgetParam]() + +A cumulative model and sandbox Credits limit. MaxCreditCost is a positive decimal string. Once reached, the server rejects the next user message; an active turn can finish. + +```go +type SessionBudgetParam struct { + Type string `json:"type" api:"required"` + MaxCreditCost string `json:"max_credit_cost" api:"required"` + // contains filtered or unexported fields +} +``` + + +### func \(SessionBudgetParam\) [MarshalJSON]() + +```go +func (r SessionBudgetParam) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*SessionBudgetParam\) [UnmarshalJSON]() + +```go +func (r *SessionBudgetParam) UnmarshalJSON(data []byte) error +``` + + + ## type [SessionCancelParams]() @@ -10830,8 +10920,10 @@ func (r SessionCancelParams) URLQuery() (url.Values, error) ```go type SessionConfig struct { + Model ModelConfig `json:"model"` EnvironmentVariables map[string]string `json:"environment_variables"` JSON struct { + Model respjson.Field EnvironmentVariables respjson.Field ExtraFields map[string]respjson.Field // contains filtered or unexported fields @@ -10864,6 +10956,24 @@ func (r *SessionConfig) UnmarshalJSON(data []byte) error ```go type SessionEvent struct { + // Cumulative total Credits. + TotalCredits float64 `json:"total_credits"` + // Cumulative sandbox Credits. + SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"` + // Cumulative model Credits; use the latest value, not a sum of events. + ModelCredits float64 `json:"model_credits"` + // Related outcome evaluation start event. + OutcomeEvaluationStartID string `json:"outcome_evaluation_start_id"` + // Outcome evaluation explanation. + Explanation string `json:"explanation"` + // Outcome evaluation iteration. + Iteration int64 `json:"iteration"` + // Optional status reason, such as outcome_evaluation. + Reason string `json:"reason"` + // Optional approval details; evaluated_permission at the event root is a string. + Evaluation ToolApprovalEvaluation `json:"evaluation"` + // Use JSON.Budget to distinguish absent, null (unlimited), and an object. + Budget SessionBudget `json:"budget" api:"nullable"` ID string `json:"id"` Type string `json:"type"` SessionID string `json:"session_id"` @@ -10895,7 +11005,7 @@ type SessionEvent struct { Usage map[string]any `json:"usage"` StopReason map[string]any `json:"stop_reason"` Error map[string]any `json:"error"` - EvaluatedPermission map[string]any `json:"evaluated_permission"` + EvaluatedPermission string `json:"evaluated_permission"` FileID string `json:"file_id"` OriginalFilename string `json:"original_filename"` Size int64 `json:"size"` @@ -10906,48 +11016,57 @@ type SessionEvent struct { ModelRequestStartID string `json:"model_request_start_id"` ModelUsage map[string]any `json:"model_usage"` JSON struct { - ID respjson.Field - Type respjson.Field - SessionID respjson.Field - Content respjson.Field - ProcessedAt respjson.Field - SessionThreadID respjson.Field - Thinking respjson.Field - Text respjson.Field - ToolUseID respjson.Field - CustomToolUseID respjson.Field - MCPToolUseID respjson.Field - Name respjson.Field - MCPServerName respjson.Field - Input respjson.Field - IsError respjson.Field - Result respjson.Field - DenyMessage respjson.Field - Description respjson.Field - Rubric respjson.Field - OutcomeID respjson.Field - MaxIterations respjson.Field - MessageID respjson.Field - Message respjson.Field - Index respjson.Field - ContentBlock respjson.Field - Delta respjson.Field - Event respjson.Field - EventID respjson.Field - Usage respjson.Field - StopReason respjson.Field - Error respjson.Field - EvaluatedPermission respjson.Field - FileID respjson.Field - OriginalFilename respjson.Field - Size respjson.Field - ContentType respjson.Field - Agent respjson.Field - Metadata respjson.Field - Title respjson.Field - ModelRequestStartID respjson.Field - ModelUsage respjson.Field - ExtraFields map[string]respjson.Field + TotalCredits respjson.Field + SandboxRuntimeCredits respjson.Field + ModelCredits respjson.Field + OutcomeEvaluationStartID respjson.Field + Explanation respjson.Field + Iteration respjson.Field + Reason respjson.Field + Evaluation respjson.Field + Budget respjson.Field + ID respjson.Field + Type respjson.Field + SessionID respjson.Field + Content respjson.Field + ProcessedAt respjson.Field + SessionThreadID respjson.Field + Thinking respjson.Field + Text respjson.Field + ToolUseID respjson.Field + CustomToolUseID respjson.Field + MCPToolUseID respjson.Field + Name respjson.Field + MCPServerName respjson.Field + Input respjson.Field + IsError respjson.Field + Result respjson.Field + DenyMessage respjson.Field + Description respjson.Field + Rubric respjson.Field + OutcomeID respjson.Field + MaxIterations respjson.Field + MessageID respjson.Field + Message respjson.Field + Index respjson.Field + ContentBlock respjson.Field + Delta respjson.Field + Event respjson.Field + EventID respjson.Field + Usage respjson.Field + StopReason respjson.Field + Error respjson.Field + EvaluatedPermission respjson.Field + FileID respjson.Field + OriginalFilename respjson.Field + Size respjson.Field + ContentType respjson.Field + Agent respjson.Field + Metadata respjson.Field + Title respjson.Field + ModelRequestStartID respjson.Field + ModelUsage respjson.Field + ExtraFields map[string]respjson.Field // contains filtered or unexported fields } `json:"-"` } @@ -11278,6 +11397,8 @@ func (r SessionListParams) URLQuery() (url.Values, error) ```go type SessionNewParams struct { + // Omit to keep the default/current budget; use param.NullStruct[SessionBudgetParam]() to clear. + Budget SessionBudgetParam `json:"budget,omitzero" api:"nullable"` // Forward Identity ID. IdentityID string `json:"identity_id" api:"required"` // Forward Template ID. @@ -11319,7 +11440,9 @@ func (r *SessionNewParams) UnmarshalJSON(data []byte) error ```go type SessionNewParamsConfigParam struct { - EnvironmentVariables map[string]any `json:"environment_variables,omitzero"` + // Model override. Omit on create to inherit; explicit null on update restores inheritance. + Model ModelConfigUnionParam `json:"model,omitzero"` + EnvironmentVariables map[string]any `json:"environment_variables,omitzero"` // contains filtered or unexported fields } ``` @@ -12122,6 +12245,8 @@ func (r *SessionThreadUsage) UnmarshalJSON(data []byte) error ```go type SessionUpdateParams struct { + // Omit to keep the default/current budget; use param.NullStruct[SessionBudgetParam]() to clear. + Budget SessionBudgetParam `json:"budget,omitzero" api:"nullable"` // New Session title. Title param.Opt[string] `json:"title,omitzero"` // Metadata merge patch; supplied keys overwrite existing keys, omitted keys are kept. @@ -12158,7 +12283,9 @@ func (r *SessionUpdateParams) UnmarshalJSON(data []byte) error ```go type SessionUpdateParamsConfigParam struct { - EnvironmentVariables map[string]any `json:"environment_variables,omitzero"` + // Model override. Omit on create to inherit; explicit null on update restores inheritance. + Model ModelConfigUnionParam `json:"model,omitzero"` + EnvironmentVariables map[string]any `json:"environment_variables,omitzero"` // contains filtered or unexported fields } ``` @@ -13521,6 +13648,78 @@ func (r *Tool) UnmarshalJSON(data []byte) error + +## type [ToolApprovalDecision]() + + + +```go +type ToolApprovalDecision struct { + Type string `json:"type"` + ReasonCode string `json:"reason_code"` + JSON struct { + Type respjson.Field + ReasonCode respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(ToolApprovalDecision\) [RawJSON]() + +```go +func (r ToolApprovalDecision) RawJSON() string +``` + + + + +### func \(\*ToolApprovalDecision\) [UnmarshalJSON]() + +```go +func (r *ToolApprovalDecision) UnmarshalJSON(data []byte) error +``` + + + + +## type [ToolApprovalEvaluation]() + + + +```go +type ToolApprovalEvaluation struct { + Type string `json:"type"` + EvaluatedPermission ToolApprovalDecision `json:"evaluated_permission"` + JSON struct { + Type respjson.Field + EvaluatedPermission respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(ToolApprovalEvaluation\) [RawJSON]() + +```go +func (r ToolApprovalEvaluation) RawJSON() string +``` + + + + +### func \(\*ToolApprovalEvaluation\) [UnmarshalJSON]() + +```go +func (r *ToolApprovalEvaluation) UnmarshalJSON(data []byte) error +``` + + + ## type [ToolConfig]() @@ -15245,6 +15444,7 @@ Qoder managed API definitions. - [func \(u EnvironmentNewParamsConfigUnion\) GetNetworking\(\) \*CloudConfigParamsNetworkingUnion](<#EnvironmentNewParamsConfigUnion.GetNetworking>) - [func \(u EnvironmentNewParamsConfigUnion\) GetPackages\(\) \*PackagesParams](<#EnvironmentNewParamsConfigUnion.GetPackages>) - [func \(u EnvironmentNewParamsConfigUnion\) GetSetupScript\(\) \*string](<#EnvironmentNewParamsConfigUnion.GetSetupScript>) + - [func \(u EnvironmentNewParamsConfigUnion\) GetStartupScript\(\) \*string](<#EnvironmentNewParamsConfigUnion.GetStartupScript>) - [func \(u EnvironmentNewParamsConfigUnion\) GetType\(\) \*string](<#EnvironmentNewParamsConfigUnion.GetType>) - [func \(u EnvironmentNewParamsConfigUnion\) MarshalJSON\(\) \(\[\]byte, error\)](<#EnvironmentNewParamsConfigUnion.MarshalJSON>) - [func \(u \*EnvironmentNewParamsConfigUnion\) UnmarshalJSON\(data \[\]byte\) error](<#EnvironmentNewParamsConfigUnion.UnmarshalJSON>) @@ -15271,6 +15471,7 @@ Qoder managed API definitions. - [func \(u EnvironmentUpdateParamsConfigUnion\) GetNetworking\(\) \*CloudConfigParamsNetworkingUnion](<#EnvironmentUpdateParamsConfigUnion.GetNetworking>) - [func \(u EnvironmentUpdateParamsConfigUnion\) GetPackages\(\) \*PackagesParams](<#EnvironmentUpdateParamsConfigUnion.GetPackages>) - [func \(u EnvironmentUpdateParamsConfigUnion\) GetSetupScript\(\) \*string](<#EnvironmentUpdateParamsConfigUnion.GetSetupScript>) + - [func \(u EnvironmentUpdateParamsConfigUnion\) GetStartupScript\(\) \*string](<#EnvironmentUpdateParamsConfigUnion.GetStartupScript>) - [func \(u EnvironmentUpdateParamsConfigUnion\) GetType\(\) \*string](<#EnvironmentUpdateParamsConfigUnion.GetType>) - [func \(u EnvironmentUpdateParamsConfigUnion\) MarshalJSON\(\) \(\[\]byte, error\)](<#EnvironmentUpdateParamsConfigUnion.MarshalJSON>) - [func \(u \*EnvironmentUpdateParamsConfigUnion\) UnmarshalJSON\(data \[\]byte\) error](<#EnvironmentUpdateParamsConfigUnion.UnmarshalJSON>) @@ -19981,8 +20182,10 @@ NewClient uses common authentication defaults and QODER\_BASE\_URL. Explicit opt ```go type CloudConfig struct { // Set Betas to include "oss-bucket-2026-09-23" when buckets remain configured. - Storage BucketStorage `json:"storage"` - SetupScript string `json:"setup_script"` + Storage BucketStorage `json:"storage"` + // Runs on sandbox start/resume; self-hosted execution depends on the worker. + StartupScript string `json:"startup_script"` + SetupScript string `json:"setup_script"` // Network configuration policy. Networking CloudConfigNetworkingUnion `json:"networking" api:"required"` // Package manager configuration. @@ -19991,12 +20194,13 @@ type CloudConfig struct { Type constant.Cloud `json:"type" default:"cloud"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - Storage respjson.Field - SetupScript respjson.Field - Networking respjson.Field - Packages respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field + Storage respjson.Field + StartupScript respjson.Field + SetupScript respjson.Field + Networking respjson.Field + Packages respjson.Field + Type respjson.Field + ExtraFields map[string]respjson.Field // contains filtered or unexported fields } `json:"-"` } @@ -20115,8 +20319,10 @@ The property Type is required. ```go type CloudConfigParams struct { // Set Betas to include "oss-bucket-2026-09-23" when buckets remain configured. - Storage BucketStorageParam `json:"storage,omitzero"` - SetupScript param.Opt[string] `json:"setup_script,omitzero"` + Storage BucketStorageParam `json:"storage,omitzero"` + // Runs on sandbox start/resume; self-hosted execution depends on the worker. + StartupScript param.Opt[string] `json:"startup_script,omitzero"` + SetupScript param.Opt[string] `json:"setup_script,omitzero"` // Network configuration policy. Omit on update to preserve the existing value. Networking CloudConfigParamsNetworkingUnion `json:"networking,omitzero"` // Specify packages (and optionally their versions) available in this environment. @@ -23181,8 +23387,10 @@ Use the methods beginning with 'As' to cast the union to one of its variants. ```go type EnvironmentConfigUnion struct { // Set Betas to include "oss-bucket-2026-09-23" when buckets remain configured. - Storage BucketStorage `json:"storage"` - SetupScript string `json:"setup_script"` + Storage BucketStorage `json:"storage"` + // Runs on sandbox start/resume; self-hosted execution depends on the worker. + StartupScript string `json:"startup_script"` + SetupScript string `json:"setup_script"` // This field is from variant [CloudConfig]. Networking CloudConfigNetworkingUnion `json:"networking"` // This field is from variant [CloudConfig]. @@ -23190,11 +23398,12 @@ type EnvironmentConfigUnion struct { // Any of "cloud", "self_hosted". Type string `json:"type"` JSON struct { - Storage respjson.Field - SetupScript respjson.Field - Networking respjson.Field - Packages respjson.Field - Type respjson.Field + Storage respjson.Field + StartupScript respjson.Field + SetupScript respjson.Field + Networking respjson.Field + Packages respjson.Field + Type respjson.Field // contains filtered or unexported fields } `json:"-"` } @@ -23458,6 +23667,15 @@ func (u EnvironmentNewParamsConfigUnion) GetSetupScript() *string Returns the setup script from the selected configuration, if present. + +### func \(EnvironmentNewParamsConfigUnion\) [GetStartupScript]() + +```go +func (u EnvironmentNewParamsConfigUnion) GetStartupScript() *string +``` + + + ### func \(EnvironmentNewParamsConfigUnion\) [GetType]() @@ -23760,6 +23978,15 @@ func (u EnvironmentUpdateParamsConfigUnion) GetSetupScript() *string Returns the setup script from the selected configuration, if present. + +### func \(EnvironmentUpdateParamsConfigUnion\) [GetStartupScript]() + +```go +func (u EnvironmentUpdateParamsConfigUnion) GetStartupScript() *string +``` + + + ### func \(EnvironmentUpdateParamsConfigUnion\) [GetType]() @@ -37642,6 +37869,8 @@ A \`memory\` object: a single text document at a hierarchical path inside a memo ```go type ManagedAgentsMemory struct { + // Current memory version, starting at 1. Absent on prefix entries. + Version int64 `json:"version"` Metadata map[string]string `json:"metadata"` // Unique identifier for this memory (a `mem_...` value). Stable across renames; @@ -37678,6 +37907,7 @@ type ManagedAgentsMemory struct { Content string `json:"content" api:"nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { + Version respjson.Field Metadata respjson.Field ID respjson.Field @@ -37725,6 +37955,9 @@ Use the methods beginning with 'As' to cast the union to one of its variants. ```go type ManagedAgentsMemoryListItemUnion struct { + Metadata map[string]string `json:"metadata"` + // Current memory version, starting at 1. Absent on prefix entries. + Version int64 `json:"version"` // This field is from variant [ManagedAgentsMemory]. ID string `json:"id"` // This field is from variant [ManagedAgentsMemory]. @@ -37745,6 +37978,8 @@ type ManagedAgentsMemoryListItemUnion struct { // This field is from variant [ManagedAgentsMemory]. Content string `json:"content"` JSON struct { + Metadata respjson.Field + Version respjson.Field ID respjson.Field ContentSha256 respjson.Field ContentSizeBytes respjson.Field @@ -38351,6 +38586,7 @@ A \`memory\_version\` object: one immutable, attributed row in a memory's append ```go type ManagedAgentsMemoryVersion struct { + Redacted bool `json:"redacted"` // Unique identifier for this version (a `memver_...` value). ID string `json:"id" api:"required"` // A timestamp in RFC 3339 format @@ -38366,7 +38602,7 @@ type ManagedAgentsMemoryVersion struct { // The kind of mutation a `memory_version` records. Every non-no-op mutation to a // memory appends exactly one version row with one of these values. // - // Any of "created", "modified", "deleted". + // Any of "created", "updated", "deleted". Operation ManagedAgentsMemoryVersionOperation `json:"operation" api:"required"` // Any of "memory_version". Type ManagedAgentsMemoryVersionType `json:"type" api:"required"` @@ -38399,6 +38635,7 @@ type ManagedAgentsMemoryVersion struct { RedactedBy ManagedAgentsActorUnion `json:"redacted_by"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { + Redacted respjson.Field ID respjson.Field CreatedAt respjson.Field MemoryID respjson.Field @@ -38449,7 +38686,9 @@ type ManagedAgentsMemoryVersionOperation string ```go const ( - ManagedAgentsMemoryVersionOperationCreated ManagedAgentsMemoryVersionOperation = "created" + ManagedAgentsMemoryVersionOperationCreated ManagedAgentsMemoryVersionOperation = "created" + ManagedAgentsMemoryVersionOperationUpdated ManagedAgentsMemoryVersionOperation = "updated" + // Deprecated: current API responses use Updated. Retained for legacy payloads. ManagedAgentsMemoryVersionOperationModified ManagedAgentsMemoryVersionOperation = "modified" ManagedAgentsMemoryVersionOperationDeleted ManagedAgentsMemoryVersionOperation = "deleted" ) @@ -43494,22 +43733,27 @@ If the underlying value is not a json object, one of the following properties wi ```go type ManagedAgentsSessionMultiagentCoordinatorAgentUnionModel struct { + EffectiveContextWindow int64 `json:"effective_context_window"` + ContextWindow int64 `json:"context_window"` // This field will be present if the value is a [string] instead of an object. OfString string `json:",inline"` // This field is from variant [ManagedAgentsModelConfig]. ID ManagedAgentsModel `json:"id"` // This field is from variant [ManagedAgentsModelConfig]. - Effort ManagedAgentsModelConfigEffortUnion `json:"effort"` + Effort string `json:"effort"` // This field is from variant [ManagedAgentsModelConfig]. InferenceGeo string `json:"inference_geo"` // This field is from variant [ManagedAgentsModelConfig]. + // Deprecated: speed is no longer part of Managed model configuration. Speed ManagedAgentsModelConfigSpeed `json:"speed"` JSON struct { - OfString respjson.Field - ID respjson.Field - Effort respjson.Field - InferenceGeo respjson.Field - Speed respjson.Field + EffectiveContextWindow respjson.Field + ContextWindow respjson.Field + OfString respjson.Field + ID respjson.Field + Effort respjson.Field + InferenceGeo respjson.Field + Speed respjson.Field // contains filtered or unexported fields } `json:"-"` } @@ -44481,8 +44725,8 @@ type ManagedAgentsSessionThread struct { Type ManagedAgentsSessionThreadType `json:"type" api:"required"` // A timestamp in RFC 3339 format UpdatedAt time.Time `json:"updated_at" api:"required" format:"date-time"` - // Cumulative token usage for a session thread across all turns. - Usage ManagedAgentsSessionThreadUsage `json:"usage" api:"required"` + // Deprecated: current Thread responses do not include usage. Read Session Credits instead. + Usage ManagedAgentsSessionThreadUsage `json:"usage"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { ID respjson.Field @@ -44981,22 +45225,27 @@ If the underlying value is not a json object, one of the following properties wi ```go type ManagedAgentsSessionThreadAgentUnionModel struct { + EffectiveContextWindow int64 `json:"effective_context_window"` + ContextWindow int64 `json:"context_window"` // This field will be present if the value is a [string] instead of an object. OfString string `json:",inline"` // This field is from variant [ManagedAgentsModelConfig]. ID ManagedAgentsModel `json:"id"` // This field is from variant [ManagedAgentsModelConfig]. - Effort ManagedAgentsModelConfigEffortUnion `json:"effort"` + Effort string `json:"effort"` // This field is from variant [ManagedAgentsModelConfig]. InferenceGeo string `json:"inference_geo"` // This field is from variant [ManagedAgentsModelConfig]. + // Deprecated: speed is no longer part of Managed model configuration. Speed ManagedAgentsModelConfigSpeed `json:"speed"` JSON struct { - OfString respjson.Field - ID respjson.Field - Effort respjson.Field - InferenceGeo respjson.Field - Speed respjson.Field + EffectiveContextWindow respjson.Field + ContextWindow respjson.Field + OfString respjson.Field + ID respjson.Field + Effort respjson.Field + InferenceGeo respjson.Field + Speed respjson.Field // contains filtered or unexported fields } `json:"-"` } @@ -45521,7 +45770,7 @@ const ( ## type [ManagedAgentsSessionThreadUsage]() -Cumulative token usage for a session thread across all turns. +Deprecated: current Thread responses do not include usage. Read Session Credits instead. ```go type ManagedAgentsSessionThreadUsage struct { @@ -51583,6 +51832,8 @@ A vault that stores credentials for use by agents during sessions. ```go type ManagedAgentsVault struct { + // Empty on creation; add credentials using the separate Credential API. + Credentials []ManagedAgentsCredential `json:"credentials"` // Unique identifier for the vault. ID string `json:"id" api:"required"` // A timestamp in RFC 3339 format @@ -51599,6 +51850,7 @@ type ManagedAgentsVault struct { UpdatedAt time.Time `json:"updated_at" api:"required" format:"date-time"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { + Credentials respjson.Field ID respjson.Field ArchivedAt respjson.Field CreatedAt respjson.Field @@ -53087,7 +53339,7 @@ type MemoryStoreMemoryVersionListParams struct { SessionID param.Opt[string] `query:"session_id,omitzero" json:"-"` // Query parameter for operation // - // Any of "created", "modified", "deleted". + // Any of "created", "updated", "deleted". Operation ManagedAgentsMemoryVersionOperation `query:"operation,omitzero" json:"-"` // Query parameter for view // @@ -55120,14 +55372,17 @@ Configuration for self\-hosted environments. ```go type SelfHostedConfig struct { - SetupScript string `json:"setup_script"` + // Runs on sandbox start/resume; self-hosted execution depends on the worker. + StartupScript string `json:"startup_script"` + SetupScript string `json:"setup_script"` // Environment type Type constant.SelfHosted `json:"type" default:"self_hosted"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - SetupScript respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field + StartupScript respjson.Field + SetupScript respjson.Field + Type respjson.Field + ExtraFields map[string]respjson.Field // contains filtered or unexported fields } `json:"-"` } @@ -55160,7 +55415,9 @@ This struct has a constant value, construct it with [NewSelfHostedConfigParams]( ```go type SelfHostedConfigParams struct { - SetupScript param.Opt[string] `json:"setup_script,omitzero"` + // Runs on sandbox start/resume; self-hosted execution depends on the worker. + StartupScript param.Opt[string] `json:"startup_script,omitzero"` + SetupScript param.Opt[string] `json:"setup_script,omitzero"` // Environment type Type constant.SelfHosted `json:"type" default:"self_hosted"` // contains filtered or unexported fields diff --git a/forward/documentation_alignment_test.go b/forward/documentation_alignment_test.go new file mode 100644 index 0000000..ddc1f5d --- /dev/null +++ b/forward/documentation_alignment_test.go @@ -0,0 +1,179 @@ +package forward_test + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "reflect" + "strings" + "testing" + + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/param" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/ssestream" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" +) + +// Official Forward Session schemas and Environment schemas, retrieved 2026-11-03. +func TestForwardDocumentationBudgetAndModelWire(t *testing.T) { + budget := forward.SessionBudgetParam{Type: "limit", MaxCreditCost: "100.00"} + model := forward.ModelConfigUnionParam{OfConfig: &forward.ModelConfigParam{ID: "ultimate", Speed: forward.String("high"), ContextWindow: forward.Int(200000)}} + for _, tc := range []struct { + name string + params forward.SessionUpdateParams + want string + }{ + {"omit", forward.SessionUpdateParams{}, `{}`}, + {"clear", forward.SessionUpdateParams{Budget: param.NullStruct[forward.SessionBudgetParam](), Config: forward.SessionUpdateParamsConfigParam{Model: param.NullStruct[forward.ModelConfigUnionParam]()}}, `{"budget":null,"config":{"model":null}}`}, + {"replace", forward.SessionUpdateParams{Budget: budget, Config: forward.SessionUpdateParamsConfigParam{Model: model}}, `{"budget":{"type":"limit","max_credit_cost":"100.00"},"config":{"model":{"id":"ultimate","speed":"high","context_window":200000}}}`}, + {"string", forward.SessionUpdateParams{Config: forward.SessionUpdateParamsConfigParam{Model: forward.ModelConfigUnionParam{OfString: forward.String("ultimate")}}}, `{"config":{"model":"ultimate"}}`}, + } { + t.Run(tc.name, func(t *testing.T) { + client := testClient(func(r *http.Request) (*http.Response, error) { + raw, err := io.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + var got, want any + if err = json.Unmarshal(raw, &got); err != nil { + t.Fatal(err) + } + if err = json.Unmarshal([]byte(tc.want), &want); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("got %s want %s", raw, tc.want) + } + return reply(r, 200, tc.want), nil + }) + result, err := client.Sessions.Update(context.Background(), "s", tc.params) + if err != nil { + t.Fatal(err) + } + if tc.name == "clear" && result.JSON.Budget.Raw() != "null" { + t.Fatal("lost explicit null") + } + if tc.name == "omit" && result.JSON.Budget.Raw() != "" { + t.Fatal("lost omission") + } + if tc.name == "replace" && (result.Budget.MaxCreditCost != "100.00" || result.Config.Model.Speed != "high") { + t.Fatalf("result=%+v", result) + } + if tc.name == "string" && result.Config.Model.ID != "ultimate" { + t.Fatal("string model lost") + } + }) + } + metadata := map[string]any{"business": "kept"} + client := testClient(func(r *http.Request) (*http.Response, error) { + var body map[string]any + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Fatal(err) + } + if body["budget"].(map[string]any)["max_credit_cost"] != "100.00" { + t.Fatal(body) + } + if body["metadata"].(map[string]any)["__qca_sdk_created"] != "true" { + t.Fatal(body) + } + if body["config"].(map[string]any)["model"].(map[string]any)["speed"] != "high" { + t.Fatal(body) + } + return reply(r, 200, `{"id":"s"}`), nil + }) + _, err := client.Sessions.New(context.Background(), forward.SessionNewParams{IdentityID: "idn", TemplateID: "tmpl", Budget: budget, Config: forward.SessionNewParamsConfigParam{Model: model}, Metadata: metadata}) + if err != nil { + t.Fatal(err) + } + if len(metadata) != 1 { + t.Fatal("mutated caller metadata") + } +} + +func TestForwardDocumentationEventTypes(t *testing.T) { + for _, tc := range []struct{ decision, policy, reason string }{{"allow", "always_allow", ""}, {"ask", "always_ask", ""}, {"allow", "auto", ""}, {"ask", "auto", "indeterminate"}, {"deny", "auto", "high_risk"}} { + t.Run(tc.decision+tc.policy, func(t *testing.T) { + detail := map[string]any{"type": tc.policy} + if tc.policy == "auto" { + detail["evaluated_permission"] = map[string]any{"type": tc.decision, "reason_code": tc.reason} + } + raw, _ := json.Marshal(map[string]any{"evaluated_permission": tc.decision, "evaluation": detail, "future": 17}) + var e forward.SessionEvent + if err := json.Unmarshal(raw, &e); err != nil { + t.Fatal(err) + } + if e.EvaluatedPermission != tc.decision || e.Evaluation.Type != tc.policy { + t.Fatal(e) + } + if tc.policy == "auto" && (e.Evaluation.EvaluatedPermission.Type != tc.decision || e.Evaluation.EvaluatedPermission.ReasonCode != tc.reason) { + t.Fatal(e.Evaluation) + } + if e.JSON.ExtraFields["future"].Raw() != "17" { + t.Fatal("unknown field lost") + } + }) + } + for _, raw := range []string{`{}`, `{"budget":null}`, `{"budget":{"type":"limit","max_credit_cost":"0.01"}}`} { + var e forward.SessionEvent + if err := json.Unmarshal([]byte(raw), &e); err != nil { + t.Fatal(err) + } + if strings.Contains(raw, "null") && e.JSON.Budget.Raw() != "null" { + t.Fatal("null budget lost") + } + if raw == `{}` && e.JSON.Budget.Raw() != "" { + t.Fatal("missing budget lost") + } + if strings.Contains(raw, "0.01") && e.Budget.MaxCreditCost != "0.01" { + t.Fatal(e.Budget) + } + } + payloads := []string{`{"id":"1","type":"session.usage","model_credits":0,"sandbox_runtime_credits":0,"total_credits":0}`, `{"id":"2","type":"session.usage","model_credits":0.68,"sandbox_runtime_credits":0.04,"total_credits":0.72}`, `{"id":"3","type":"session.status_idle","reason":"outcome_evaluation"}`, `{"id":"4","type":"span.outcome_evaluation_end","iteration":2,"explanation":"done","outcome_evaluation_start_id":"start"}`} + client := testClient(func(r *http.Request) (*http.Response, error) { + if strings.HasSuffix(r.URL.Path, "/stream") { + var b strings.Builder + for i, p := range payloads { + fmt.Fprintf(&b, "id: %d\ndata: %s\n\n", i, p) + } + res := reply(r, 200, b.String()) + res.Header.Set("Content-Type", "text/event-stream") + return res, nil + } + return reply(r, 200, `{"data":[`+strings.Join(payloads, ",")+`],"has_more":false}`), nil + }) + for _, thread := range []bool{false, true} { + var stream *ssestream.Stream[forward.SessionEvent] + if thread { + stream = client.Sessions.Threads.Events.StreamEvents(context.Background(), "s", "t", forward.SessionThreadEventStreamParams{}) + } else { + stream = client.Sessions.Events.StreamEvents(context.Background(), "s", forward.SessionEventStreamParams{}) + } + var got []forward.SessionEvent + for stream.Next() { + got = append(got, stream.Current()) + } + err := stream.Err() + stream.Close() + if err != nil { + t.Fatal(err) + } + if len(got) != 4 || !got[0].JSON.TotalCredits.Valid() || got[1].TotalCredits != 0.72 || got[1].ModelCredits != 0.68 || got[1].SandboxRuntimeCredits != 0.04 || got[2].Reason != "outcome_evaluation" || got[3].Iteration != 2 || got[3].Explanation != "done" || got[3].OutcomeEvaluationStartID != "start" { + t.Fatalf("events=%+v", got) + } + } + page, err := client.Sessions.Events.List(context.Background(), "s", forward.SessionEventListParams{}) + if err != nil { + t.Fatal(err) + } + if page.Data[1].TotalCredits != 0.72 { + t.Fatal("history type differs") + } + var env forward.Environment + if err = json.Unmarshal([]byte(`{"config":{"type":"cloud","startup_script":"echo ready"}}`), &env); err != nil { + t.Fatal(err) + } + if env.Config.StartupScript != "echo ready" { + t.Fatal("missing startup script") + } +} diff --git a/forward/environment.go b/forward/environment.go index c04d8c2..0e72370 100644 --- a/forward/environment.go +++ b/forward/environment.go @@ -211,15 +211,18 @@ func (r Environment) RawJSON() string { return r.JSON.raw } func (r *Environment) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } type EnvironmentConfig struct { - SetupScript string `json:"setup_script"` - Type string `json:"type"` - Packages EnvironmentConfigPackages `json:"packages"` - JSON struct { - SetupScript respjson.Field - Type respjson.Field - Packages respjson.Field - ExtraFields map[string]respjson.Field - raw string + // Runs on sandbox start/resume; self-hosted execution depends on the worker. + StartupScript string `json:"startup_script"` + SetupScript string `json:"setup_script"` + Type string `json:"type"` + Packages EnvironmentConfigPackages `json:"packages"` + JSON struct { + StartupScript respjson.Field + SetupScript respjson.Field + Type respjson.Field + Packages respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } diff --git a/forward/params.go b/forward/params.go index fee34ca..c259018 100644 --- a/forward/params.go +++ b/forward/params.go @@ -35,10 +35,13 @@ func WithIdentityID(identityID string) option.RequestOption { // ModelConfig preserves either a model ID string or an object from the API. // ID is populated for both shapes; RawJSON retains the original representation. type ModelConfig struct { + // Inference speed: standard or high, subject to model capabilities. + Speed string `json:"speed"` ID string `json:"id"` Effort string `json:"effort"` ContextWindow int64 `json:"context_window"` JSON struct { + Speed respjson.Field ID respjson.Field Effort respjson.Field ContextWindow respjson.Field @@ -51,6 +54,8 @@ func (r ModelConfig) RawJSON() string { return r.JSON.raw } func (r *ModelConfig) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } type ModelConfigParam struct { + // Inference speed: standard or high, subject to model capabilities. + Speed param.Opt[string] `json:"speed,omitzero"` ID string `json:"id" api:"required"` Effort param.Opt[string] `json:"effort,omitzero"` ContextWindow param.Opt[int64] `json:"context_window,omitzero"` diff --git a/forward/session.go b/forward/session.go index b8d42fa..fdc6e0a 100644 --- a/forward/session.go +++ b/forward/session.go @@ -104,6 +104,8 @@ func (r *SessionService) New(ctx context.Context, params SessionNewParams, opts } type SessionNewParams struct { + // Omit to keep the default/current budget; use param.NullStruct[SessionBudgetParam]() to clear. + Budget SessionBudgetParam `json:"budget,omitzero" api:"nullable"` // Forward Identity ID. IdentityID string `json:"identity_id" api:"required"` // Forward Template ID. @@ -126,7 +128,9 @@ func (r SessionNewParams) MarshalJSON() ([]byte, error) { func (r *SessionNewParams) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } type SessionNewParamsConfigParam struct { - EnvironmentVariables map[string]any `json:"environment_variables,omitzero"` + // Model override. Omit on create to inherit; explicit null on update restores inheritance. + Model ModelConfigUnionParam `json:"model,omitzero"` + EnvironmentVariables map[string]any `json:"environment_variables,omitzero"` paramObj } @@ -165,6 +169,8 @@ func (r *SessionService) Update(ctx context.Context, sessionID string, params Se } type SessionUpdateParams struct { + // Omit to keep the default/current budget; use param.NullStruct[SessionBudgetParam]() to clear. + Budget SessionBudgetParam `json:"budget,omitzero" api:"nullable"` // New Session title. Title param.Opt[string] `json:"title,omitzero"` // Metadata merge patch; supplied keys overwrite existing keys, omitted keys are kept. @@ -182,7 +188,9 @@ func (r SessionUpdateParams) MarshalJSON() ([]byte, error) { func (r *SessionUpdateParams) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } type SessionUpdateParamsConfigParam struct { - EnvironmentVariables map[string]any `json:"environment_variables,omitzero"` + // Model override. Omit on create to inherit; explicit null on update restores inheritance. + Model ModelConfigUnionParam `json:"model,omitzero"` + EnvironmentVariables map[string]any `json:"environment_variables,omitzero"` paramObj } @@ -243,6 +251,8 @@ func (r SessionCancelParams) URLQuery() (url.Values, error) { } type Session struct { + // Use JSON.Budget to distinguish absent, null (unlimited), and an object. + Budget SessionBudget `json:"budget" api:"nullable"` OutcomeEvaluations []SessionOutcomeEvaluation `json:"outcome_evaluations"` // Session ID. ID string `json:"id"` @@ -266,6 +276,7 @@ type Session struct { CreatedAt time.Time `json:"created_at" format:"date-time"` UpdatedAt time.Time `json:"updated_at" format:"date-time"` JSON struct { + Budget respjson.Field OutcomeEvaluations respjson.Field ID respjson.Field Type respjson.Field @@ -321,8 +332,10 @@ func (r SessionStats) RawJSON() string { return r.JSON.raw } func (r *SessionStats) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } type SessionConfig struct { + Model ModelConfig `json:"model"` EnvironmentVariables map[string]string `json:"environment_variables"` JSON struct { + Model respjson.Field EnvironmentVariables respjson.Field ExtraFields map[string]respjson.Field raw string @@ -377,3 +390,31 @@ func (r SessionOutcomeEvaluation) RawJSON() string { return r.JSON.raw } func (r *SessionOutcomeEvaluation) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +type SessionBudget struct { + Type string `json:"type"` + MaxCreditCost string `json:"max_credit_cost"` + JSON struct { + Type respjson.Field + MaxCreditCost respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r SessionBudget) RawJSON() string { return r.JSON.raw } +func (r *SessionBudget) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +// A cumulative model and sandbox Credits limit. MaxCreditCost is a positive decimal string. +// Once reached, the server rejects the next user message; an active turn can finish. +type SessionBudgetParam struct { + Type string `json:"type" api:"required"` + MaxCreditCost string `json:"max_credit_cost" api:"required"` + paramObj +} + +func (r SessionBudgetParam) MarshalJSON() ([]byte, error) { + type shadow SessionBudgetParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *SessionBudgetParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } diff --git a/forward/sessionevent.go b/forward/sessionevent.go index d754e17..2d6f293 100644 --- a/forward/sessionevent.go +++ b/forward/sessionevent.go @@ -139,6 +139,24 @@ func (r SessionEventStreamParams) URLQuery() (url.Values, error) { } type SessionEvent struct { + // Cumulative total Credits. + TotalCredits float64 `json:"total_credits"` + // Cumulative sandbox Credits. + SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"` + // Cumulative model Credits; use the latest value, not a sum of events. + ModelCredits float64 `json:"model_credits"` + // Related outcome evaluation start event. + OutcomeEvaluationStartID string `json:"outcome_evaluation_start_id"` + // Outcome evaluation explanation. + Explanation string `json:"explanation"` + // Outcome evaluation iteration. + Iteration int64 `json:"iteration"` + // Optional status reason, such as outcome_evaluation. + Reason string `json:"reason"` + // Optional approval details; evaluated_permission at the event root is a string. + Evaluation ToolApprovalEvaluation `json:"evaluation"` + // Use JSON.Budget to distinguish absent, null (unlimited), and an object. + Budget SessionBudget `json:"budget" api:"nullable"` ID string `json:"id"` Type string `json:"type"` SessionID string `json:"session_id"` @@ -170,7 +188,7 @@ type SessionEvent struct { Usage map[string]any `json:"usage"` StopReason map[string]any `json:"stop_reason"` Error map[string]any `json:"error"` - EvaluatedPermission map[string]any `json:"evaluated_permission"` + EvaluatedPermission string `json:"evaluated_permission"` FileID string `json:"file_id"` OriginalFilename string `json:"original_filename"` Size int64 `json:"size"` @@ -181,49 +199,58 @@ type SessionEvent struct { ModelRequestStartID string `json:"model_request_start_id"` ModelUsage map[string]any `json:"model_usage"` JSON struct { - ID respjson.Field - Type respjson.Field - SessionID respjson.Field - Content respjson.Field - ProcessedAt respjson.Field - SessionThreadID respjson.Field - Thinking respjson.Field - Text respjson.Field - ToolUseID respjson.Field - CustomToolUseID respjson.Field - MCPToolUseID respjson.Field - Name respjson.Field - MCPServerName respjson.Field - Input respjson.Field - IsError respjson.Field - Result respjson.Field - DenyMessage respjson.Field - Description respjson.Field - Rubric respjson.Field - OutcomeID respjson.Field - MaxIterations respjson.Field - MessageID respjson.Field - Message respjson.Field - Index respjson.Field - ContentBlock respjson.Field - Delta respjson.Field - Event respjson.Field - EventID respjson.Field - Usage respjson.Field - StopReason respjson.Field - Error respjson.Field - EvaluatedPermission respjson.Field - FileID respjson.Field - OriginalFilename respjson.Field - Size respjson.Field - ContentType respjson.Field - Agent respjson.Field - Metadata respjson.Field - Title respjson.Field - ModelRequestStartID respjson.Field - ModelUsage respjson.Field - ExtraFields map[string]respjson.Field - raw string + TotalCredits respjson.Field + SandboxRuntimeCredits respjson.Field + ModelCredits respjson.Field + OutcomeEvaluationStartID respjson.Field + Explanation respjson.Field + Iteration respjson.Field + Reason respjson.Field + Evaluation respjson.Field + Budget respjson.Field + ID respjson.Field + Type respjson.Field + SessionID respjson.Field + Content respjson.Field + ProcessedAt respjson.Field + SessionThreadID respjson.Field + Thinking respjson.Field + Text respjson.Field + ToolUseID respjson.Field + CustomToolUseID respjson.Field + MCPToolUseID respjson.Field + Name respjson.Field + MCPServerName respjson.Field + Input respjson.Field + IsError respjson.Field + Result respjson.Field + DenyMessage respjson.Field + Description respjson.Field + Rubric respjson.Field + OutcomeID respjson.Field + MaxIterations respjson.Field + MessageID respjson.Field + Message respjson.Field + Index respjson.Field + ContentBlock respjson.Field + Delta respjson.Field + Event respjson.Field + EventID respjson.Field + Usage respjson.Field + StopReason respjson.Field + Error respjson.Field + EvaluatedPermission respjson.Field + FileID respjson.Field + OriginalFilename respjson.Field + Size respjson.Field + ContentType respjson.Field + Agent respjson.Field + Metadata respjson.Field + Title respjson.Field + ModelRequestStartID respjson.Field + ModelUsage respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } @@ -243,3 +270,35 @@ func (r SessionEventSendResponse) RawJSON() string { return r.JSON.raw } func (r *SessionEventSendResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +type ToolApprovalEvaluation struct { + Type string `json:"type"` + EvaluatedPermission ToolApprovalDecision `json:"evaluated_permission"` + JSON struct { + Type respjson.Field + EvaluatedPermission respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r ToolApprovalEvaluation) RawJSON() string { return r.JSON.raw } +func (r *ToolApprovalEvaluation) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type ToolApprovalDecision struct { + Type string `json:"type"` + ReasonCode string `json:"reason_code"` + JSON struct { + Type respjson.Field + ReasonCode respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r ToolApprovalDecision) RawJSON() string { return r.JSON.raw } +func (r *ToolApprovalDecision) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} diff --git a/managed/documentation_alignment_test.go b/managed/documentation_alignment_test.go new file mode 100644 index 0000000..32e8f00 --- /dev/null +++ b/managed/documentation_alignment_test.go @@ -0,0 +1,87 @@ +package managed_test + +import ( + "encoding/json" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/managed" + "testing" +) + +// Official Memory Store, Vault and Environment schemas, retrieved 2026-11-03. +func TestManagedDocumentationResponses(t *testing.T) { + var memory managed.ManagedAgentsMemory + raw := []byte(`{"type":"memory","version":2,"metadata":{"source":"test"}}`) + if err := json.Unmarshal(raw, &memory); err != nil { + t.Fatal(err) + } + if memory.Version != 2 || !memory.JSON.Version.Valid() { + t.Fatal(memory) + } + var item managed.ManagedAgentsMemoryListItemUnion + if err := json.Unmarshal(raw, &item); err != nil { + t.Fatal(err) + } + if item.Version != 2 || item.Metadata["source"] != "test" || item.AsMemory().Version != 2 { + t.Fatal(item) + } + for _, redacted := range []string{"false", "true"} { + var version managed.ManagedAgentsMemoryVersion + if err := json.Unmarshal([]byte(`{"operation":"updated","redacted":`+redacted+`}`), &version); err != nil { + t.Fatal(err) + } + if version.Operation != managed.ManagedAgentsMemoryVersionOperationUpdated || !version.JSON.Redacted.Valid() || version.Redacted != (redacted == "true") { + t.Fatal(version) + } + } + var vault managed.ManagedAgentsVault + if err := json.Unmarshal([]byte(`{"credentials":[]}`), &vault); err != nil { + t.Fatal(err) + } + if !vault.JSON.Credentials.Valid() || vault.Credentials == nil || len(vault.Credentials) != 0 { + t.Fatal(vault) + } + for _, id := range []string{`"ultimate"`, `{"id":"ultimate","effort":"none","context_window":200000,"effective_context_window":180000}`} { + var thread managed.ManagedAgentsSessionThreadAgentUnionModel + var coordinator managed.ManagedAgentsSessionMultiagentCoordinatorAgentUnionModel + if err := json.Unmarshal([]byte(id), &thread); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal([]byte(id), &coordinator); err != nil { + t.Fatal(err) + } + if id[0] == '{' && (thread.Effort != "none" || thread.ContextWindow != 200000 || thread.EffectiveContextWindow != 180000 || coordinator.Effort != "none" || coordinator.ContextWindow != 200000 || coordinator.EffectiveContextWindow != 180000) { + t.Fatal(thread, coordinator) + } + if id[0] == '"' && (thread.OfString != "ultimate" || coordinator.OfString != "ultimate") { + t.Fatal("string variant lost") + } + } + for _, kind := range []string{"cloud", "self_hosted"} { + var env managed.Environment + if err := json.Unmarshal([]byte(`{"config":{"type":"`+kind+`","startup_script":"echo ready"}}`), &env); err != nil { + t.Fatal(err) + } + if env.Config.StartupScript != "echo ready" { + t.Fatal(env) + } + } + for _, config := range []any{managed.CloudConfigParams{StartupScript: managed.String("echo ready")}, managed.SelfHostedConfigParams{StartupScript: managed.String("echo ready")}} { + raw, err := json.Marshal(config) + if err != nil { + t.Fatal(err) + } + var fields map[string]any + if err = json.Unmarshal(raw, &fields); err != nil { + t.Fatal(err) + } + if fields["startup_script"] != "echo ready" { + t.Fatal(string(raw)) + } + } + var thread managed.ManagedAgentsSessionThread + if err := json.Unmarshal([]byte(`{"id":"t"}`), &thread); err != nil { + t.Fatal(err) + } + if thread.JSON.Usage.Valid() { + t.Fatal("usage must be optional") + } +} diff --git a/managed/environment.go b/managed/environment.go index be27f34..27be334 100644 --- a/managed/environment.go +++ b/managed/environment.go @@ -148,8 +148,10 @@ func (r *EnvironmentService) Archive(ctx context.Context, environmentID string, // `cloud` environment configuration. type CloudConfig struct { // Set Betas to include "oss-bucket-2026-09-23" when buckets remain configured. - Storage BucketStorage `json:"storage"` - SetupScript string `json:"setup_script"` + Storage BucketStorage `json:"storage"` + // Runs on sandbox start/resume; self-hosted execution depends on the worker. + StartupScript string `json:"startup_script"` + SetupScript string `json:"setup_script"` // Network configuration policy. Networking CloudConfigNetworkingUnion `json:"networking" api:"required"` // Package manager configuration. @@ -158,13 +160,14 @@ type CloudConfig struct { Type constant.Cloud `json:"type" default:"cloud"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - Storage respjson.Field - SetupScript respjson.Field - Networking respjson.Field - Packages respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string + Storage respjson.Field + StartupScript respjson.Field + SetupScript respjson.Field + Networking respjson.Field + Packages respjson.Field + Type respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } @@ -250,8 +253,10 @@ func (r *CloudConfigNetworkingUnion) UnmarshalJSON(data []byte) error { // The property Type is required. type CloudConfigParams struct { // Set Betas to include "oss-bucket-2026-09-23" when buckets remain configured. - Storage BucketStorageParam `json:"storage,omitzero"` - SetupScript param.Opt[string] `json:"setup_script,omitzero"` + Storage BucketStorageParam `json:"storage,omitzero"` + // Runs on sandbox start/resume; self-hosted execution depends on the worker. + StartupScript param.Opt[string] `json:"startup_script,omitzero"` + SetupScript param.Opt[string] `json:"setup_script,omitzero"` // Network configuration policy. Omit on update to preserve the existing value. Networking CloudConfigParamsNetworkingUnion `json:"networking,omitzero"` // Specify packages (and optionally their versions) available in this environment. @@ -401,8 +406,10 @@ func (r *Environment) UnmarshalJSON(data []byte) error { // Use the methods beginning with 'As' to cast the union to one of its variants. type EnvironmentConfigUnion struct { // Set Betas to include "oss-bucket-2026-09-23" when buckets remain configured. - Storage BucketStorage `json:"storage"` - SetupScript string `json:"setup_script"` + Storage BucketStorage `json:"storage"` + // Runs on sandbox start/resume; self-hosted execution depends on the worker. + StartupScript string `json:"startup_script"` + SetupScript string `json:"setup_script"` // This field is from variant [CloudConfig]. Networking CloudConfigNetworkingUnion `json:"networking"` // This field is from variant [CloudConfig]. @@ -410,12 +417,13 @@ type EnvironmentConfigUnion struct { // Any of "cloud", "self_hosted". Type string `json:"type"` JSON struct { - Storage respjson.Field - SetupScript respjson.Field - Networking respjson.Field - Packages respjson.Field - Type respjson.Field - raw string + Storage respjson.Field + StartupScript respjson.Field + SetupScript respjson.Field + Networking respjson.Field + Packages respjson.Field + Type respjson.Field + raw string } `json:"-"` } @@ -652,15 +660,18 @@ const ( // Configuration for self-hosted environments. type SelfHostedConfig struct { - SetupScript string `json:"setup_script"` + // Runs on sandbox start/resume; self-hosted execution depends on the worker. + StartupScript string `json:"startup_script"` + SetupScript string `json:"setup_script"` // Environment type Type constant.SelfHosted `json:"type" default:"self_hosted"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - SetupScript respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string + StartupScript respjson.Field + SetupScript respjson.Field + Type respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } @@ -681,7 +692,9 @@ func NewSelfHostedConfigParams() SelfHostedConfigParams { // This struct has a constant value, construct it with // [NewSelfHostedConfigParams]. type SelfHostedConfigParams struct { - SetupScript param.Opt[string] `json:"setup_script,omitzero"` + // Runs on sandbox start/resume; self-hosted execution depends on the worker. + StartupScript param.Opt[string] `json:"startup_script,omitzero"` + SetupScript param.Opt[string] `json:"setup_script,omitzero"` // Environment type Type constant.SelfHosted `json:"type" default:"self_hosted"` paramObj @@ -1052,3 +1065,21 @@ func (r EnvironmentSearchParams) MarshalJSON() ([]byte, error) { func (r *EnvironmentSearchParams) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +func (u EnvironmentNewParamsConfigUnion) GetStartupScript() *string { + if v := u.OfCloud; v != nil && v.StartupScript.Valid() { + return &v.StartupScript.Value + } else if v := u.OfSelfHosted; v != nil && v.StartupScript.Valid() { + return &v.StartupScript.Value + } + return nil +} + +func (u EnvironmentUpdateParamsConfigUnion) GetStartupScript() *string { + if v := u.OfCloud; v != nil && v.StartupScript.Valid() { + return &v.StartupScript.Value + } else if v := u.OfSelfHosted; v != nil && v.StartupScript.Valid() { + return &v.StartupScript.Value + } + return nil +} diff --git a/managed/memorystorememory.go b/managed/memorystorememory.go index a6c0b49..82e0aaf 100644 --- a/managed/memorystorememory.go +++ b/managed/memorystorememory.go @@ -184,6 +184,8 @@ const ( // addressed by their `mem_...` ID; the path is the create key and can be changed // via update. type ManagedAgentsMemory struct { + // Current memory version, starting at 1. Absent on prefix entries. + Version int64 `json:"version"` Metadata map[string]string `json:"metadata"` // Unique identifier for this memory (a `mem_...` value). Stable across renames; @@ -220,6 +222,7 @@ type ManagedAgentsMemory struct { Content string `json:"content" api:"nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { + Version respjson.Field Metadata respjson.Field ID respjson.Field @@ -257,6 +260,9 @@ const ( // // Use the methods beginning with 'As' to cast the union to one of its variants. type ManagedAgentsMemoryListItemUnion struct { + Metadata map[string]string `json:"metadata"` + // Current memory version, starting at 1. Absent on prefix entries. + Version int64 `json:"version"` // This field is from variant [ManagedAgentsMemory]. ID string `json:"id"` // This field is from variant [ManagedAgentsMemory]. @@ -277,6 +283,8 @@ type ManagedAgentsMemoryListItemUnion struct { // This field is from variant [ManagedAgentsMemory]. Content string `json:"content"` JSON struct { + Metadata respjson.Field + Version respjson.Field ID respjson.Field ContentSha256 respjson.Field ContentSizeBytes respjson.Field diff --git a/managed/memorystorememoryversion.go b/managed/memorystorememoryversion.go index 4f36164..489c395 100644 --- a/managed/memorystorememoryversion.go +++ b/managed/memorystorememoryversion.go @@ -239,6 +239,7 @@ const ( // `content_size_bytes`, and `content_sha256` set to `null`; branch on // `redacted_at`, not HTTP status. type ManagedAgentsMemoryVersion struct { + Redacted bool `json:"redacted"` // Unique identifier for this version (a `memver_...` value). ID string `json:"id" api:"required"` // A timestamp in RFC 3339 format @@ -254,7 +255,7 @@ type ManagedAgentsMemoryVersion struct { // The kind of mutation a `memory_version` records. Every non-no-op mutation to a // memory appends exactly one version row with one of these values. // - // Any of "created", "modified", "deleted". + // Any of "created", "updated", "deleted". Operation ManagedAgentsMemoryVersionOperation `json:"operation" api:"required"` // Any of "memory_version". Type ManagedAgentsMemoryVersionType `json:"type" api:"required"` @@ -287,6 +288,7 @@ type ManagedAgentsMemoryVersion struct { RedactedBy ManagedAgentsActorUnion `json:"redacted_by"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { + Redacted respjson.Field ID respjson.Field CreatedAt respjson.Field MemoryID respjson.Field @@ -322,7 +324,9 @@ const ( type ManagedAgentsMemoryVersionOperation string const ( - ManagedAgentsMemoryVersionOperationCreated ManagedAgentsMemoryVersionOperation = "created" + ManagedAgentsMemoryVersionOperationCreated ManagedAgentsMemoryVersionOperation = "created" + ManagedAgentsMemoryVersionOperationUpdated ManagedAgentsMemoryVersionOperation = "updated" + // Deprecated: current API responses use Updated. Retained for legacy payloads. ManagedAgentsMemoryVersionOperationModified ManagedAgentsMemoryVersionOperation = "modified" ManagedAgentsMemoryVersionOperationDeleted ManagedAgentsMemoryVersionOperation = "deleted" ) @@ -444,7 +448,7 @@ type MemoryStoreMemoryVersionListParams struct { SessionID param.Opt[string] `query:"session_id,omitzero" json:"-"` // Query parameter for operation // - // Any of "created", "modified", "deleted". + // Any of "created", "updated", "deleted". Operation ManagedAgentsMemoryVersionOperation `query:"operation,omitzero" json:"-"` // Query parameter for view // diff --git a/managed/session.go b/managed/session.go index 3c9df5f..bf6d2b1 100644 --- a/managed/session.go +++ b/managed/session.go @@ -1918,23 +1918,28 @@ func (r *ManagedAgentsSessionMultiagentCoordinatorAgentUnion) UnmarshalJSON(data // If the underlying value is not a json object, one of the following properties // will be valid: OfString] type ManagedAgentsSessionMultiagentCoordinatorAgentUnionModel struct { + EffectiveContextWindow int64 `json:"effective_context_window"` + ContextWindow int64 `json:"context_window"` // This field will be present if the value is a [string] instead of an object. OfString string `json:",inline"` // This field is from variant [ManagedAgentsModelConfig]. ID ManagedAgentsModel `json:"id"` // This field is from variant [ManagedAgentsModelConfig]. - Effort ManagedAgentsModelConfigEffortUnion `json:"effort"` + Effort string `json:"effort"` // This field is from variant [ManagedAgentsModelConfig]. InferenceGeo string `json:"inference_geo"` // This field is from variant [ManagedAgentsModelConfig]. + // Deprecated: speed is no longer part of Managed model configuration. Speed ManagedAgentsModelConfigSpeed `json:"speed"` JSON struct { - OfString respjson.Field - ID respjson.Field - Effort respjson.Field - InferenceGeo respjson.Field - Speed respjson.Field - raw string + EffectiveContextWindow respjson.Field + ContextWindow respjson.Field + OfString respjson.Field + ID respjson.Field + Effort respjson.Field + InferenceGeo respjson.Field + Speed respjson.Field + raw string } `json:"-"` } diff --git a/managed/sessionthread.go b/managed/sessionthread.go index 5d646aa..9896b85 100644 --- a/managed/sessionthread.go +++ b/managed/sessionthread.go @@ -137,8 +137,8 @@ type ManagedAgentsSessionThread struct { Type ManagedAgentsSessionThreadType `json:"type" api:"required"` // A timestamp in RFC 3339 format UpdatedAt time.Time `json:"updated_at" api:"required" format:"date-time"` - // Cumulative token usage for a session thread across all turns. - Usage ManagedAgentsSessionThreadUsage `json:"usage" api:"required"` + // Deprecated: current Thread responses do not include usage. Read Session Credits instead. + Usage ManagedAgentsSessionThreadUsage `json:"usage"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { ID respjson.Field @@ -262,23 +262,28 @@ func (r *ManagedAgentsSessionThreadAgentUnion) UnmarshalJSON(data []byte) error // If the underlying value is not a json object, one of the following properties // will be valid: OfString] type ManagedAgentsSessionThreadAgentUnionModel struct { + EffectiveContextWindow int64 `json:"effective_context_window"` + ContextWindow int64 `json:"context_window"` // This field will be present if the value is a [string] instead of an object. OfString string `json:",inline"` // This field is from variant [ManagedAgentsModelConfig]. ID ManagedAgentsModel `json:"id"` // This field is from variant [ManagedAgentsModelConfig]. - Effort ManagedAgentsModelConfigEffortUnion `json:"effort"` + Effort string `json:"effort"` // This field is from variant [ManagedAgentsModelConfig]. InferenceGeo string `json:"inference_geo"` // This field is from variant [ManagedAgentsModelConfig]. + // Deprecated: speed is no longer part of Managed model configuration. Speed ManagedAgentsModelConfigSpeed `json:"speed"` JSON struct { - OfString respjson.Field - ID respjson.Field - Effort respjson.Field - InferenceGeo respjson.Field - Speed respjson.Field - raw string + EffectiveContextWindow respjson.Field + ContextWindow respjson.Field + OfString respjson.Field + ID respjson.Field + Effort respjson.Field + InferenceGeo respjson.Field + Speed respjson.Field + raw string } `json:"-"` } @@ -329,7 +334,7 @@ const ( ManagedAgentsSessionThreadStatusTerminated ManagedAgentsSessionThreadStatus = "terminated" ) -// Cumulative token usage for a session thread across all turns. +// Deprecated: current Thread responses do not include usage. Read Session Credits instead. type ManagedAgentsSessionThreadUsage struct { // Cumulative time in seconds this thread spent in running status. Equal to // `stats.active_seconds`; surfaced here so a thread's usage carries every quantity diff --git a/managed/vault.go b/managed/vault.go index 2085990..912579d 100644 --- a/managed/vault.go +++ b/managed/vault.go @@ -155,6 +155,8 @@ const ( // A vault that stores credentials for use by agents during sessions. type ManagedAgentsVault struct { + // Empty on creation; add credentials using the separate Credential API. + Credentials []ManagedAgentsCredential `json:"credentials"` // Unique identifier for the vault. ID string `json:"id" api:"required"` // A timestamp in RFC 3339 format @@ -171,6 +173,7 @@ type ManagedAgentsVault struct { UpdatedAt time.Time `json:"updated_at" api:"required" format:"date-time"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { + Credentials respjson.Field ID respjson.Field ArchivedAt respjson.Field CreatedAt respjson.Field From 0eb3cd3ca13fb18eac987729c0e3a8389c4ea5fd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 16:51:29 +0800 Subject: [PATCH 2/2] docs(environments): explain startup script constraints and failures --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 7ae46b3..17fd9f4 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ The SDK ships two clients that share authentication, transport, retries, error h - [Requirements](#requirements) - [Usage](#usage) - [PAT and Service Account authentication](#pat-and-service-account-authentication) +- [Environment startup scripts](#environment-startup-scripts) - [Streaming](#streaming) - [Request fields](#request-fields) - [Response objects](#response-objects) @@ -160,6 +161,22 @@ cleanup verification, run `make test-live-sa-execution` with independent; `QODER_AUTH_MODE` switches examples, not the PAT test suites. See the [official authentication contract](https://docs.qoder.com/cloud-agents/api/conventions/authentication). +## Environment startup scripts + +Both Forward and Managed accept `config.startup_script`. For cloud environments, +it runs after `setup_script` on the first sandbox start and again on every resume, +before the first tool call. The server enforces a 64 KB limit and a 5-minute +timeout. Scripts must exit; start long-lived services in the background and wait +for readiness when needed. + +Failure emits a `session.error` event whose `error.type` is +`environment_startup_failed_error`; the session and sandbox remain usable. +The script is not retried during the same start. For self-hosted environments, +the API stores and returns the script; execution depends on the external worker. +See the official [Forward](https://docs.qoder.com/cloud-agents/api/forward/environments/schemas#environment-startup-script) +and [Managed](https://docs.qoder.com/cloud-agents/api/environments/schemas#environment-startup-script) +contracts. + ## Streaming Streaming responses are consumed incrementally. Close the stream when finished and handle interruption explicitly; closing a local stream does not cancel server-side work.