From 668e89b9ec11b8b2badf995d978497fa1112ed6e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 20 Oct 2026 14:55:57 +0800
Subject: [PATCH] fix(api)!: align credits, ownership, and pairing contracts
Match QCA Session budget and usage schemas, route Forward identity ownership through query parameters, and support pairing by record ID. Include wire contract tests, generated references, and migration notes.
BREAKING CHANGE: replace max_list_cost currency objects with max_credit_cost decimal strings.
---
CHANGELOG.md | 14 +
docs/api/reference.md | 320 +++++++++++++++++-----
forward/channelpairing.go | 34 ++-
forward/environment.go | 14 +
forward/file.go | 8 +
forward/identity_pairing_contract_test.go | 245 +++++++++++++++++
forward/params.go | 8 +
forward/session.go | 14 +-
forward/skill.go | 16 ++
forward/skillversion.go | 8 +
forward/vault.go | 8 +
forward/vaultcredential.go | 8 +
managed/deployment.go | 12 +-
managed/qoder_types_test.go | 5 +
managed/session.go | 105 ++++---
managed/session_credit_contract_test.go | 124 +++++++++
managed/sessionevent.go | 41 ++-
managed/sessionthread.go | 10 +-
18 files changed, 845 insertions(+), 149 deletions(-)
create mode 100644 forward/identity_pairing_contract_test.go
create mode 100644 managed/session_credit_contract_test.go
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 13f2070..61ac64d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,20 @@ existing `0.1.0` release; earlier development prereleases are not listed.
## [Unreleased]
+### Fixed
+
+- Align Managed Session budgets with the official decimal-string `max_credit_cost` contract, shared by Deployment budgets. The limit covers cumulative model and sandbox Credits; reaching it rejects the next user message without interrupting the current turn.
+- Expose Managed and Forward Session `model_credits`, `sandbox_runtime_credits`, and `total_credits`; decode top-level `session.usage` Credits and per-call `model_usage.credits` / compaction `usage.credits`. Session usage may be absent. Legacy response token/list-cost fields remain available.
+- Route Identity ownership through query parameters on existing Forward Environment, Skill/Version, Vault/Credential, and File operations, including multipart uploads and content downloads.
+- Allow existing channel pairing creation with either a code or `channel_pairing_id`, and expose pairing scope details. No additional API endpoints are introduced.
+
+### Migration
+
+- **Breaking budget field change:** Replace `MaxListCost: managed.MonetaryAmountParam{...}` with `MaxCreditCost: "100.00"` in `ManagedAgentsBudgetLimitParam` (keep `Type: "limit"`). Amounts are positive decimal strings, not currency objects. Omitting a budget update preserves the limit; explicit null removes it.
+- Consume the three top-level Credits fields from `session.usage` events. Replace cumulative snapshots rather than adding them; compaction Credits are already included in Session totals.
+- Set `IdentityID: forward.String("idn_...")` in resource params; for methods without params, pass `forward.WithIdentityID("idn_...")`. Use this only with a PAT. With a SAT, omit `identity_id`; the server infers ownership from the token and rejects explicit values (including an empty string).
+- Channel pairing requests must provide exactly one of `code` or `channel_pairing_id`; existing code-based calls remain supported.
+
## [0.6.0]
### Added
diff --git a/docs/api/reference.md b/docs/api/reference.md
index 3d62b28..0cfc698 100644
--- a/docs/api/reference.md
+++ b/docs/api/reference.md
@@ -13,6 +13,7 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward"
- [func Float\(v float64\) param.Opt\[float64\]](<#Float>)
- [func Int\(v int64\) param.Opt\[int64\]](<#Int>)
- [func String\(v string\) param.Opt\[string\]](<#String>)
+- [func WithIdentityID\(identityID string\) option.RequestOption](<#WithIdentityID>)
- [type Batch](<#Batch>)
- [func \(r Batch\) RawJSON\(\) string](<#Batch.RawJSON>)
- [func \(r \*Batch\) UnmarshalJSON\(data \[\]byte\) error](<#Batch.UnmarshalJSON>)
@@ -209,6 +210,7 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward"
- [func \(r EnvironmentListParams\) URLQuery\(\) \(url.Values, error\)](<#EnvironmentListParams.URLQuery>)
- [type EnvironmentNewParams](<#EnvironmentNewParams>)
- [func \(r EnvironmentNewParams\) MarshalJSON\(\) \(\[\]byte, error\)](<#EnvironmentNewParams.MarshalJSON>)
+ - [func \(r EnvironmentNewParams\) URLQuery\(\) \(url.Values, error\)](<#EnvironmentNewParams.URLQuery>)
- [func \(r \*EnvironmentNewParams\) UnmarshalJSON\(data \[\]byte\) error](<#EnvironmentNewParams.UnmarshalJSON>)
- [type EnvironmentService](<#EnvironmentService>)
- [func NewEnvironmentService\(opts ...option.RequestOption\) EnvironmentService](<#NewEnvironmentService>)
@@ -221,6 +223,7 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward"
- [func \(r \*EnvironmentService\) Update\(ctx context.Context, id string, params EnvironmentUpdateParams, opts ...option.RequestOption\) \(res \*Environment, err error\)](<#EnvironmentService.Update>)
- [type EnvironmentUpdateParams](<#EnvironmentUpdateParams>)
- [func \(r EnvironmentUpdateParams\) MarshalJSON\(\) \(\[\]byte, error\)](<#EnvironmentUpdateParams.MarshalJSON>)
+ - [func \(r EnvironmentUpdateParams\) URLQuery\(\) \(url.Values, error\)](<#EnvironmentUpdateParams.URLQuery>)
- [func \(r \*EnvironmentUpdateParams\) UnmarshalJSON\(data \[\]byte\) error](<#EnvironmentUpdateParams.UnmarshalJSON>)
- [type EnvironmentVariableOverride](<#EnvironmentVariableOverride>)
- [func \(r EnvironmentVariableOverride\) RawJSON\(\) string](<#EnvironmentVariableOverride.RawJSON>)
@@ -253,6 +256,7 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward"
- [func \(r \*FileService\) Upload\(ctx context.Context, params FileUploadParams, opts ...option.RequestOption\) \(res \*FileMetadata, err error\)](<#FileService.Upload>)
- [type FileUploadParams](<#FileUploadParams>)
- [func \(r FileUploadParams\) MarshalMultipart\(\) \(\[\]byte, string, error\)](<#FileUploadParams.MarshalMultipart>)
+ - [func \(r FileUploadParams\) URLQuery\(\) \(url.Values, error\)](<#FileUploadParams.URLQuery>)
- [type GitHubRepository](<#GitHubRepository>)
- [func \(r GitHubRepository\) RawJSON\(\) string](<#GitHubRepository.RawJSON>)
- [func \(r \*GitHubRepository\) UnmarshalJSON\(data \[\]byte\) error](<#GitHubRepository.UnmarshalJSON>)
@@ -761,6 +765,7 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward"
- [func \(r SkillListParams\) URLQuery\(\) \(url.Values, error\)](<#SkillListParams.URLQuery>)
- [type SkillNewParams](<#SkillNewParams>)
- [func \(r SkillNewParams\) MarshalMultipart\(\) \(\[\]byte, string, error\)](<#SkillNewParams.MarshalMultipart>)
+ - [func \(r SkillNewParams\) URLQuery\(\) \(url.Values, error\)](<#SkillNewParams.URLQuery>)
- [type SkillOverride](<#SkillOverride>)
- [func \(r SkillOverride\) RawJSON\(\) string](<#SkillOverride.RawJSON>)
- [func \(r \*SkillOverride\) UnmarshalJSON\(data \[\]byte\) error](<#SkillOverride.UnmarshalJSON>)
@@ -777,6 +782,7 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward"
- [func \(r \*SkillService\) Update\(ctx context.Context, id string, params SkillUpdateParams, opts ...option.RequestOption\) \(res \*Skill, err error\)](<#SkillService.Update>)
- [type SkillUpdateParams](<#SkillUpdateParams>)
- [func \(r SkillUpdateParams\) MarshalJSON\(\) \(\[\]byte, error\)](<#SkillUpdateParams.MarshalJSON>)
+ - [func \(r SkillUpdateParams\) URLQuery\(\) \(url.Values, error\)](<#SkillUpdateParams.URLQuery>)
- [func \(r \*SkillUpdateParams\) UnmarshalJSON\(data \[\]byte\) error](<#SkillUpdateParams.UnmarshalJSON>)
- [type SkillVersion](<#SkillVersion>)
- [func \(r SkillVersion\) RawJSON\(\) string](<#SkillVersion.RawJSON>)
@@ -785,6 +791,7 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward"
- [func \(r SkillVersionListParams\) URLQuery\(\) \(url.Values, error\)](<#SkillVersionListParams.URLQuery>)
- [type SkillVersionNewParams](<#SkillVersionNewParams>)
- [func \(r SkillVersionNewParams\) MarshalMultipart\(\) \(\[\]byte, string, error\)](<#SkillVersionNewParams.MarshalMultipart>)
+ - [func \(r SkillVersionNewParams\) URLQuery\(\) \(url.Values, error\)](<#SkillVersionNewParams.URLQuery>)
- [type SkillVersionService](<#SkillVersionService>)
- [func NewSkillVersionService\(opts ...option.RequestOption\) SkillVersionService](<#NewSkillVersionService>)
- [func \(r \*SkillVersionService\) Delete\(ctx context.Context, id string, version string, opts ...option.RequestOption\) \(res \*DeletedSkillVersion, err error\)](<#SkillVersionService.Delete>)
@@ -870,6 +877,7 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward"
- [func \(r VaultCredentialListParams\) URLQuery\(\) \(url.Values, error\)](<#VaultCredentialListParams.URLQuery>)
- [type VaultCredentialNewParams](<#VaultCredentialNewParams>)
- [func \(r VaultCredentialNewParams\) MarshalJSON\(\) \(\[\]byte, error\)](<#VaultCredentialNewParams.MarshalJSON>)
+ - [func \(r VaultCredentialNewParams\) URLQuery\(\) \(url.Values, error\)](<#VaultCredentialNewParams.URLQuery>)
- [func \(r \*VaultCredentialNewParams\) UnmarshalJSON\(data \[\]byte\) error](<#VaultCredentialNewParams.UnmarshalJSON>)
- [type VaultCredentialService](<#VaultCredentialService>)
- [func NewVaultCredentialService\(opts ...option.RequestOption\) VaultCredentialService](<#NewVaultCredentialService>)
@@ -890,6 +898,7 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward"
- [func \(r VaultListParams\) URLQuery\(\) \(url.Values, error\)](<#VaultListParams.URLQuery>)
- [type VaultNewParams](<#VaultNewParams>)
- [func \(r VaultNewParams\) MarshalJSON\(\) \(\[\]byte, error\)](<#VaultNewParams.MarshalJSON>)
+ - [func \(r VaultNewParams\) URLQuery\(\) \(url.Values, error\)](<#VaultNewParams.URLQuery>)
- [func \(r \*VaultNewParams\) UnmarshalJSON\(data \[\]byte\) error](<#VaultNewParams.UnmarshalJSON>)
- [type VaultService](<#VaultService>)
- [func NewVaultService\(opts ...option.RequestOption\) VaultService](<#NewVaultService>)
@@ -956,6 +965,15 @@ func String(v string) param.Opt[string]
+
+## func [WithIdentityID]()
+
+```go
+func WithIdentityID(identityID string) option.RequestOption
+```
+
+WithIdentityID selects identity ownership for Forward resources using a PAT. Omit this option when using a SAT; its identity is inferred by the server. Use it on retrieval, deletion, archive, and download methods without params.
+
## type [Batch]()
@@ -1821,6 +1839,11 @@ func (r *ChannelNewParams) UnmarshalJSON(data []byte) error
```go
type ChannelPairing struct {
+ ScopeType string `json:"scope_type"`
+ ScopeExternalID string `json:"scope_external_id"`
+ ScopeDisplayName string `json:"scope_display_name"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
// Pairing ID, used when removing the pairing.
ID string `json:"id"`
// Always `channel_pairing`.
@@ -1836,14 +1859,19 @@ type ChannelPairing struct {
// Time the pairing completed.
PairedAt string `json:"paired_at"`
JSON struct {
- ID respjson.Field
- Type respjson.Field
- ChannelID respjson.Field
- IdentityID respjson.Field
- TemplateID respjson.Field
- Status respjson.Field
- PairedAt respjson.Field
- ExtraFields map[string]respjson.Field
+ ScopeType respjson.Field
+ ScopeExternalID respjson.Field
+ ScopeDisplayName respjson.Field
+ CreatedAt respjson.Field
+ UpdatedAt respjson.Field
+ ID respjson.Field
+ Type respjson.Field
+ ChannelID respjson.Field
+ IdentityID respjson.Field
+ TemplateID respjson.Field
+ Status respjson.Field
+ PairedAt respjson.Field
+ ExtraFields map[string]respjson.Field
// contains filtered or unexported fields
} `json:"-"`
}
@@ -1874,8 +1902,10 @@ func (r *ChannelPairing) UnmarshalJSON(data []byte) error
```go
type ChannelPairingNewParams struct {
- // Six-digit pairing code shown in the Channel message.
- Code string `json:"code" api:"required"`
+ // Pairing code. Supply exactly one of Code or ChannelPairingID.
+ Code string `json:"code,omitzero"`
+ // Pending pairing ID. Supply exactly one of Code or ChannelPairingID.
+ ChannelPairingID string `json:"channel_pairing_id,omitzero"`
// Forward Identity ID to bind.
IdentityID string `json:"identity_id" api:"required"`
// Forward Template ID to bind.
@@ -3441,6 +3471,8 @@ func (r *EnvironmentConfigPackages) UnmarshalJSON(data []byte) error
```go
type EnvironmentListParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Page size, maximum 100.
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Pagination cursor (recommended); take the value from `next_page` in the previous
@@ -3470,6 +3502,8 @@ func (r EnvironmentListParams) URLQuery() (url.Values, error)
```go
type EnvironmentNewParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Environment name; must not be empty after trimming surrounding whitespace.
Name string `json:"name" api:"required"`
// Description.
@@ -3497,6 +3531,15 @@ func (r EnvironmentNewParams) MarshalJSON() ([]byte, error)
+
+### func \(EnvironmentNewParams\) [URLQuery]()
+
+```go
+func (r EnvironmentNewParams) URLQuery() (url.Values, error)
+```
+
+
+
### func \(\*EnvironmentNewParams\) [UnmarshalJSON]()
@@ -3596,6 +3639,8 @@ Update Environment
```go
type EnvironmentUpdateParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// New name.
Name param.Opt[string] `json:"name,omitzero"`
// New description.
@@ -3619,6 +3664,15 @@ func (r EnvironmentUpdateParams) MarshalJSON() ([]byte, error)
+
+### func \(EnvironmentUpdateParams\) [URLQuery]()
+
+```go
+func (r EnvironmentUpdateParams) URLQuery() (url.Values, error)
+```
+
+
+
### func \(\*EnvironmentUpdateParams\) [UnmarshalJSON]()
@@ -3805,6 +3859,8 @@ func (r *EventContentUnionParam) UnmarshalJSON(data []byte) error
```go
type FileListParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Page size, maximum 100.
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Pagination cursor (recommended), taken from `next_page` in the previous response;
@@ -3979,6 +4035,8 @@ Upload File
```go
type FileUploadParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Content of the file to upload. For accepted types see
// [Supported upload file types](./schemas.md).
File io.Reader `json:"file" api:"required" format:"binary"`
@@ -4008,6 +4066,15 @@ func (r FileUploadParams) MarshalMultipart() ([]byte, string, error)
+
+### func \(FileUploadParams\) [URLQuery]()
+
+```go
+func (r FileUploadParams) URLQuery() (url.Values, error)
+```
+
+
+
## type [GitHubRepository]()
@@ -10039,10 +10106,14 @@ func (r *SessionUpdateParamsConfigParam) UnmarshalJSON(data []byte) error
```go
type SessionUsage struct {
- TotalCredits float64 `json:"total_credits"`
- JSON struct {
- TotalCredits respjson.Field
- ExtraFields map[string]respjson.Field
+ TotalCredits float64 `json:"total_credits"`
+ ModelCredits float64 `json:"model_credits"`
+ SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"`
+ JSON struct {
+ ModelCredits respjson.Field
+ SandboxRuntimeCredits respjson.Field
+ TotalCredits respjson.Field
+ ExtraFields map[string]respjson.Field
// contains filtered or unexported fields
} `json:"-"`
}
@@ -10202,6 +10273,8 @@ func (r *SkillBindingParam) UnmarshalJSON(data []byte) error
```go
type SkillGetParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// ⚠️ **Deprecated**: when `true`, the response also carries `content` and
// `content_encoding` (a base64 zip). Responses carry the `Deprecation: true` header
// when it is used. Use [Download Skill version content](./Versions/download.md)
@@ -10227,6 +10300,8 @@ func (r SkillGetParams) URLQuery() (url.Values, error)
```go
type SkillListParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Page size, maximum 100.
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Pagination cursor (recommended), taken from `next_page` in the previous response;
@@ -10264,6 +10339,8 @@ func (r SkillListParams) URLQuery() (url.Values, error)
```go
type SkillNewParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Recommended upload field, which may **repeat** several times. Two shapes are
// supported: (1) a single `.zip` archive; (2) a bare file tree — each part uploads
// one file and carries its relative path in `filename` (such as
@@ -10304,6 +10381,15 @@ func (r SkillNewParams) MarshalMultipart() ([]byte, string, error)
+
+### func \(SkillNewParams\) [URLQuery]()
+
+```go
+func (r SkillNewParams) URLQuery() (url.Values, error)
+```
+
+
+
## type [SkillOverride]()
@@ -10456,6 +10542,8 @@ Update Skill
```go
type SkillUpdateParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// New description.
Description param.Opt[string] `json:"description,omitzero"`
// New content (the contents of a zip archive). Neither the archive itself nor its
@@ -10490,6 +10578,15 @@ func (r SkillUpdateParams) MarshalJSON() ([]byte, error)
+
+### func \(SkillUpdateParams\) [URLQuery]()
+
+```go
+func (r SkillUpdateParams) URLQuery() (url.Values, error)
+```
+
+
+
### func \(\*SkillUpdateParams\) [UnmarshalJSON]()
@@ -10560,6 +10657,8 @@ func (r *SkillVersion) UnmarshalJSON(data []byte) error
```go
type SkillVersionListParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Page size, maximum 100, default 20.
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Cursor for paging forward; take the value from `next_page` in the previous
@@ -10585,6 +10684,8 @@ func (r SkillVersionListParams) URLQuery() (url.Values, error)
```go
type SkillVersionNewParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Upload field, which may appear **multiple** times. Two shapes are supported: • a
// single `.zip` archive; • a bare file tree, where every part uploads one file and
// `filename` carries its relative path (e.g. `customer-reply/SKILL.md`,
@@ -10604,6 +10705,15 @@ func (r SkillVersionNewParams) MarshalMultipart() ([]byte, string, error)
+
+### func \(SkillVersionNewParams\) [URLQuery]()
+
+```go
+func (r SkillVersionNewParams) URLQuery() (url.Values, error)
+```
+
+
+
## type [SkillVersionService]()
@@ -11677,6 +11787,8 @@ func (r *VaultCredentialAuth) UnmarshalJSON(data []byte) error
```go
type VaultCredentialListParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Page size, maximum 100.
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Pagination cursor (recommended), taken from `next_page` in the previous response;
@@ -11708,6 +11820,8 @@ func (r VaultCredentialListParams) URLQuery() (url.Values, error)
```go
type VaultCredentialNewParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Credential authentication material; supports `static_bearer` and `mcp_oauth`. The
// response only returns redacted, non-secret fields.
Auth map[string]any `json:"auth" api:"required"`
@@ -11733,6 +11847,15 @@ func (r VaultCredentialNewParams) MarshalJSON() ([]byte, error)
+
+### func \(VaultCredentialNewParams\) [URLQuery]()
+
+```go
+func (r VaultCredentialNewParams) URLQuery() (url.Values, error)
+```
+
+
+
### func \(\*VaultCredentialNewParams\) [UnmarshalJSON]()
@@ -11896,6 +12019,8 @@ func (r *VaultCredentialUpdateParams) UnmarshalJSON(data []byte) error
```go
type VaultListParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Page size, maximum 100.
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Pagination cursor (recommended), taken from `next_page` in the previous
@@ -11927,6 +12052,8 @@ func (r VaultListParams) URLQuery() (url.Values, error)
```go
type VaultNewParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Vault display name.
DisplayName string `json:"display_name" api:"required"`
// Metadata object; `created_by` is reserved and cannot be supplied (supplying it
@@ -11948,6 +12075,15 @@ func (r VaultNewParams) MarshalJSON() ([]byte, error)
+
+### func \(VaultNewParams\) [URLQuery]()
+
+```go
+func (r VaultNewParams) URLQuery() (url.Values, error)
+```
+
+
+
### func \(\*VaultNewParams\) [UnmarshalJSON]()
@@ -18258,8 +18394,8 @@ type DeploymentNewParams struct {
Name string `json:"name" api:"required"`
// Description of what the deployment does.
Description param.Opt[string] `json:"description,omitzero"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimitParam `json:"budget,omitzero"`
// Arbitrary key-value metadata. Maximum 16 pairs, keys up to 64 chars, values up
// to 512 chars.
@@ -18832,8 +18968,8 @@ type DeploymentUpdateParams struct {
// version, or an `agent` object with both id and version specified. Omit to
// preserve. Cannot be cleared.
Agent DeploymentUpdateParamsAgentUnion `json:"agent,omitzero"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimitParam `json:"budget,omitzero"`
// Initial events. Full replacement. Omit to preserve. Cannot be cleared. At least
// 1, maximum 50.
@@ -23202,6 +23338,8 @@ Indicates that context compaction \(summarization\) occurred during the session.
```go
type ManagedAgentsAgentThreadContextCompactedEvent struct {
+ // Credits for this compaction call, already included in Session totals.
+ Usage ManagedAgentsSpanModelUsage `json:"usage"`
// Unique identifier for this event.
ID string `json:"id" api:"required"`
// A timestamp in RFC 3339 format
@@ -23210,6 +23348,7 @@ type ManagedAgentsAgentThreadContextCompactedEvent struct {
Type ManagedAgentsAgentThreadContextCompactedEventType `json:"type" api:"required"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
+ Usage respjson.Field
ID respjson.Field
ProcessedAt respjson.Field
Type respjson.Field
@@ -26101,19 +26240,19 @@ const (
## type [ManagedAgentsBudgetLimit]()
-A hard spend ceiling. The session stops issuing new model requests once the tracked list cost reaches \`max\_list\_cost\`.
+A total model and sandbox credit limit. Once reached, new user messages are rejected; an already running turn continues to completion.
```go
type ManagedAgentsBudgetLimit struct {
- // A monetary amount in a specific currency.
- MaxListCost MonetaryAmount `json:"max_list_cost" api:"required"`
+ // Positive decimal Credits, with at most 12 integer and 2 fractional digits.
+ MaxCreditCost string `json:"max_credit_cost" api:"required"`
// Any of "limit".
Type ManagedAgentsBudgetLimitType `json:"type" api:"required"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
- MaxListCost respjson.Field
- Type respjson.Field
- ExtraFields map[string]respjson.Field
+ MaxCreditCost respjson.Field
+ Type respjson.Field
+ ExtraFields map[string]respjson.Field
// contains filtered or unexported fields
} `json:"-"`
}
@@ -26151,14 +26290,14 @@ func (r *ManagedAgentsBudgetLimit) UnmarshalJSON(data []byte) error
## type [ManagedAgentsBudgetLimitParam]()
-A hard spend ceiling. The session stops issuing new model requests once the tracked list cost reaches \`max\_list\_cost\`.
+A total model and sandbox credit limit. Once reached, new user messages are rejected; an already running turn continues to completion.
-The properties MaxListCost, Type are required.
+The properties MaxCreditCost, Type are required.
```go
type ManagedAgentsBudgetLimitParam struct {
- // A monetary amount in a specific currency.
- MaxListCost MonetaryAmountParam `json:"max_list_cost,omitzero" api:"required"`
+ // Positive decimal Credits, with at most 12 integer and 2 fractional digits.
+ MaxCreditCost string `json:"max_credit_cost" api:"required"`
// Any of "limit".
Type ManagedAgentsBudgetLimitType `json:"type,omitzero" api:"required"`
// contains filtered or unexported fields
@@ -27667,8 +27806,8 @@ type ManagedAgentsDeployment struct {
// Vault IDs supplying stored credentials for sessions created from this
// deployment.
VaultIDs []string `json:"vault_ids" api:"required"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimit `json:"budget" api:"nullable"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
@@ -38150,9 +38289,9 @@ type ManagedAgentsSession struct {
Agent ManagedAgentsSessionAgent `json:"agent" api:"required"`
// A timestamp in RFC 3339 format
ArchivedAt time.Time `json:"archived_at" api:"required" format:"date-time"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
- Budget ManagedAgentsBudgetLimit `json:"budget" api:"required"`
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
+ Budget ManagedAgentsBudgetLimit `json:"budget" api:"nullable"`
// A timestamp in RFC 3339 format
CreatedAt time.Time `json:"created_at" api:"required" format:"date-time"`
EnvironmentID string `json:"environment_id" api:"required"`
@@ -38172,8 +38311,9 @@ type ManagedAgentsSession struct {
Type ManagedAgentsSessionType `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 across all turns.
- Usage ManagedAgentsSessionUsage `json:"usage" api:"required"`
+ // Cumulative Credits snapshot. Replace previous values; do not sum snapshots.
+ // Legacy token and list-cost fields remain available for older responses.
+ Usage ManagedAgentsSessionUsage `json:"usage"`
// Vault IDs attached to the session at creation. Empty when no vaults were
// supplied.
VaultIDs []string `json:"vault_ids" api:"required"`
@@ -39255,7 +39395,10 @@ Use the methods beginning with 'As' to cast the union to one of its variants.
```go
type ManagedAgentsSessionEventUnion struct {
- ID string `json:"id"`
+ ModelCredits float64 `json:"model_credits"`
+ SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"`
+ TotalCredits float64 `json:"total_credits"`
+ ID string `json:"id"`
// This field is a union of [[]ManagedAgentsUserMessageEventContentUnion],
// [[]ManagedAgentsUserCustomToolResultEventContentUnion],
// [[]ManagedAgentsAgentMessageEventContentUnion],
@@ -39340,6 +39483,9 @@ type ManagedAgentsSessionEventUnion struct {
// This field is from variant [ManagedAgentsSessionUpdatedEvent].
Title string `json:"title"`
JSON struct {
+ ModelCredits respjson.Field
+ SandboxRuntimeCredits respjson.Field
+ TotalCredits respjson.Field
ID respjson.Field
Content respjson.Field
Type respjson.Field
@@ -39869,6 +40015,7 @@ For type safety it is recommended to directly use a variant of the [ManagedAgent
```go
type ManagedAgentsSessionEventUnionUsage struct {
+ Credits float64 `json:"credits"`
// This field is from variant [ManagedAgentsSpanModelUsage].
CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"`
CacheReadInputTokens int64 `json:"cache_read_input_tokens"`
@@ -39885,6 +40032,7 @@ type ManagedAgentsSessionEventUnionUsage struct {
// This field is from variant [ManagedAgentsSessionUsageSnapshot].
ServerToolUse ManagedAgentsServerToolUsage `json:"server_tool_use"`
JSON struct {
+ Credits respjson.Field
CacheCreationInputTokens respjson.Field
CacheReadInputTokens respjson.Field
InputTokens respjson.Field
@@ -42154,8 +42302,8 @@ type ManagedAgentsSessionUpdatedEvent struct {
// Resolved `agent` definition for a `session`. Snapshot of the `agent` at
// `session` creation time.
Agent ManagedAgentsSessionAgent `json:"agent" api:"nullable"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimit `json:"budget" api:"nullable"`
// The session's full metadata bag after the update. Present when the update set
// non-empty metadata; absent when metadata was unchanged or cleared to empty.
@@ -42215,10 +42363,13 @@ const (
## type [ManagedAgentsSessionUsage]()
-Cumulative token usage for a session across all turns.
+Cumulative Credits snapshot. Replace previous values; do not sum snapshots. Legacy token and list\-cost fields remain available for older responses.
```go
type ManagedAgentsSessionUsage struct {
+ ModelCredits float64 `json:"model_credits"`
+ SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"`
+ TotalCredits float64 `json:"total_credits"`
// Cumulative time in seconds during which the session had at least one thread in
// running status. Overlapping activity from concurrent threads is counted once,
// unlike `stats.active_seconds`, which sums each thread's own active time. This is
@@ -42238,14 +42389,17 @@ type ManagedAgentsSessionUsage struct {
ServerToolUse ManagedAgentsServerToolUsage `json:"server_tool_use" api:"nullable"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
- ActiveSeconds respjson.Field
- CacheCreation respjson.Field
- CacheReadInputTokens respjson.Field
- InputTokens respjson.Field
- ListCost respjson.Field
- OutputTokens respjson.Field
- ServerToolUse respjson.Field
- ExtraFields map[string]respjson.Field
+ ModelCredits respjson.Field
+ SandboxRuntimeCredits respjson.Field
+ TotalCredits respjson.Field
+ ActiveSeconds respjson.Field
+ CacheCreation respjson.Field
+ CacheReadInputTokens respjson.Field
+ InputTokens respjson.Field
+ ListCost respjson.Field
+ OutputTokens respjson.Field
+ ServerToolUse respjson.Field
+ ExtraFields map[string]respjson.Field
// contains filtered or unexported fields
} `json:"-"`
}
@@ -42272,10 +42426,13 @@ func (r *ManagedAgentsSessionUsage) UnmarshalJSON(data []byte) error
## type [ManagedAgentsSessionUsageEvent]()
-Periodic snapshot of the session's cumulative usage and tracked list cost.
+Cumulative Credits emitted before session.status\_idle when usage is available. Credits are top\-level fields; Usage and Budget are legacy response fields.
```go
type ManagedAgentsSessionUsageEvent struct {
+ ModelCredits float64 `json:"model_credits"`
+ SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"`
+ TotalCredits float64 `json:"total_credits"`
// Unique identifier for this event.
ID string `json:"id" api:"required"`
// A timestamp in RFC 3339 format
@@ -42283,18 +42440,21 @@ type ManagedAgentsSessionUsageEvent struct {
// Any of "session.usage".
Type ManagedAgentsSessionUsageEventType `json:"type" api:"required"`
// Point-in-time snapshot of a session's cumulative usage.
- Usage ManagedAgentsSessionUsageSnapshot `json:"usage" api:"required"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ Usage ManagedAgentsSessionUsageSnapshot `json:"usage"`
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimit `json:"budget" api:"nullable"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
- ID respjson.Field
- ProcessedAt respjson.Field
- Type respjson.Field
- Usage respjson.Field
- Budget respjson.Field
- ExtraFields map[string]respjson.Field
+ ModelCredits respjson.Field
+ SandboxRuntimeCredits respjson.Field
+ TotalCredits respjson.Field
+ ID respjson.Field
+ ProcessedAt respjson.Field
+ Type respjson.Field
+ Usage respjson.Field
+ Budget respjson.Field
+ ExtraFields map[string]respjson.Field
// contains filtered or unexported fields
} `json:"-"`
}
@@ -42591,8 +42751,8 @@ type ManagedAgentsSpanModelRequestEndEvent struct {
IsError bool `json:"is_error" api:"required"`
// The id of the corresponding `span.model_request_start` event.
ModelRequestStartID string `json:"model_request_start_id" api:"required"`
- // Token usage for a single model request.
- ModelUsage ManagedAgentsSpanModelUsage `json:"model_usage" api:"required"`
+ // Credits for a single model request; legacy token fields may appear in older responses.
+ ModelUsage ManagedAgentsSpanModelUsage `json:"model_usage"`
// A timestamp in RFC 3339 format
ProcessedAt time.Time `json:"processed_at" api:"required" format:"date-time"`
// Any of "span.model_request_end".
@@ -42708,18 +42868,19 @@ const (
## type [ManagedAgentsSpanModelUsage]()
-Token usage for a single model request.
+Credits for a single model request; legacy token fields may appear in older responses.
```go
type ManagedAgentsSpanModelUsage struct {
+ Credits float64 `json:"credits"`
// Tokens used to create prompt cache in this request.
- CacheCreationInputTokens int64 `json:"cache_creation_input_tokens" api:"required"`
+ CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"`
// Tokens read from prompt cache in this request.
- CacheReadInputTokens int64 `json:"cache_read_input_tokens" api:"required"`
+ CacheReadInputTokens int64 `json:"cache_read_input_tokens"`
// Input tokens consumed by this request.
- InputTokens int64 `json:"input_tokens" api:"required"`
+ InputTokens int64 `json:"input_tokens"`
// Output tokens generated by this request.
- OutputTokens int64 `json:"output_tokens" api:"required"`
+ OutputTokens int64 `json:"output_tokens"`
// Inference speed mode. `fast` provides significantly faster output token
// generation at premium pricing. Not all models support `fast`; invalid
// combinations are rejected at create time.
@@ -42728,6 +42889,7 @@ type ManagedAgentsSpanModelUsage struct {
Speed ManagedAgentsSpanModelUsageSpeed `json:"speed" api:"nullable"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
+ Credits respjson.Field
CacheCreationInputTokens respjson.Field
CacheReadInputTokens respjson.Field
InputTokens respjson.Field
@@ -42806,7 +42968,7 @@ type ManagedAgentsSpanOutcomeEvaluationEndEvent struct {
Result string `json:"result" api:"required"`
// Any of "span.outcome_evaluation_end".
Type ManagedAgentsSpanOutcomeEvaluationEndEventType `json:"type" api:"required"`
- // Token usage for a single model request.
+ // Credits for a single model request; legacy token fields may appear in older responses.
Usage ManagedAgentsSpanModelUsage `json:"usage" api:"required"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
@@ -43300,7 +43462,10 @@ Use the methods beginning with 'As' to cast the union to one of its variants.
```go
type ManagedAgentsStreamSessionEventsUnion struct {
- ID string `json:"id"`
+ ModelCredits float64 `json:"model_credits"`
+ SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"`
+ TotalCredits float64 `json:"total_credits"`
+ ID string `json:"id"`
// This field is a union of [[]ManagedAgentsUserMessageEventContentUnion],
// [[]ManagedAgentsUserCustomToolResultEventContentUnion],
// [[]ManagedAgentsAgentMessageEventContentUnion],
@@ -43391,6 +43556,9 @@ type ManagedAgentsStreamSessionEventsUnion struct {
// This field is from variant [ManagedAgentsDeltaEvent].
EventID string `json:"event_id"`
JSON struct {
+ ModelCredits respjson.Field
+ SandboxRuntimeCredits respjson.Field
+ TotalCredits respjson.Field
ID respjson.Field
Content respjson.Field
Type respjson.Field
@@ -43943,6 +44111,7 @@ For type safety it is recommended to directly use a variant of the [ManagedAgent
```go
type ManagedAgentsStreamSessionEventsUnionUsage struct {
+ Credits float64 `json:"credits"`
// This field is from variant [ManagedAgentsSpanModelUsage].
CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"`
CacheReadInputTokens int64 `json:"cache_read_input_tokens"`
@@ -43959,6 +44128,7 @@ type ManagedAgentsStreamSessionEventsUnionUsage struct {
// This field is from variant [ManagedAgentsSessionUsageSnapshot].
ServerToolUse ManagedAgentsServerToolUsage `json:"server_tool_use"`
JSON struct {
+ Credits respjson.Field
CacheCreationInputTokens respjson.Field
CacheReadInputTokens respjson.Field
InputTokens respjson.Field
@@ -43993,7 +44163,10 @@ Use the methods beginning with 'As' to cast the union to one of its variants.
```go
type ManagedAgentsStreamSessionThreadEventsUnion struct {
- ID string `json:"id"`
+ ModelCredits float64 `json:"model_credits"`
+ SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"`
+ TotalCredits float64 `json:"total_credits"`
+ ID string `json:"id"`
// This field is a union of [[]ManagedAgentsUserMessageEventContentUnion],
// [[]ManagedAgentsUserCustomToolResultEventContentUnion],
// [[]ManagedAgentsAgentMessageEventContentUnion],
@@ -44084,6 +44257,9 @@ type ManagedAgentsStreamSessionThreadEventsUnion struct {
// This field is from variant [ManagedAgentsDeltaEvent].
EventID string `json:"event_id"`
JSON struct {
+ ModelCredits respjson.Field
+ SandboxRuntimeCredits respjson.Field
+ TotalCredits respjson.Field
ID respjson.Field
Content respjson.Field
Type respjson.Field
@@ -44636,6 +44812,7 @@ For type safety it is recommended to directly use a variant of the [ManagedAgent
```go
type ManagedAgentsStreamSessionThreadEventsUnionUsage struct {
+ Credits float64 `json:"credits"`
// This field is from variant [ManagedAgentsSpanModelUsage].
CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"`
CacheReadInputTokens int64 `json:"cache_read_input_tokens"`
@@ -44652,6 +44829,7 @@ type ManagedAgentsStreamSessionThreadEventsUnionUsage struct {
// This field is from variant [ManagedAgentsSessionUsageSnapshot].
ServerToolUse ManagedAgentsServerToolUsage `json:"server_tool_use"`
JSON struct {
+ Credits respjson.Field
CacheCreationInputTokens respjson.Field
CacheReadInputTokens respjson.Field
InputTokens respjson.Field
@@ -52180,8 +52358,8 @@ type SessionNewParams struct {
EnvironmentID string `json:"environment_id" api:"required"`
// Human-readable session title.
Title param.Opt[string] `json:"title,omitzero"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimitParam `json:"budget,omitzero"`
// Initial events to send to the `session` at creation, processed in order.
// Supports `user.message` and `user.define_outcome` events. Maximum 50 events.
@@ -53293,8 +53471,8 @@ type SessionUpdateParams struct {
// updatable. Full replacement: the provided array becomes the new value. To
// preserve existing entries, GET the session, modify the array, and POST it back.
Agent ManagedAgentsSessionAgentUpdateParam `json:"agent,omitzero"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimitParam `json:"budget,omitzero"`
// Vault IDs (`vlt_*`) to attach to the session. Not yet supported; requests
// setting this field are rejected. Reserved for future use.
diff --git a/forward/channelpairing.go b/forward/channelpairing.go
index b25d9a6..9ff8632 100644
--- a/forward/channelpairing.go
+++ b/forward/channelpairing.go
@@ -35,8 +35,10 @@ func (r *ChannelPairingService) New(ctx context.Context, params ChannelPairingNe
}
type ChannelPairingNewParams struct {
- // Six-digit pairing code shown in the Channel message.
- Code string `json:"code" api:"required"`
+ // Pairing code. Supply exactly one of Code or ChannelPairingID.
+ Code string `json:"code,omitzero"`
+ // Pending pairing ID. Supply exactly one of Code or ChannelPairingID.
+ ChannelPairingID string `json:"channel_pairing_id,omitzero"`
// Forward Identity ID to bind.
IdentityID string `json:"identity_id" api:"required"`
// Forward Template ID to bind.
@@ -86,6 +88,11 @@ func (r *DeletedChannelPairing) UnmarshalJSON(data []byte) error {
}
type ChannelPairing struct {
+ ScopeType string `json:"scope_type"`
+ ScopeExternalID string `json:"scope_external_id"`
+ ScopeDisplayName string `json:"scope_display_name"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
// Pairing ID, used when removing the pairing.
ID string `json:"id"`
// Always `channel_pairing`.
@@ -101,15 +108,20 @@ type ChannelPairing struct {
// Time the pairing completed.
PairedAt string `json:"paired_at"`
JSON struct {
- ID respjson.Field
- Type respjson.Field
- ChannelID respjson.Field
- IdentityID respjson.Field
- TemplateID respjson.Field
- Status respjson.Field
- PairedAt respjson.Field
- ExtraFields map[string]respjson.Field
- raw string
+ ScopeType respjson.Field
+ ScopeExternalID respjson.Field
+ ScopeDisplayName respjson.Field
+ CreatedAt respjson.Field
+ UpdatedAt respjson.Field
+ ID respjson.Field
+ Type respjson.Field
+ ChannelID respjson.Field
+ IdentityID respjson.Field
+ TemplateID respjson.Field
+ Status respjson.Field
+ PairedAt respjson.Field
+ ExtraFields map[string]respjson.Field
+ raw string
} `json:"-"`
}
diff --git a/forward/environment.go b/forward/environment.go
index d5b451f..541945c 100644
--- a/forward/environment.go
+++ b/forward/environment.go
@@ -48,6 +48,8 @@ func (r *EnvironmentService) ListAutoPaging(ctx context.Context, params Environm
}
type EnvironmentListParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Page size, maximum 100.
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Pagination cursor (recommended); take the value from `next_page` in the previous
@@ -76,6 +78,8 @@ func (r *EnvironmentService) New(ctx context.Context, params EnvironmentNewParam
}
type EnvironmentNewParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Environment name; must not be empty after trimming surrounding whitespace.
Name string `json:"name" api:"required"`
// Description.
@@ -126,6 +130,8 @@ func (r *EnvironmentService) Update(ctx context.Context, id string, params Envir
}
type EnvironmentUpdateParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// New name.
Name param.Opt[string] `json:"name,omitzero"`
// New description.
@@ -239,3 +245,11 @@ func (r EnvironmentConfigPackages) RawJSON() string { return r.JSON.raw }
func (r *EnvironmentConfigPackages) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, r)
}
+
+func (r EnvironmentNewParams) URLQuery() (url.Values, error) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ArrayFormat: apiquery.ArrayQueryFormatRepeat, NestedFormat: apiquery.NestedQueryFormatBrackets})
+}
+
+func (r EnvironmentUpdateParams) URLQuery() (url.Values, error) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ArrayFormat: apiquery.ArrayQueryFormatRepeat, NestedFormat: apiquery.NestedQueryFormatBrackets})
+}
diff --git a/forward/file.go b/forward/file.go
index 58e9caa..f33ca32 100644
--- a/forward/file.go
+++ b/forward/file.go
@@ -49,6 +49,8 @@ func (r *FileService) ListAutoPaging(ctx context.Context, params FileListParams,
}
type FileListParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Page size, maximum 100.
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Pagination cursor (recommended), taken from `next_page` in the previous response;
@@ -83,6 +85,8 @@ func (r *FileService) Upload(ctx context.Context, params FileUploadParams, opts
}
type FileUploadParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Content of the file to upload. For accepted types see
// [Supported upload file types](./schemas.md).
File io.Reader `json:"file" api:"required" format:"binary"`
@@ -183,3 +187,7 @@ type FileMetadata struct {
func (r FileMetadata) RawJSON() string { return r.JSON.raw }
func (r *FileMetadata) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) }
+
+func (r FileUploadParams) URLQuery() (url.Values, error) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ArrayFormat: apiquery.ArrayQueryFormatRepeat, NestedFormat: apiquery.NestedQueryFormatBrackets})
+}
diff --git a/forward/identity_pairing_contract_test.go b/forward/identity_pairing_contract_test.go
new file mode 100644
index 0000000..1c2cac8
--- /dev/null
+++ b/forward/identity_pairing_contract_test.go
@@ -0,0 +1,245 @@
+package forward_test
+
+import (
+ "context"
+ "encoding/json"
+ "io"
+ "net/http"
+ "strings"
+ "testing"
+
+ "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option"
+ "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/param"
+ "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward"
+)
+
+// Official Forward ownership contracts (2026-10-20), independent of inventories.
+func TestIdentityOwnershipContracts(t *testing.T) {
+ ctx := context.Background()
+ for _, scope := range []struct {
+ name string
+ identity param.Opt[string]
+ }{{"PAT identity", forward.String("idn_ /?&")}, {"explicit empty", forward.String("")}, {"omitted", param.Opt[string]{}}} {
+ t.Run(scope.name, func(t *testing.T) {
+ identity := scope.identity
+ var opts []option.RequestOption
+ if identity.Valid() {
+ opts = append(opts, forward.WithIdentityID(identity.Value))
+ }
+ cases := []struct {
+ method, path string
+ call func(forward.Client) error
+ }{
+ {"GET", "environments", func(c forward.Client) error {
+ _, err := c.Environments.List(ctx, forward.EnvironmentListParams{IdentityID: identity})
+ return err
+ }},
+ {"POST", "environments", func(c forward.Client) error {
+ _, err := c.Environments.New(ctx, forward.EnvironmentNewParams{Name: "env", IdentityID: identity})
+ return err
+ }},
+ {"GET", "environments/env", func(c forward.Client) error { _, err := c.Environments.Get(ctx, "env", opts...); return err }},
+ {"POST", "environments/env", func(c forward.Client) error {
+ _, err := c.Environments.Update(ctx, "env", forward.EnvironmentUpdateParams{Name: forward.String("renamed"), IdentityID: identity})
+ return err
+ }},
+ {"POST", "environments/env/archive", func(c forward.Client) error { _, err := c.Environments.Archive(ctx, "env", opts...); return err }},
+ {"DELETE", "environments/env", func(c forward.Client) error { return c.Environments.Delete(ctx, "env", opts...) }},
+ {"GET", "skills", func(c forward.Client) error {
+ _, err := c.Skills.List(ctx, forward.SkillListParams{IdentityID: identity})
+ return err
+ }},
+ {"POST", "skills", func(c forward.Client) error {
+ _, err := c.Skills.New(ctx, forward.SkillNewParams{Files: []io.Reader{strings.NewReader("# skill")}, IdentityID: identity})
+ return err
+ }},
+ {"GET", "skills/skill", func(c forward.Client) error {
+ _, err := c.Skills.Get(ctx, "skill", forward.SkillGetParams{IdentityID: identity})
+ return err
+ }},
+ {"PUT", "skills/skill", func(c forward.Client) error {
+ _, err := c.Skills.Update(ctx, "skill", forward.SkillUpdateParams{Description: forward.String("desc"), IdentityID: identity})
+ return err
+ }},
+ {"DELETE", "skills/skill", func(c forward.Client) error { return c.Skills.Delete(ctx, "skill", opts...) }},
+ {"GET", "skills/skill/versions", func(c forward.Client) error {
+ _, err := c.Skills.Versions.List(ctx, "skill", forward.SkillVersionListParams{IdentityID: identity})
+ return err
+ }},
+ {"POST", "skills/skill/versions", func(c forward.Client) error {
+ _, err := c.Skills.Versions.New(ctx, "skill", forward.SkillVersionNewParams{Files: []io.Reader{strings.NewReader("# skill")}, IdentityID: identity})
+ return err
+ }},
+ {"GET", "skills/skill/versions/1", func(c forward.Client) error { _, err := c.Skills.Versions.Get(ctx, "skill", "1", opts...); return err }},
+ {"DELETE", "skills/skill/versions/1", func(c forward.Client) error {
+ _, err := c.Skills.Versions.Delete(ctx, "skill", "1", opts...)
+ return err
+ }},
+ {"GET", "skills/skill/versions/1/content", func(c forward.Client) error {
+ res, err := c.Skills.Versions.Download(ctx, "skill", "1", opts...)
+ if err == nil {
+ res.Body.Close()
+ }
+ return err
+ }},
+ {"GET", "vaults", func(c forward.Client) error {
+ _, err := c.Vaults.List(ctx, forward.VaultListParams{IdentityID: identity})
+ return err
+ }},
+ {"POST", "vaults", func(c forward.Client) error {
+ _, err := c.Vaults.New(ctx, forward.VaultNewParams{DisplayName: "vault", IdentityID: identity})
+ return err
+ }},
+ {"GET", "vaults/vault", func(c forward.Client) error { _, err := c.Vaults.Get(ctx, "vault", opts...); return err }},
+ {"DELETE", "vaults/vault", func(c forward.Client) error { return c.Vaults.Delete(ctx, "vault", opts...) }},
+ {"GET", "vaults/vault/credentials", func(c forward.Client) error {
+ _, err := c.Vaults.Credentials.List(ctx, "vault", forward.VaultCredentialListParams{IdentityID: identity})
+ return err
+ }},
+ {"POST", "vaults/vault/credentials", func(c forward.Client) error {
+ _, err := c.Vaults.Credentials.New(ctx, "vault", forward.VaultCredentialNewParams{Auth: map[string]any{"type": "static_bearer", "token": "secret"}, IdentityID: identity})
+ return err
+ }},
+ {"GET", "vaults/vault/credentials/cred", func(c forward.Client) error {
+ _, err := c.Vaults.Credentials.Get(ctx, "vault", "cred", opts...)
+ return err
+ }},
+ {"POST", "vaults/vault/credentials/cred", func(c forward.Client) error {
+ _, err := c.Vaults.Credentials.Update(ctx, "vault", "cred", forward.VaultCredentialUpdateParams{Metadata: map[string]any{"note": "rotation"}, IdentityID: identity})
+ return err
+ }},
+ {"DELETE", "vaults/vault/credentials/cred", func(c forward.Client) error { return c.Vaults.Credentials.Delete(ctx, "vault", "cred", opts...) }},
+ {"GET", "files", func(c forward.Client) error {
+ _, err := c.Files.List(ctx, forward.FileListParams{IdentityID: identity})
+ return err
+ }},
+ {"POST", "files", func(c forward.Client) error {
+ _, err := c.Files.Upload(ctx, forward.FileUploadParams{File: strings.NewReader("content"), IdentityID: identity})
+ return err
+ }},
+ {"GET", "files/file", func(c forward.Client) error { _, err := c.Files.GetMetadata(ctx, "file", opts...); return err }},
+ {"DELETE", "files/file", func(c forward.Client) error { return c.Files.Delete(ctx, "file", opts...) }},
+ {"GET", "files/file/content", func(c forward.Client) error {
+ res, err := c.Files.Download(ctx, "file", opts...)
+ if err == nil {
+ res.Body.Close()
+ }
+ return err
+ }},
+ }
+ for _, tc := range cases {
+ t.Run(tc.method+" "+tc.path, func(t *testing.T) {
+ calls := 0
+ c := testClient(func(r *http.Request) (*http.Response, error) {
+ if r.URL.Host == "storage.test" {
+ if r.URL.RawQuery != "signature=ok" || r.Header.Get("Authorization") != "" {
+ t.Fatal("scope or auth leaked to storage")
+ }
+ return reply(r, 200, "content"), nil
+ }
+ calls++
+ if r.Method != tc.method || r.URL.Path != "/api/v1/forward/"+tc.path {
+ t.Fatalf("route: %s %s", r.Method, r.URL)
+ }
+ query := r.URL.Query()
+ if query.Has("identity_id") != identity.Valid() || query.Get("identity_id") != identity.Value {
+ t.Fatalf("scope: %s", r.URL)
+ }
+ if len(query) > 1 {
+ t.Fatalf("body fields leaked to query: %s", r.URL)
+ }
+ if r.Body != nil {
+ body, _ := io.ReadAll(r.Body)
+ if strings.Contains(string(body), "identity_id") {
+ t.Fatalf("identity leaked into body: %s", body)
+ }
+ }
+ if tc.path == "files/file/content" {
+ return reply(r, 200, `{"url":"https://storage.test/content?signature=ok"}`), nil
+ }
+ return reply(r, 200, `{"id":"resource","data":[],"has_more":false}`), nil
+ })
+ if err := tc.call(c); err != nil {
+ t.Fatal(err)
+ }
+ if calls != 1 {
+ t.Fatalf("API calls: %d", calls)
+ }
+ })
+ }
+ })
+ }
+}
+
+func TestPairChannelByCodeOrID(t *testing.T) {
+ for _, byID := range []bool{false, true} {
+ t.Run(map[bool]string{false: "code", true: "ID"}[byID], func(t *testing.T) {
+ params := forward.ChannelPairingNewParams{IdentityID: "idn_one", TemplateID: "tmpl_one", IdempotencyKey: forward.String("pair-once")}
+ expected := map[string]any{"identity_id": "idn_one", "template_id": "tmpl_one"}
+ if byID {
+ params.ChannelPairingID = "pair_one"
+ expected["channel_pairing_id"] = "pair_one"
+ } else {
+ params.Code = "K7MP92"
+ expected["code"] = "K7MP92"
+ }
+ c := testClient(func(r *http.Request) (*http.Response, error) {
+ if r.URL.RawQuery != "" || r.Header.Get("Idempotency-Key") != "pair-once" {
+ t.Fatal("pairing query/header")
+ }
+ var body map[string]any
+ if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
+ t.Fatal(err)
+ }
+ if len(body) != len(expected) {
+ t.Fatalf("unexpected body: %+v", body)
+ }
+ for k, v := range expected {
+ if body[k] != v {
+ t.Fatalf("body: %+v", body)
+ }
+ }
+ return reply(r, 200, `{"id": "pair_one", "type": "channel_pairing", "channel_id": "channel_one", "scope_type": "room", "scope_external_id": "room_one", "scope_display_name": "Team", "identity_id": "idn_one", "template_id": "tmpl_one", "status": "active", "paired_at": "2026-10-20T01:00:00Z"}`), nil
+ })
+ res, err := c.ChannelPairings.New(context.Background(), params)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if res.ScopeType != "room" || res.ScopeExternalID != "room_one" || res.ScopeDisplayName != "Team" {
+ t.Fatalf("pairing=%+v", res)
+ }
+ })
+ }
+}
+
+func TestIdentityOwnershipPagingAndOverride(t *testing.T) {
+ calls := 0
+ c := testClient(func(r *http.Request) (*http.Response, error) {
+ if r.URL.Query().Get("identity_id") != "idn_override" {
+ t.Fatalf("lost scope: %s", r.URL)
+ }
+ calls++
+ if calls == 1 {
+ return reply(r, 200, `{"data":[{"id":"env_one"}],"has_more":true,"next_page":"next"}`), nil
+ }
+ if calls == 2 && r.URL.Query().Get("page") != "next" {
+ t.Fatalf("lost cursor: %s", r.URL)
+ }
+ return reply(r, 200, `{"data":[{"id":"env_two"}],"has_more":false}`), nil
+ })
+ pager := c.Environments.ListAutoPaging(context.Background(), forward.EnvironmentListParams{IdentityID: forward.String("idn_typed")}, forward.WithIdentityID("idn_override"))
+ count := 0
+ for pager.Next() {
+ count++
+ }
+ if err := pager.Err(); err != nil {
+ t.Fatal(err)
+ }
+ if count != 2 {
+ t.Fatalf("items=%d", count)
+ }
+ _, err := c.Environments.New(context.Background(), forward.EnvironmentNewParams{Name: "env", IdentityID: forward.String("idn_typed")}, forward.WithIdentityID("idn_override"))
+ if err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/forward/params.go b/forward/params.go
index e28a615..fee34ca 100644
--- a/forward/params.go
+++ b/forward/params.go
@@ -10,6 +10,7 @@ import (
"github.com/QoderAI/qoder-cloud-agents-sdk-go/convention"
"github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/apiform"
"github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/apijson"
+ "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option"
"github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/param"
"github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/respjson"
"github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/thirdparty/gjson"
@@ -24,6 +25,13 @@ func Int(v int64) param.Opt[int64] { return param.NewOpt(v) }
func Bool(v bool) param.Opt[bool] { return param.NewOpt(v) }
func Float(v float64) param.Opt[float64] { return param.NewOpt(v) }
+// WithIdentityID selects identity ownership for Forward resources using a PAT.
+// Omit this option when using a SAT; its identity is inferred by the server.
+// Use it on retrieval, deletion, archive, and download methods without params.
+func WithIdentityID(identityID string) option.RequestOption {
+ return option.WithQuery("identity_id", identityID)
+}
+
// 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 {
diff --git a/forward/session.go b/forward/session.go
index acb23ce..32cb425 100644
--- a/forward/session.go
+++ b/forward/session.go
@@ -287,11 +287,15 @@ func (r Session) RawJSON() string { return r.JSON.raw }
func (r *Session) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) }
type SessionUsage struct {
- TotalCredits float64 `json:"total_credits"`
- JSON struct {
- TotalCredits respjson.Field
- ExtraFields map[string]respjson.Field
- raw string
+ TotalCredits float64 `json:"total_credits"`
+ ModelCredits float64 `json:"model_credits"`
+ SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"`
+ JSON struct {
+ ModelCredits respjson.Field
+ SandboxRuntimeCredits respjson.Field
+ TotalCredits respjson.Field
+ ExtraFields map[string]respjson.Field
+ raw string
} `json:"-"`
}
diff --git a/forward/skill.go b/forward/skill.go
index 8d00361..2848a45 100644
--- a/forward/skill.go
+++ b/forward/skill.go
@@ -50,6 +50,8 @@ func (r *SkillService) ListAutoPaging(ctx context.Context, params SkillListParam
}
type SkillListParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Page size, maximum 100.
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Pagination cursor (recommended), taken from `next_page` in the previous response;
@@ -86,6 +88,8 @@ func (r *SkillService) New(ctx context.Context, params SkillNewParams, opts ...o
}
type SkillNewParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Recommended upload field, which may **repeat** several times. Two shapes are
// supported: (1) a single `.zip` archive; (2) a bare file tree — each part uploads
// one file and carries its relative path in `filename` (such as
@@ -133,6 +137,8 @@ func (r *SkillService) Get(ctx context.Context, id string, params SkillGetParams
}
type SkillGetParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// ⚠️ **Deprecated**: when `true`, the response also carries `content` and
// `content_encoding` (a base64 zip). Responses carry the `Deprecation: true` header
// when it is used. Use [Download Skill version content](./Versions/download.md)
@@ -158,6 +164,8 @@ func (r *SkillService) Update(ctx context.Context, id string, params SkillUpdate
}
type SkillUpdateParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// New description.
Description param.Opt[string] `json:"description,omitzero"`
// New content (the contents of a zip archive). Neither the archive itself nor its
@@ -233,3 +241,11 @@ type Skill struct {
func (r Skill) RawJSON() string { return r.JSON.raw }
func (r *Skill) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) }
+
+func (r SkillNewParams) URLQuery() (url.Values, error) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ArrayFormat: apiquery.ArrayQueryFormatRepeat, NestedFormat: apiquery.NestedQueryFormatBrackets})
+}
+
+func (r SkillUpdateParams) URLQuery() (url.Values, error) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ArrayFormat: apiquery.ArrayQueryFormatRepeat, NestedFormat: apiquery.NestedQueryFormatBrackets})
+}
diff --git a/forward/skillversion.go b/forward/skillversion.go
index f0aedec..58a5fc9 100644
--- a/forward/skillversion.go
+++ b/forward/skillversion.go
@@ -52,6 +52,8 @@ func (r *SkillVersionService) ListAutoPaging(ctx context.Context, id string, par
}
type SkillVersionListParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Page size, maximum 100, default 20.
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Cursor for paging forward; take the value from `next_page` in the previous
@@ -77,6 +79,8 @@ func (r *SkillVersionService) New(ctx context.Context, id string, params SkillVe
}
type SkillVersionNewParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Upload field, which may appear **multiple** times. Two shapes are supported: • a
// single `.zip` archive; • a bare file tree, where every part uploads one file and
// `filename` carries its relative path (e.g. `customer-reply/SKILL.md`,
@@ -185,3 +189,7 @@ type SkillVersion struct {
func (r SkillVersion) RawJSON() string { return r.JSON.raw }
func (r *SkillVersion) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) }
+
+func (r SkillVersionNewParams) URLQuery() (url.Values, error) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ArrayFormat: apiquery.ArrayQueryFormatRepeat, NestedFormat: apiquery.NestedQueryFormatBrackets})
+}
diff --git a/forward/vault.go b/forward/vault.go
index 00537f1..928948c 100644
--- a/forward/vault.go
+++ b/forward/vault.go
@@ -49,6 +49,8 @@ func (r *VaultService) ListAutoPaging(ctx context.Context, params VaultListParam
}
type VaultListParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Page size, maximum 100.
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Pagination cursor (recommended), taken from `next_page` in the previous
@@ -79,6 +81,8 @@ func (r *VaultService) New(ctx context.Context, params VaultNewParams, opts ...o
}
type VaultNewParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Vault display name.
DisplayName string `json:"display_name" api:"required"`
// Metadata object; `created_by` is reserved and cannot be supplied (supplying it
@@ -150,3 +154,7 @@ type Vault struct {
func (r Vault) RawJSON() string { return r.JSON.raw }
func (r *Vault) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) }
+
+func (r VaultNewParams) URLQuery() (url.Values, error) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ArrayFormat: apiquery.ArrayQueryFormatRepeat, NestedFormat: apiquery.NestedQueryFormatBrackets})
+}
diff --git a/forward/vaultcredential.go b/forward/vaultcredential.go
index c9c453f..0091ec3 100644
--- a/forward/vaultcredential.go
+++ b/forward/vaultcredential.go
@@ -51,6 +51,8 @@ func (r *VaultCredentialService) ListAutoPaging(ctx context.Context, id string,
}
type VaultCredentialListParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Page size, maximum 100.
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Pagination cursor (recommended), taken from `next_page` in the previous response;
@@ -84,6 +86,8 @@ func (r *VaultCredentialService) New(ctx context.Context, id string, params Vaul
}
type VaultCredentialNewParams struct {
+ // Select identity ownership with a PAT. Omit when using a SAT.
+ IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"`
// Credential authentication material; supports `static_bearer` and `mcp_oauth`. The
// response only returns redacted, non-secret fields.
Auth map[string]any `json:"auth" api:"required"`
@@ -293,3 +297,7 @@ func (r MCPOAuthRefreshUpdateParam) MarshalJSON() ([]byte, error) {
func (r *MCPOAuthRefreshUpdateParam) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, r)
}
+
+func (r VaultCredentialNewParams) URLQuery() (url.Values, error) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ArrayFormat: apiquery.ArrayQueryFormatRepeat, NestedFormat: apiquery.NestedQueryFormatBrackets})
+}
diff --git a/managed/deployment.go b/managed/deployment.go
index cc2c345..71e998b 100644
--- a/managed/deployment.go
+++ b/managed/deployment.go
@@ -243,8 +243,8 @@ type ManagedAgentsDeployment struct {
// Vault IDs supplying stored credentials for sessions created from this
// deployment.
VaultIDs []string `json:"vault_ids" api:"required"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimit `json:"budget" api:"nullable"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
@@ -1848,8 +1848,8 @@ type DeploymentNewParams struct {
Name string `json:"name" api:"required"`
// Description of what the deployment does.
Description param.Opt[string] `json:"description,omitzero"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimitParam `json:"budget,omitzero"`
// Arbitrary key-value metadata. Maximum 16 pairs, keys up to 64 chars, values up
// to 512 chars.
@@ -2045,8 +2045,8 @@ type DeploymentUpdateParams struct {
// version, or an `agent` object with both id and version specified. Omit to
// preserve. Cannot be cleared.
Agent DeploymentUpdateParamsAgentUnion `json:"agent,omitzero"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimitParam `json:"budget,omitzero"`
// Initial events. Full replacement. Omit to preserve. Cannot be cleared. At least
// 1, maximum 50.
diff --git a/managed/qoder_types_test.go b/managed/qoder_types_test.go
index 0137ab4..1bc9df1 100644
--- a/managed/qoder_types_test.go
+++ b/managed/qoder_types_test.go
@@ -67,6 +67,11 @@ func TestQoderFieldAlignment(t *testing.T) {
}
for name, fields := range expected {
for _, f := range fields {
+ // QCA uses decimal Credits, not the upstream monetary budget.
+ // https://docs.qoder.com/cloud-agents/api/sessions/schemas#session-budget
+ if (name == "ManagedAgentsBudgetLimit" || name == "ManagedAgentsBudgetLimitParam") && f == "MaxListCost" {
+ f = "MaxCreditCost"
+ }
if !actual[name][f] {
t.Errorf("upstream field missing: %s.%s", name, f)
}
diff --git a/managed/session.go b/managed/session.go
index 29d7604..9064603 100644
--- a/managed/session.go
+++ b/managed/session.go
@@ -540,19 +540,19 @@ func (r *ManagedAgentsBranchCheckoutParam) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, r)
}
-// A hard spend ceiling. The session stops issuing new model requests once the
-// tracked list cost reaches `max_list_cost`.
+// A total model and sandbox credit limit. Once reached, new user messages are rejected;
+// an already running turn continues to completion.
type ManagedAgentsBudgetLimit struct {
- // A monetary amount in a specific currency.
- MaxListCost MonetaryAmount `json:"max_list_cost" api:"required"`
+ // Positive decimal Credits, with at most 12 integer and 2 fractional digits.
+ MaxCreditCost string `json:"max_credit_cost" api:"required"`
// Any of "limit".
Type ManagedAgentsBudgetLimitType `json:"type" api:"required"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
- MaxListCost respjson.Field
- Type respjson.Field
- ExtraFields map[string]respjson.Field
- raw string
+ MaxCreditCost respjson.Field
+ Type respjson.Field
+ ExtraFields map[string]respjson.Field
+ raw string
} `json:"-"`
}
@@ -578,13 +578,13 @@ const (
ManagedAgentsBudgetLimitTypeLimit ManagedAgentsBudgetLimitType = "limit"
)
-// A hard spend ceiling. The session stops issuing new model requests once the
-// tracked list cost reaches `max_list_cost`.
+// A total model and sandbox credit limit. Once reached, new user messages are rejected;
+// an already running turn continues to completion.
//
-// The properties MaxListCost, Type are required.
+// The properties MaxCreditCost, Type are required.
type ManagedAgentsBudgetLimitParam struct {
- // A monetary amount in a specific currency.
- MaxListCost MonetaryAmountParam `json:"max_list_cost,omitzero" api:"required"`
+ // Positive decimal Credits, with at most 12 integer and 2 fractional digits.
+ MaxCreditCost string `json:"max_credit_cost" api:"required"`
// Any of "limit".
Type ManagedAgentsBudgetLimitType `json:"type,omitzero" api:"required"`
paramObj
@@ -1226,9 +1226,9 @@ type ManagedAgentsSession struct {
Agent ManagedAgentsSessionAgent `json:"agent" api:"required"`
// A timestamp in RFC 3339 format
ArchivedAt time.Time `json:"archived_at" api:"required" format:"date-time"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
- Budget ManagedAgentsBudgetLimit `json:"budget" api:"required"`
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
+ Budget ManagedAgentsBudgetLimit `json:"budget" api:"nullable"`
// A timestamp in RFC 3339 format
CreatedAt time.Time `json:"created_at" api:"required" format:"date-time"`
EnvironmentID string `json:"environment_id" api:"required"`
@@ -1248,8 +1248,9 @@ type ManagedAgentsSession struct {
Type ManagedAgentsSessionType `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 across all turns.
- Usage ManagedAgentsSessionUsage `json:"usage" api:"required"`
+ // Cumulative Credits snapshot. Replace previous values; do not sum snapshots.
+ // Legacy token and list-cost fields remain available for older responses.
+ Usage ManagedAgentsSessionUsage `json:"usage"`
// Vault IDs attached to the session at creation. Empty when no vaults were
// supplied.
VaultIDs []string `json:"vault_ids" api:"required"`
@@ -1983,8 +1984,8 @@ type ManagedAgentsSessionUpdatedEvent struct {
// Resolved `agent` definition for a `session`. Snapshot of the `agent` at
// `session` creation time.
Agent ManagedAgentsSessionAgent `json:"agent" api:"nullable"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimit `json:"budget" api:"nullable"`
// The session's full metadata bag after the update. Present when the update set
// non-empty metadata; absent when metadata was unchanged or cleared to empty.
@@ -2017,8 +2018,12 @@ const (
ManagedAgentsSessionUpdatedEventTypeSessionUpdated ManagedAgentsSessionUpdatedEventType = "session.updated"
)
-// Cumulative token usage for a session across all turns.
+// Cumulative Credits snapshot. Replace previous values; do not sum snapshots.
+// Legacy token and list-cost fields remain available for older responses.
type ManagedAgentsSessionUsage struct {
+ ModelCredits float64 `json:"model_credits"`
+ SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"`
+ TotalCredits float64 `json:"total_credits"`
// Cumulative time in seconds during which the session had at least one thread in
// running status. Overlapping activity from concurrent threads is counted once,
// unlike `stats.active_seconds`, which sums each thread's own active time. This is
@@ -2038,15 +2043,18 @@ type ManagedAgentsSessionUsage struct {
ServerToolUse ManagedAgentsServerToolUsage `json:"server_tool_use" api:"nullable"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
- ActiveSeconds respjson.Field
- CacheCreation respjson.Field
- CacheReadInputTokens respjson.Field
- InputTokens respjson.Field
- ListCost respjson.Field
- OutputTokens respjson.Field
- ServerToolUse respjson.Field
- ExtraFields map[string]respjson.Field
- raw string
+ ModelCredits respjson.Field
+ SandboxRuntimeCredits respjson.Field
+ TotalCredits respjson.Field
+ ActiveSeconds respjson.Field
+ CacheCreation respjson.Field
+ CacheReadInputTokens respjson.Field
+ InputTokens respjson.Field
+ ListCost respjson.Field
+ OutputTokens respjson.Field
+ ServerToolUse respjson.Field
+ ExtraFields map[string]respjson.Field
+ raw string
} `json:"-"`
}
@@ -2056,8 +2064,12 @@ func (r *ManagedAgentsSessionUsage) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, r)
}
-// Periodic snapshot of the session's cumulative usage and tracked list cost.
+// Cumulative Credits emitted before session.status_idle when usage is available.
+// Credits are top-level fields; Usage and Budget are legacy response fields.
type ManagedAgentsSessionUsageEvent struct {
+ ModelCredits float64 `json:"model_credits"`
+ SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"`
+ TotalCredits float64 `json:"total_credits"`
// Unique identifier for this event.
ID string `json:"id" api:"required"`
// A timestamp in RFC 3339 format
@@ -2065,19 +2077,22 @@ type ManagedAgentsSessionUsageEvent struct {
// Any of "session.usage".
Type ManagedAgentsSessionUsageEventType `json:"type" api:"required"`
// Point-in-time snapshot of a session's cumulative usage.
- Usage ManagedAgentsSessionUsageSnapshot `json:"usage" api:"required"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ Usage ManagedAgentsSessionUsageSnapshot `json:"usage"`
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimit `json:"budget" api:"nullable"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
- ID respjson.Field
- ProcessedAt respjson.Field
- Type respjson.Field
- Usage respjson.Field
- Budget respjson.Field
- ExtraFields map[string]respjson.Field
- raw string
+ ModelCredits respjson.Field
+ SandboxRuntimeCredits respjson.Field
+ TotalCredits respjson.Field
+ ID respjson.Field
+ ProcessedAt respjson.Field
+ Type respjson.Field
+ Usage respjson.Field
+ Budget respjson.Field
+ ExtraFields map[string]respjson.Field
+ raw string
} `json:"-"`
}
@@ -2472,8 +2487,8 @@ type SessionNewParams struct {
EnvironmentID string `json:"environment_id" api:"required"`
// Human-readable session title.
Title param.Opt[string] `json:"title,omitzero"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimitParam `json:"budget,omitzero"`
// Initial events to send to the `session` at creation, processed in order.
// Supports `user.message` and `user.define_outcome` events. Maximum 50 events.
@@ -2804,8 +2819,8 @@ type SessionUpdateParams struct {
// updatable. Full replacement: the provided array becomes the new value. To
// preserve existing entries, GET the session, modify the array, and POST it back.
Agent ManagedAgentsSessionAgentUpdateParam `json:"agent,omitzero"`
- // A hard spend ceiling. The session stops issuing new model requests once the
- // tracked list cost reaches `max_list_cost`.
+ // A total model and sandbox credit limit. Once reached, new user messages are
+ // rejected; an already running turn continues to completion.
Budget ManagedAgentsBudgetLimitParam `json:"budget,omitzero"`
// Vault IDs (`vlt_*`) to attach to the session. Not yet supported; requests
// setting this field are rejected. Reserved for future use.
diff --git a/managed/session_credit_contract_test.go b/managed/session_credit_contract_test.go
new file mode 100644
index 0000000..17b9790
--- /dev/null
+++ b/managed/session_credit_contract_test.go
@@ -0,0 +1,124 @@
+package managed_test
+
+import (
+ "context"
+ "encoding/json"
+ "io"
+ "net/http"
+ "reflect"
+ "strings"
+ "testing"
+
+ "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/param"
+ "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward"
+ "github.com/QoderAI/qoder-cloud-agents-sdk-go/managed"
+)
+
+// Contract: https://docs.qoder.com/cloud-agents/api/sessions/schemas (2026-10-20).
+func TestSessionCreditBudgetWireContract(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ params managed.SessionUpdateParams
+ want string
+ }{
+ {"decimal", managed.SessionUpdateParams{Budget: managed.ManagedAgentsBudgetLimitParam{Type: "limit", MaxCreditCost: "100.00"}}, `{"budget":{"type":"limit","max_credit_cost":"100.00"}}`},
+ {"omit", managed.SessionUpdateParams{}, `{}`},
+ {"clear", managed.SessionUpdateParams{Budget: param.NullStruct[managed.ManagedAgentsBudgetLimitParam]()}, `{"budget":null}`},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ client := testClient(func(r *http.Request) (*http.Response, error) {
+ body, _ := io.ReadAll(r.Body)
+ var got, want any
+ if err := json.Unmarshal(body, &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", body, tc.want)
+ }
+ return reply(r, 200, `{"id":"sess_one","budget":{"type":"limit","max_credit_cost":"100.00"}}`), nil
+ })
+ session, err := client.Sessions.Update(context.Background(), "sess_one", tc.params)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if session.Budget.MaxCreditCost != "100.00" {
+ t.Fatalf("budget=%+v", session.Budget)
+ }
+ })
+ }
+}
+
+func TestSessionCreditsSnapshotsAndEvents(t *testing.T) {
+ const usage = `{"model_credits":7.66,"sandbox_runtime_credits":0.34,"total_credits":8}`
+ var session managed.ManagedAgentsSession
+ if err := json.Unmarshal([]byte(`{"id":"sess_one","usage":`+usage+`}`), &session); err != nil {
+ t.Fatal(err)
+ }
+ if session.Usage.ModelCredits != 7.66 || session.Usage.SandboxRuntimeCredits != 0.34 || session.Usage.TotalCredits != 8 {
+ t.Fatalf("usage=%+v", session.Usage)
+ }
+ var fsession forward.Session
+ if err := json.Unmarshal([]byte(`{"id":"sess_one","usage":`+usage+`}`), &fsession); err != nil {
+ t.Fatal(err)
+ }
+ if fsession.Usage.ModelCredits != 7.66 || fsession.Usage.SandboxRuntimeCredits != 0.34 || fsession.Usage.TotalCredits != 8 {
+ t.Fatalf("forward usage=%+v", fsession.Usage)
+ }
+ var empty managed.ManagedAgentsSession
+ if err := json.Unmarshal([]byte(`{"id":"sess_empty"}`), &empty); err != nil {
+ t.Fatal(err)
+ }
+ if empty.JSON.Usage.Valid() {
+ t.Fatal("missing usage must stay absent")
+ }
+ const event = `{"id":"evt_one","type":"session.usage","model_credits":7.66,"sandbox_runtime_credits":0.34,"total_credits":8}`
+ var history managed.ManagedAgentsSessionEventUnion
+ if err := json.Unmarshal([]byte(event), &history); err != nil {
+ t.Fatal(err)
+ }
+ if history.TotalCredits != 8 || history.AsSessionUsage().TotalCredits != 8 {
+ t.Fatal("history lost top-level credits")
+ }
+ var thread managed.ManagedAgentsStreamSessionThreadEventsUnion
+ if err := json.Unmarshal([]byte(event), &thread); err != nil {
+ t.Fatal(err)
+ }
+ if thread.TotalCredits != 8 || thread.AsSessionUsage().ModelCredits != 7.66 {
+ t.Fatal("thread stream lost credits")
+ }
+ client := testClient(func(r *http.Request) (*http.Response, error) {
+ res := reply(r, 200, "id: evt_one\nevent: session.usage\ndata: "+event+"\n\nid: evt_two\nevent: agent.thread_context_compacted\ndata: {\"id\":\"evt_two\",\"type\":\"agent.thread_context_compacted\",\"usage\":{\"credits\":0}}\n\nid: evt_three\nevent: span.model_request_end\ndata: {\"id\":\"evt_three\",\"type\":\"span.model_request_end\",\"model_usage\":{\"credits\":1.25}}\n\n")
+ res.Header.Set("Content-Type", "text/event-stream")
+ return res, nil
+ })
+ stream := client.Sessions.Events.StreamEvents(context.Background(), "sess_one", managed.SessionEventStreamParams{})
+ defer stream.Close()
+ var seen []string
+ for stream.Next() {
+ e := stream.Current()
+ seen = append(seen, e.Type)
+ switch e.Type {
+ case "session.usage":
+ if e.TotalCredits != 8 || e.AsSessionUsage().TotalCredits != 8 {
+ t.Fatal("SSE lost credits")
+ }
+ case "agent.thread_context_compacted":
+ if !e.Usage.JSON.Credits.Valid() || e.AsAgentThreadContextCompacted().Usage.Credits != 0 {
+ t.Fatal("zero credits must be preserved")
+ }
+ case "span.model_request_end":
+ if e.ModelUsage.Credits != 1.25 {
+ t.Fatal("model credits lost")
+ }
+ }
+ }
+ if err := stream.Err(); err != nil {
+ t.Fatal(err)
+ }
+ if strings.Join(seen, ",") != "session.usage,agent.thread_context_compacted,span.model_request_end" {
+ t.Fatal(seen)
+ }
+}
diff --git a/managed/sessionevent.go b/managed/sessionevent.go
index 3c82925..d9a1e48 100644
--- a/managed/sessionevent.go
+++ b/managed/sessionevent.go
@@ -513,6 +513,8 @@ const (
// Indicates that context compaction (summarization) occurred during the session.
type ManagedAgentsAgentThreadContextCompactedEvent struct {
+ // Credits for this compaction call, already included in Session totals.
+ Usage ManagedAgentsSpanModelUsage `json:"usage"`
// Unique identifier for this event.
ID string `json:"id" api:"required"`
// A timestamp in RFC 3339 format
@@ -521,6 +523,7 @@ type ManagedAgentsAgentThreadContextCompactedEvent struct {
Type ManagedAgentsAgentThreadContextCompactedEventType `json:"type" api:"required"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
+ Usage respjson.Field
ID respjson.Field
ProcessedAt respjson.Field
Type respjson.Field
@@ -3757,7 +3760,10 @@ const (
//
// Use the methods beginning with 'As' to cast the union to one of its variants.
type ManagedAgentsSessionEventUnion struct {
- ID string `json:"id"`
+ ModelCredits float64 `json:"model_credits"`
+ SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"`
+ TotalCredits float64 `json:"total_credits"`
+ ID string `json:"id"`
// This field is a union of [[]ManagedAgentsUserMessageEventContentUnion],
// [[]ManagedAgentsUserCustomToolResultEventContentUnion],
// [[]ManagedAgentsAgentMessageEventContentUnion],
@@ -3842,6 +3848,9 @@ type ManagedAgentsSessionEventUnion struct {
// This field is from variant [ManagedAgentsSessionUpdatedEvent].
Title string `json:"title"`
JSON struct {
+ ModelCredits respjson.Field
+ SandboxRuntimeCredits respjson.Field
+ TotalCredits respjson.Field
ID respjson.Field
Content respjson.Field
Type respjson.Field
@@ -4321,6 +4330,7 @@ func (r *ManagedAgentsSessionEventUnionStopReason) UnmarshalJSON(data []byte) er
// For type safety it is recommended to directly use a variant of the
// [ManagedAgentsSessionEventUnion].
type ManagedAgentsSessionEventUnionUsage struct {
+ Credits float64 `json:"credits"`
// This field is from variant [ManagedAgentsSpanModelUsage].
CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"`
CacheReadInputTokens int64 `json:"cache_read_input_tokens"`
@@ -4337,6 +4347,7 @@ type ManagedAgentsSessionEventUnionUsage struct {
// This field is from variant [ManagedAgentsSessionUsageSnapshot].
ServerToolUse ManagedAgentsServerToolUsage `json:"server_tool_use"`
JSON struct {
+ Credits respjson.Field
CacheCreationInputTokens respjson.Field
CacheReadInputTokens respjson.Field
InputTokens respjson.Field
@@ -4949,8 +4960,8 @@ type ManagedAgentsSpanModelRequestEndEvent struct {
IsError bool `json:"is_error" api:"required"`
// The id of the corresponding `span.model_request_start` event.
ModelRequestStartID string `json:"model_request_start_id" api:"required"`
- // Token usage for a single model request.
- ModelUsage ManagedAgentsSpanModelUsage `json:"model_usage" api:"required"`
+ // Credits for a single model request; legacy token fields may appear in older responses.
+ ModelUsage ManagedAgentsSpanModelUsage `json:"model_usage"`
// A timestamp in RFC 3339 format
ProcessedAt time.Time `json:"processed_at" api:"required" format:"date-time"`
// Any of "span.model_request_end".
@@ -5010,16 +5021,17 @@ const (
ManagedAgentsSpanModelRequestStartEventTypeSpanModelRequestStart ManagedAgentsSpanModelRequestStartEventType = "span.model_request_start"
)
-// Token usage for a single model request.
+// Credits for a single model request; legacy token fields may appear in older responses.
type ManagedAgentsSpanModelUsage struct {
+ Credits float64 `json:"credits"`
// Tokens used to create prompt cache in this request.
- CacheCreationInputTokens int64 `json:"cache_creation_input_tokens" api:"required"`
+ CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"`
// Tokens read from prompt cache in this request.
- CacheReadInputTokens int64 `json:"cache_read_input_tokens" api:"required"`
+ CacheReadInputTokens int64 `json:"cache_read_input_tokens"`
// Input tokens consumed by this request.
- InputTokens int64 `json:"input_tokens" api:"required"`
+ InputTokens int64 `json:"input_tokens"`
// Output tokens generated by this request.
- OutputTokens int64 `json:"output_tokens" api:"required"`
+ OutputTokens int64 `json:"output_tokens"`
// Inference speed mode. `fast` provides significantly faster output token
// generation at premium pricing. Not all models support `fast`; invalid
// combinations are rejected at create time.
@@ -5028,6 +5040,7 @@ type ManagedAgentsSpanModelUsage struct {
Speed ManagedAgentsSpanModelUsageSpeed `json:"speed" api:"nullable"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
+ Credits respjson.Field
CacheCreationInputTokens respjson.Field
CacheReadInputTokens respjson.Field
InputTokens respjson.Field
@@ -5083,7 +5096,7 @@ type ManagedAgentsSpanOutcomeEvaluationEndEvent struct {
Result string `json:"result" api:"required"`
// Any of "span.outcome_evaluation_end".
Type ManagedAgentsSpanOutcomeEvaluationEndEventType `json:"type" api:"required"`
- // Token usage for a single model request.
+ // Credits for a single model request; legacy token fields may appear in older responses.
Usage ManagedAgentsSpanModelUsage `json:"usage" api:"required"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
@@ -5230,7 +5243,10 @@ const (
//
// Use the methods beginning with 'As' to cast the union to one of its variants.
type ManagedAgentsStreamSessionEventsUnion struct {
- ID string `json:"id"`
+ ModelCredits float64 `json:"model_credits"`
+ SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"`
+ TotalCredits float64 `json:"total_credits"`
+ ID string `json:"id"`
// This field is a union of [[]ManagedAgentsUserMessageEventContentUnion],
// [[]ManagedAgentsUserCustomToolResultEventContentUnion],
// [[]ManagedAgentsAgentMessageEventContentUnion],
@@ -5321,6 +5337,9 @@ type ManagedAgentsStreamSessionEventsUnion struct {
// This field is from variant [ManagedAgentsDeltaEvent].
EventID string `json:"event_id"`
JSON struct {
+ ModelCredits respjson.Field
+ SandboxRuntimeCredits respjson.Field
+ TotalCredits respjson.Field
ID respjson.Field
Content respjson.Field
Type respjson.Field
@@ -5833,6 +5852,7 @@ func (r *ManagedAgentsStreamSessionEventsUnionStopReason) UnmarshalJSON(data []b
// For type safety it is recommended to directly use a variant of the
// [ManagedAgentsStreamSessionEventsUnion].
type ManagedAgentsStreamSessionEventsUnionUsage struct {
+ Credits float64 `json:"credits"`
// This field is from variant [ManagedAgentsSpanModelUsage].
CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"`
CacheReadInputTokens int64 `json:"cache_read_input_tokens"`
@@ -5849,6 +5869,7 @@ type ManagedAgentsStreamSessionEventsUnionUsage struct {
// This field is from variant [ManagedAgentsSessionUsageSnapshot].
ServerToolUse ManagedAgentsServerToolUsage `json:"server_tool_use"`
JSON struct {
+ Credits respjson.Field
CacheCreationInputTokens respjson.Field
CacheReadInputTokens respjson.Field
InputTokens respjson.Field
diff --git a/managed/sessionthread.go b/managed/sessionthread.go
index d22c0b3..9e5ac57 100644
--- a/managed/sessionthread.go
+++ b/managed/sessionthread.go
@@ -407,7 +407,10 @@ func (r *ManagedAgentsSessionThreadUsage) UnmarshalJSON(data []byte) error {
//
// Use the methods beginning with 'As' to cast the union to one of its variants.
type ManagedAgentsStreamSessionThreadEventsUnion struct {
- ID string `json:"id"`
+ ModelCredits float64 `json:"model_credits"`
+ SandboxRuntimeCredits float64 `json:"sandbox_runtime_credits"`
+ TotalCredits float64 `json:"total_credits"`
+ ID string `json:"id"`
// This field is a union of [[]ManagedAgentsUserMessageEventContentUnion],
// [[]ManagedAgentsUserCustomToolResultEventContentUnion],
// [[]ManagedAgentsAgentMessageEventContentUnion],
@@ -498,6 +501,9 @@ type ManagedAgentsStreamSessionThreadEventsUnion struct {
// This field is from variant [ManagedAgentsDeltaEvent].
EventID string `json:"event_id"`
JSON struct {
+ ModelCredits respjson.Field
+ SandboxRuntimeCredits respjson.Field
+ TotalCredits respjson.Field
ID respjson.Field
Content respjson.Field
Type respjson.Field
@@ -1021,6 +1027,7 @@ func (r *ManagedAgentsStreamSessionThreadEventsUnionStopReason) UnmarshalJSON(da
// For type safety it is recommended to directly use a variant of the
// [ManagedAgentsStreamSessionThreadEventsUnion].
type ManagedAgentsStreamSessionThreadEventsUnionUsage struct {
+ Credits float64 `json:"credits"`
// This field is from variant [ManagedAgentsSpanModelUsage].
CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"`
CacheReadInputTokens int64 `json:"cache_read_input_tokens"`
@@ -1037,6 +1044,7 @@ type ManagedAgentsStreamSessionThreadEventsUnionUsage struct {
// This field is from variant [ManagedAgentsSessionUsageSnapshot].
ServerToolUse ManagedAgentsServerToolUsage `json:"server_tool_use"`
JSON struct {
+ Credits respjson.Field
CacheCreationInputTokens respjson.Field
CacheReadInputTokens respjson.Field
InputTokens respjson.Field