From 1efdd18c04a243d65bb5a095fd83ae11a0c3ec4f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:43:25 +0800 Subject: [PATCH 1/2] feat(api): align SDK with current official contracts --- CHANGELOG.md | 22 + README.md | 71 + docs/api/reference.md | 1533 +++++++++++++++++++-- examples/forward/memory/main.go | 2 +- forward/channelpairing.go | 69 + forward/client.go | 2 + forward/client_test.go | 4 +- forward/configuration_types.go | 105 +- forward/dream.go | 262 ++++ forward/identity.go | 24 +- forward/identitymemorystore.go | 36 +- forward/identitymemorystore_test.go | 2 +- forward/memorystore_live_test.go | 2 +- forward/official_contract_test.go | 215 +++ forward/outcomerubric.go | 39 + forward/protocol_test.go | 2 +- forward/schedule.go | 38 + forward/schedulerun.go | 3 + forward/sessionevent.go | 2 +- forward/test_helpers_test.go | 4 +- forward/testdata/api-contracts.json | 310 +++++ forward/testdata/api-operation-cases.json | 278 +++- managed/agent.go | 70 +- managed/agent_test.go | 9 +- managed/bucketstorage.go | 109 ++ managed/client.go | 4 +- managed/client_test.go | 2 +- managed/dream.go | 69 +- managed/environment.go | 14 +- managed/memorystore.go | 4 +- managed/oauth.go | 60 + managed/official_contract_test.go | 111 ++ managed/qoder_types_test.go | 5 + managed/test_helpers_test.go | 2 +- managed/testdata/api-contracts.json | 14 + 35 files changed, 3219 insertions(+), 279 deletions(-) create mode 100644 forward/dream.go create mode 100644 forward/official_contract_test.go create mode 100644 forward/outcomerubric.go create mode 100644 managed/bucketstorage.go create mode 100644 managed/oauth.go create mode 100644 managed/official_contract_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bb7268..ac87012 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,28 @@ existing `0.1.0` release; earlier development prereleases are not listed. ## [Unreleased] +### Added + +- Add Forward Dreams (create, list, retrieve, cancel, archive), channel pairing list/retrieve/update, and Managed OAuth start with typed requests and responses. +- Add Identity automatic memory-store control, mount access and cursor pagination, sparse managed-capability overrides, and full tools-array replacement. +- Support string Template references and Advisor model entries in Forward Multiagent rosters, Schedule file resources, and optional Run input snapshots. +- Add typed Managed OSS bucket storage with separate write credentials/read views, and Memory Store ID cursor parameters. + +### Fixed + +- Align Forward Outcome rubric objects and Managed Agent/Dream model configuration with the official API. Dream model defaults to `auto` when omitted; object configuration supports `effort` and `context_window`. + +### Breaking changes + +- Forward event `rubric` is a text/content or file/file_id object instead of a string. +- Managed Agent `effort` is a level string instead of an object; Agent and Dream model configuration no longer declares the unsupported `speed` field. +- Identity mount `List` now takes `IdentityMemoryStoreListParams` and returns `pagination.Page[MemoryStoreMount]`; supply an empty params struct to preserve an unfiltered call. +- Wrap Identity tool maps in `IdentityToolsUnionParam{OfOverrides: ...}`; use `OfArray` for replacement. Read maps through `Tools.OfOverrides`. + +### Migration + +- See the README's “Official contract alignment” section for updated model/rubric types, pagination calls and explicit null clearing. + ## [0.12.0] ### Added diff --git a/README.md b/README.md index fa03b24..7073f84 100644 --- a/README.md +++ b/README.md @@ -1232,3 +1232,74 @@ sessions, err := managedClient.Sessions.Search(ctx, managed.SessionSearchParams{ Each resource exposes `Search` for a page and `SearchAutoPaging` for iteration. Use the page's `GetNextPage()` for manual pagination. + +## Official contract alignment + +The next release aligns these public types with the official API documentation +retrieved on 2026-10-27. It introduces source changes relative to 0.12.0: +Outcome `rubric` is an object; Managed Agent `effort` is a string; Managed Agent +and Dream model objects do not accept `speed`. Managed Dream `model` is optional +and defaults to `auto`. Discover model IDs and supported efforts/context windows +with List models. These Managed model changes do not remove Forward model features. + +```go +rubric := forward.OutcomeRubricParam{Type: "text", Content: forward.String("report exists")} +model := managed.ManagedAgentsModelConfigParams{ID: "ultimate", Effort: managed.String("high")} +// Dream model can be omitted; its default is auto. +dream, err := client.Dreams.New(ctx, forward.DreamNewParams{ + Inputs: []forward.DreamInputParam{{Type: "identity_template", + IdentityID: forward.String("idn_..."), TemplateID: forward.String("tmpl_...")}}, +}) +``` + +Mount list callers must supply query params, even if empty. The result now uses +the standard page type. Use `ListAutoPaging` to traverse every page: + +```go +pager := client.Identities.MemoryStores.ListAutoPaging(ctx, "idn_...", "tmpl_...", + forward.IdentityMemoryStoreListParams{Limit: forward.Int(20)}) +for pager.Next() { + fmt.Println(pager.Current().MemoryStoreID) +} +if err := pager.Err(); err != nil { /* handle error */ } +``` + +Identity tool maps now use `IdentityToolsUnionParam{OfOverrides: toolMap}`; +`OfArray: []forward.ToolParam{}` explicitly replaces all tools with an empty list. +Use `param.NullStruct[forward.IdentityToolsUnionParam]()` to restore inherited +tools, and `param.NullMap[map[string]forward.ManagedCapabilityOverrideParam]()` +to clear all managed-capability overrides. A selector value of +`param.NullStruct[forward.ManagedCapabilityOverrideParam]()` clears that selector. +For roster string entries use `MultiagentEntryParam{OfString: forward.String("tmpl_...")}`; +Advisor entries use `Type: "advisor", Model: forward.String("ultimate")`. + +New Forward Dream calls require a PAT or an admin SAT; an Identity SAT is not +supported. Inputs contain exactly one primary Memory Store or Identity/Template +source, optionally plus Session sources. Pairing `update` changes an active +binding; supply at least one nonempty Identity or Template ID. Use pairing +`create` to complete a pending pairing. Pending records preserve empty +`identity_id`, `template_id`, and `paired_at` strings. + +Identity `auto_memory_store=false` disables automatic default-store creation. +Independent mounts now accept `access="read_write"`; omit access for a new +read-only mount, or to preserve an existing mount's permission. Schedule +`resources` accepts file entries and `[]` clears them; Run retrieval supports +`include_initial_events=true` to return the saved input snapshot. + +Managed cloud Environment `config.storage.buckets` supports up to eight OSS +mounts. When the resulting configuration has buckets, explicitly include beta +`oss-bucket-2026-09-23` (`Betas` in Go, `betas` in Python/TS). Send credentials +only when creating or replacing them; omit `credential` on an existing named +bucket to keep it. Responses expose a read view without role ARN or AccessKey +secret. `storage: {buckets: []}` clears mounts. + +Managed `OAuth.Start` (Go) / `oauth.start` (Python/TS) returns `authorization_url`, +`state`, and `callback_origin`; the user completes authorization in a browser. +Client secrets go in the POST body, never in a URL. This method does not open a +browser or complete consent automatically. + +See the official [Outcome contract](https://docs.qoder.com/cloud-agents/api/forward/sessions/send-events), +[Identity overrides](https://docs.qoder.com/cloud-agents/api/forward/identities/upsert-config), +[Dream inputs](https://docs.qoder.com/cloud-agents/api/forward/dreams/create), +[OSS storage](https://docs.qoder.com/cloud-agents/api/environments/storage-buckets), +and [OAuth flow](https://docs.qoder.com/cloud-agents/api/vaults/start-oauth). diff --git a/docs/api/reference.md b/docs/api/reference.md index 11d28f2..a3732bd 100644 --- a/docs/api/reference.md +++ b/docs/api/reference.md @@ -82,13 +82,22 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" - [type ChannelPairing](<#ChannelPairing>) - [func \(r ChannelPairing\) RawJSON\(\) string](<#ChannelPairing.RawJSON>) - [func \(r \*ChannelPairing\) UnmarshalJSON\(data \[\]byte\) error](<#ChannelPairing.UnmarshalJSON>) +- [type ChannelPairingListParams](<#ChannelPairingListParams>) + - [func \(r ChannelPairingListParams\) URLQuery\(\) \(url.Values, error\)](<#ChannelPairingListParams.URLQuery>) - [type ChannelPairingNewParams](<#ChannelPairingNewParams>) - [func \(r ChannelPairingNewParams\) MarshalJSON\(\) \(\[\]byte, error\)](<#ChannelPairingNewParams.MarshalJSON>) - [func \(r \*ChannelPairingNewParams\) UnmarshalJSON\(data \[\]byte\) error](<#ChannelPairingNewParams.UnmarshalJSON>) - [type ChannelPairingService](<#ChannelPairingService>) - [func NewChannelPairingService\(opts ...option.RequestOption\) ChannelPairingService](<#NewChannelPairingService>) - [func \(r \*ChannelPairingService\) Delete\(ctx context.Context, pairingID string, opts ...option.RequestOption\) \(res \*DeletedChannelPairing, err error\)](<#ChannelPairingService.Delete>) + - [func \(r \*ChannelPairingService\) Get\(ctx context.Context, pairingID string, opts ...option.RequestOption\) \(res \*ChannelPairing, err error\)](<#ChannelPairingService.Get>) + - [func \(r \*ChannelPairingService\) List\(ctx context.Context, params ChannelPairingListParams, opts ...option.RequestOption\) \(res \*pagination.Page\[ChannelPairing\], err error\)](<#ChannelPairingService.List>) + - [func \(r \*ChannelPairingService\) ListAutoPaging\(ctx context.Context, params ChannelPairingListParams, opts ...option.RequestOption\) \*pagination.PageAutoPager\[ChannelPairing\]](<#ChannelPairingService.ListAutoPaging>) - [func \(r \*ChannelPairingService\) New\(ctx context.Context, params ChannelPairingNewParams, opts ...option.RequestOption\) \(res \*ChannelPairing, err error\)](<#ChannelPairingService.New>) + - [func \(r \*ChannelPairingService\) Update\(ctx context.Context, pairingID string, params ChannelPairingUpdateParams, opts ...option.RequestOption\) \(res \*ChannelPairing, err error\)](<#ChannelPairingService.Update>) +- [type ChannelPairingUpdateParams](<#ChannelPairingUpdateParams>) + - [func \(r ChannelPairingUpdateParams\) MarshalJSON\(\) \(\[\]byte, error\)](<#ChannelPairingUpdateParams.MarshalJSON>) + - [func \(r \*ChannelPairingUpdateParams\) UnmarshalJSON\(data \[\]byte\) error](<#ChannelPairingUpdateParams.UnmarshalJSON>) - [type ChannelQRSession](<#ChannelQRSession>) - [func \(r ChannelQRSession\) RawJSON\(\) string](<#ChannelQRSession.RawJSON>) - [func \(r \*ChannelQRSession\) UnmarshalJSON\(data \[\]byte\) error](<#ChannelQRSession.UnmarshalJSON>) @@ -179,6 +188,46 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" - [type DeletedSkillVersion](<#DeletedSkillVersion>) - [func \(r DeletedSkillVersion\) RawJSON\(\) string](<#DeletedSkillVersion.RawJSON>) - [func \(r \*DeletedSkillVersion\) UnmarshalJSON\(data \[\]byte\) error](<#DeletedSkillVersion.UnmarshalJSON>) +- [type Dream](<#Dream>) + - [func \(r Dream\) RawJSON\(\) string](<#Dream.RawJSON>) + - [func \(r \*Dream\) UnmarshalJSON\(data \[\]byte\) error](<#Dream.UnmarshalJSON>) +- [type DreamError](<#DreamError>) + - [func \(r DreamError\) RawJSON\(\) string](<#DreamError.RawJSON>) + - [func \(r \*DreamError\) UnmarshalJSON\(data \[\]byte\) error](<#DreamError.UnmarshalJSON>) +- [type DreamInput](<#DreamInput>) + - [func \(r DreamInput\) RawJSON\(\) string](<#DreamInput.RawJSON>) + - [func \(r \*DreamInput\) UnmarshalJSON\(data \[\]byte\) error](<#DreamInput.UnmarshalJSON>) +- [type DreamInputParam](<#DreamInputParam>) + - [func \(r DreamInputParam\) MarshalJSON\(\) \(\[\]byte, error\)](<#DreamInputParam.MarshalJSON>) + - [func \(r \*DreamInputParam\) UnmarshalJSON\(data \[\]byte\) error](<#DreamInputParam.UnmarshalJSON>) +- [type DreamListParams](<#DreamListParams>) + - [func \(r DreamListParams\) URLQuery\(\) \(url.Values, error\)](<#DreamListParams.URLQuery>) +- [type DreamModelConfig](<#DreamModelConfig>) + - [func \(r DreamModelConfig\) RawJSON\(\) string](<#DreamModelConfig.RawJSON>) + - [func \(r \*DreamModelConfig\) UnmarshalJSON\(data \[\]byte\) error](<#DreamModelConfig.UnmarshalJSON>) +- [type DreamModelConfigParam](<#DreamModelConfigParam>) + - [func \(r DreamModelConfigParam\) MarshalJSON\(\) \(\[\]byte, error\)](<#DreamModelConfigParam.MarshalJSON>) + - [func \(r \*DreamModelConfigParam\) UnmarshalJSON\(data \[\]byte\) error](<#DreamModelConfigParam.UnmarshalJSON>) +- [type DreamNewParams](<#DreamNewParams>) + - [func \(r DreamNewParams\) MarshalJSON\(\) \(\[\]byte, error\)](<#DreamNewParams.MarshalJSON>) + - [func \(r \*DreamNewParams\) UnmarshalJSON\(data \[\]byte\) error](<#DreamNewParams.UnmarshalJSON>) +- [type DreamNewParamsModelUnion](<#DreamNewParamsModelUnion>) + - [func \(r DreamNewParamsModelUnion\) MarshalJSON\(\) \(\[\]byte, error\)](<#DreamNewParamsModelUnion.MarshalJSON>) + - [func \(r \*DreamNewParamsModelUnion\) UnmarshalJSON\(data \[\]byte\) error](<#DreamNewParamsModelUnion.UnmarshalJSON>) +- [type DreamOutput](<#DreamOutput>) + - [func \(r DreamOutput\) RawJSON\(\) string](<#DreamOutput.RawJSON>) + - [func \(r \*DreamOutput\) UnmarshalJSON\(data \[\]byte\) error](<#DreamOutput.UnmarshalJSON>) +- [type DreamService](<#DreamService>) + - [func NewDreamService\(opts ...option.RequestOption\) DreamService](<#NewDreamService>) + - [func \(r \*DreamService\) Archive\(ctx context.Context, dreamID string, opts ...option.RequestOption\) \(res \*Dream, err error\)](<#DreamService.Archive>) + - [func \(r \*DreamService\) Cancel\(ctx context.Context, dreamID string, opts ...option.RequestOption\) \(res \*Dream, err error\)](<#DreamService.Cancel>) + - [func \(r \*DreamService\) Get\(ctx context.Context, dreamID string, opts ...option.RequestOption\) \(res \*Dream, err error\)](<#DreamService.Get>) + - [func \(r \*DreamService\) List\(ctx context.Context, params DreamListParams, opts ...option.RequestOption\) \(res \*pagination.Page\[Dream\], err error\)](<#DreamService.List>) + - [func \(r \*DreamService\) ListAutoPaging\(ctx context.Context, params DreamListParams, opts ...option.RequestOption\) \*pagination.PageAutoPager\[Dream\]](<#DreamService.ListAutoPaging>) + - [func \(r \*DreamService\) New\(ctx context.Context, params DreamNewParams, opts ...option.RequestOption\) \(res \*Dream, err error\)](<#DreamService.New>) +- [type DreamUsage](<#DreamUsage>) + - [func \(r DreamUsage\) RawJSON\(\) string](<#DreamUsage.RawJSON>) + - [func \(r \*DreamUsage\) UnmarshalJSON\(data \[\]byte\) error](<#DreamUsage.UnmarshalJSON>) - [type DriveClearParams](<#DriveClearParams>) - [func \(r DriveClearParams\) URLQuery\(\) \(url.Values, error\)](<#DriveClearParams.URLQuery>) - [func \(r \*DriveClearParams\) UnmarshalJSON\(data \[\]byte\) error](<#DriveClearParams.UnmarshalJSON>) @@ -359,6 +408,8 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" - [type IdentityListTemplatesResponse](<#IdentityListTemplatesResponse>) - [func \(r IdentityListTemplatesResponse\) RawJSON\(\) string](<#IdentityListTemplatesResponse.RawJSON>) - [func \(r \*IdentityListTemplatesResponse\) UnmarshalJSON\(data \[\]byte\) error](<#IdentityListTemplatesResponse.UnmarshalJSON>) +- [type IdentityMemoryStoreListParams](<#IdentityMemoryStoreListParams>) + - [func \(r IdentityMemoryStoreListParams\) URLQuery\(\) \(url.Values, error\)](<#IdentityMemoryStoreListParams.URLQuery>) - [type IdentityMemoryStoreListResponse](<#IdentityMemoryStoreListResponse>) - [func \(r IdentityMemoryStoreListResponse\) RawJSON\(\) string](<#IdentityMemoryStoreListResponse.RawJSON>) - [func \(r \*IdentityMemoryStoreListResponse\) UnmarshalJSON\(data \[\]byte\) error](<#IdentityMemoryStoreListResponse.UnmarshalJSON>) @@ -368,7 +419,8 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" - [type IdentityMemoryStoreService](<#IdentityMemoryStoreService>) - [func NewIdentityMemoryStoreService\(opts ...option.RequestOption\) IdentityMemoryStoreService](<#NewIdentityMemoryStoreService>) - [func \(r \*IdentityMemoryStoreService\) Detach\(ctx context.Context, identityID string, templateID string, memoryStoreID string, opts ...option.RequestOption\) \(res \*DeletedMemoryStoreMount, err error\)](<#IdentityMemoryStoreService.Detach>) - - [func \(r \*IdentityMemoryStoreService\) List\(ctx context.Context, identityID string, templateID string, opts ...option.RequestOption\) \(res \*IdentityMemoryStoreListResponse, err error\)](<#IdentityMemoryStoreService.List>) + - [func \(r \*IdentityMemoryStoreService\) List\(ctx context.Context, identityID string, templateID string, params IdentityMemoryStoreListParams, opts ...option.RequestOption\) \(res \*pagination.Page\[MemoryStoreMount\], err error\)](<#IdentityMemoryStoreService.List>) + - [func \(r \*IdentityMemoryStoreService\) ListAutoPaging\(ctx context.Context, identityID, templateID string, params IdentityMemoryStoreListParams, opts ...option.RequestOption\) \*pagination.PageAutoPager\[MemoryStoreMount\]](<#IdentityMemoryStoreService.ListAutoPaging>) - [func \(r \*IdentityMemoryStoreService\) Mount\(ctx context.Context, identityID string, templateID string, params IdentityMemoryStoreMountParams, opts ...option.RequestOption\) \(res \*MemoryStoreMount, err error\)](<#IdentityMemoryStoreService.Mount>) - [type IdentityNewParams](<#IdentityNewParams>) - [func \(r IdentityNewParams\) MarshalJSON\(\) \(\[\]byte, error\)](<#IdentityNewParams.MarshalJSON>) @@ -396,6 +448,12 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" - [type IdentityTemplate](<#IdentityTemplate>) - [func \(r IdentityTemplate\) RawJSON\(\) string](<#IdentityTemplate.RawJSON>) - [func \(r \*IdentityTemplate\) UnmarshalJSON\(data \[\]byte\) error](<#IdentityTemplate.UnmarshalJSON>) +- [type IdentityToolsUnion](<#IdentityToolsUnion>) + - [func \(r IdentityToolsUnion\) RawJSON\(\) string](<#IdentityToolsUnion.RawJSON>) + - [func \(r \*IdentityToolsUnion\) UnmarshalJSON\(data \[\]byte\) error](<#IdentityToolsUnion.UnmarshalJSON>) +- [type IdentityToolsUnionParam](<#IdentityToolsUnionParam>) + - [func \(r IdentityToolsUnionParam\) MarshalJSON\(\) \(\[\]byte, error\)](<#IdentityToolsUnionParam.MarshalJSON>) + - [func \(r \*IdentityToolsUnionParam\) UnmarshalJSON\(data \[\]byte\) error](<#IdentityToolsUnionParam.UnmarshalJSON>) - [type IdentityUpdateParams](<#IdentityUpdateParams>) - [func \(r IdentityUpdateParams\) MarshalJSON\(\) \(\[\]byte, error\)](<#IdentityUpdateParams.MarshalJSON>) - [func \(r \*IdentityUpdateParams\) UnmarshalJSON\(data \[\]byte\) error](<#IdentityUpdateParams.UnmarshalJSON>) @@ -426,6 +484,12 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" - [type MCPServerParam](<#MCPServerParam>) - [func \(r MCPServerParam\) MarshalJSON\(\) \(\[\]byte, error\)](<#MCPServerParam.MarshalJSON>) - [func \(r \*MCPServerParam\) UnmarshalJSON\(data \[\]byte\) error](<#MCPServerParam.UnmarshalJSON>) +- [type ManagedCapabilityOverride](<#ManagedCapabilityOverride>) + - [func \(r ManagedCapabilityOverride\) RawJSON\(\) string](<#ManagedCapabilityOverride.RawJSON>) + - [func \(r \*ManagedCapabilityOverride\) UnmarshalJSON\(data \[\]byte\) error](<#ManagedCapabilityOverride.UnmarshalJSON>) +- [type ManagedCapabilityOverrideParam](<#ManagedCapabilityOverrideParam>) + - [func \(r ManagedCapabilityOverrideParam\) MarshalJSON\(\) \(\[\]byte, error\)](<#ManagedCapabilityOverrideParam.MarshalJSON>) + - [func \(r \*ManagedCapabilityOverrideParam\) UnmarshalJSON\(data \[\]byte\) error](<#ManagedCapabilityOverrideParam.UnmarshalJSON>) - [type ManagedToolConfig](<#ManagedToolConfig>) - [func \(r ManagedToolConfig\) RawJSON\(\) string](<#ManagedToolConfig.RawJSON>) - [func \(r \*ManagedToolConfig\) UnmarshalJSON\(data \[\]byte\) error](<#ManagedToolConfig.UnmarshalJSON>) @@ -514,10 +578,18 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" - [func \(r \*MultiagentConfigParam\) UnmarshalJSON\(data \[\]byte\) error](<#MultiagentConfigParam.UnmarshalJSON>) - [type MultiagentEntry](<#MultiagentEntry>) - [func \(r MultiagentEntry\) RawJSON\(\) string](<#MultiagentEntry.RawJSON>) + - [func \(r \*MultiagentEntry\) UnmarshalAPIJSON\(data \[\]byte\) error](<#MultiagentEntry.UnmarshalAPIJSON>) - [func \(r \*MultiagentEntry\) UnmarshalJSON\(data \[\]byte\) error](<#MultiagentEntry.UnmarshalJSON>) - [type MultiagentEntryParam](<#MultiagentEntryParam>) - [func \(r MultiagentEntryParam\) MarshalJSON\(\) \(\[\]byte, error\)](<#MultiagentEntryParam.MarshalJSON>) + - [func \(r \*MultiagentEntryParam\) UnmarshalAPIJSON\(data \[\]byte\) error](<#MultiagentEntryParam.UnmarshalAPIJSON>) - [func \(r \*MultiagentEntryParam\) UnmarshalJSON\(data \[\]byte\) error](<#MultiagentEntryParam.UnmarshalJSON>) +- [type OutcomeRubric](<#OutcomeRubric>) + - [func \(r OutcomeRubric\) RawJSON\(\) string](<#OutcomeRubric.RawJSON>) + - [func \(r \*OutcomeRubric\) UnmarshalJSON\(data \[\]byte\) error](<#OutcomeRubric.UnmarshalJSON>) +- [type OutcomeRubricParam](<#OutcomeRubricParam>) + - [func \(r OutcomeRubricParam\) MarshalJSON\(\) \(\[\]byte, error\)](<#OutcomeRubricParam.MarshalJSON>) + - [func \(r \*OutcomeRubricParam\) UnmarshalJSON\(data \[\]byte\) error](<#OutcomeRubricParam.UnmarshalJSON>) - [type PermissionPolicy](<#PermissionPolicy>) - [func \(r PermissionPolicy\) RawJSON\(\) string](<#PermissionPolicy.RawJSON>) - [func \(r \*PermissionPolicy\) UnmarshalJSON\(data \[\]byte\) error](<#PermissionPolicy.UnmarshalJSON>) @@ -627,6 +699,12 @@ import "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" - [type ScheduleExecution](<#ScheduleExecution>) - [func \(r ScheduleExecution\) RawJSON\(\) string](<#ScheduleExecution.RawJSON>) - [func \(r \*ScheduleExecution\) UnmarshalJSON\(data \[\]byte\) error](<#ScheduleExecution.UnmarshalJSON>) +- [type ScheduleFileResource](<#ScheduleFileResource>) + - [func \(r ScheduleFileResource\) RawJSON\(\) string](<#ScheduleFileResource.RawJSON>) + - [func \(r \*ScheduleFileResource\) UnmarshalJSON\(data \[\]byte\) error](<#ScheduleFileResource.UnmarshalJSON>) +- [type ScheduleFileResourceParam](<#ScheduleFileResourceParam>) + - [func \(r ScheduleFileResourceParam\) MarshalJSON\(\) \(\[\]byte, error\)](<#ScheduleFileResourceParam.MarshalJSON>) + - [func \(r \*ScheduleFileResourceParam\) UnmarshalJSON\(data \[\]byte\) error](<#ScheduleFileResourceParam.UnmarshalJSON>) - [type ScheduleInitialEventsItem](<#ScheduleInitialEventsItem>) - [func \(r ScheduleInitialEventsItem\) RawJSON\(\) string](<#ScheduleInitialEventsItem.RawJSON>) - [func \(r \*ScheduleInitialEventsItem\) UnmarshalJSON\(data \[\]byte\) error](<#ScheduleInitialEventsItem.UnmarshalJSON>) @@ -2031,6 +2109,35 @@ func (r *ChannelPairing) UnmarshalJSON(data []byte) error + +## type [ChannelPairingListParams]() + + + +```go +type ChannelPairingListParams struct { + ChannelID param.Opt[string] `query:"channel_id,omitzero" json:"-"` + Status param.Opt[string] `query:"status,omitzero" json:"-"` + TargetType param.Opt[string] `query:"target_type,omitzero" json:"-"` + TargetExternalID param.Opt[string] `query:"target_external_id,omitzero" json:"-"` + IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"` + TemplateID param.Opt[string] `query:"template_id,omitzero" json:"-"` + Limit param.Opt[int64] `query:"limit,omitzero" json:"-"` + AfterID param.Opt[string] `query:"after_id,omitzero" json:"-"` + BeforeID param.Opt[string] `query:"before_id,omitzero" json:"-"` + // contains filtered or unexported fields +} +``` + + +### func \(ChannelPairingListParams\) [URLQuery]() + +```go +func (r ChannelPairingListParams) URLQuery() (url.Values, error) +``` + + + ## type [ChannelPairingNewParams]() @@ -2100,6 +2207,33 @@ func (r *ChannelPairingService) Delete(ctx context.Context, pairingID string, op Remove Channel pairing + +### func \(\*ChannelPairingService\) [Get]() + +```go +func (r *ChannelPairingService) Get(ctx context.Context, pairingID string, opts ...option.RequestOption) (res *ChannelPairing, err error) +``` + + + + +### func \(\*ChannelPairingService\) [List]() + +```go +func (r *ChannelPairingService) List(ctx context.Context, params ChannelPairingListParams, opts ...option.RequestOption) (res *pagination.Page[ChannelPairing], err error) +``` + + + + +### func \(\*ChannelPairingService\) [ListAutoPaging]() + +```go +func (r *ChannelPairingService) ListAutoPaging(ctx context.Context, params ChannelPairingListParams, opts ...option.RequestOption) *pagination.PageAutoPager[ChannelPairing] +``` + + + ### func \(\*ChannelPairingService\) [New]() @@ -2109,6 +2243,46 @@ func (r *ChannelPairingService) New(ctx context.Context, params ChannelPairingNe Complete Channel pairing + +### func \(\*ChannelPairingService\) [Update]() + +```go +func (r *ChannelPairingService) Update(ctx context.Context, pairingID string, params ChannelPairingUpdateParams, opts ...option.RequestOption) (res *ChannelPairing, err error) +``` + +Update changes an active binding. Supply at least one nonempty field. + + +## type [ChannelPairingUpdateParams]() + + + +```go +type ChannelPairingUpdateParams struct { + IdentityID param.Opt[string] `json:"identity_id,omitzero"` + TemplateID param.Opt[string] `json:"template_id,omitzero"` + // contains filtered or unexported fields +} +``` + + +### func \(ChannelPairingUpdateParams\) [MarshalJSON]() + +```go +func (r ChannelPairingUpdateParams) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*ChannelPairingUpdateParams\) [UnmarshalJSON]() + +```go +func (r *ChannelPairingUpdateParams) UnmarshalJSON(data []byte) error +``` + + + ## type [ChannelQRSession]() @@ -2350,6 +2524,7 @@ Client exposes Forward resources. Configure options before sharing a client. ```go type Client struct { + Dreams DreamService Webhooks WebhookService Options []option.RequestOption Templates TemplateService @@ -3229,6 +3404,483 @@ func (r *DeletedSkillVersion) UnmarshalJSON(data []byte) error + +## type [Dream]() + + + +```go +type Dream struct { + ID string `json:"id"` + Type string `json:"type"` + Status string `json:"status"` + Inputs []DreamInput `json:"inputs"` + Outputs []DreamOutput `json:"outputs"` + Model DreamModelConfig `json:"model"` + Instructions string `json:"instructions"` + Usage DreamUsage `json:"usage"` + Error DreamError `json:"error"` + CreatedAt time.Time `json:"created_at"` + EndedAt time.Time `json:"ended_at"` + ArchivedAt time.Time `json:"archived_at"` + JSON struct { + ID respjson.Field + Type respjson.Field + Status respjson.Field + Inputs respjson.Field + Outputs respjson.Field + Model respjson.Field + Instructions respjson.Field + Usage respjson.Field + Error respjson.Field + CreatedAt respjson.Field + EndedAt respjson.Field + ArchivedAt respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(Dream\) [RawJSON]() + +```go +func (r Dream) RawJSON() string +``` + + + + +### func \(\*Dream\) [UnmarshalJSON]() + +```go +func (r *Dream) UnmarshalJSON(data []byte) error +``` + + + + +## type [DreamError]() + + + +```go +type DreamError struct { + Type string `json:"type"` + Code string `json:"code"` + Message string `json:"message"` + JSON struct { + Type respjson.Field + Code respjson.Field + Message respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(DreamError\) [RawJSON]() + +```go +func (r DreamError) RawJSON() string +``` + + + + +### func \(\*DreamError\) [UnmarshalJSON]() + +```go +func (r *DreamError) UnmarshalJSON(data []byte) error +``` + + + + +## type [DreamInput]() + + + +```go +type DreamInput struct { + Type string `json:"type"` + MemoryStoreID string `json:"memory_store_id"` + IdentityID string `json:"identity_id"` + TemplateID string `json:"template_id"` + SessionIDs []string `json:"session_ids"` + JSON struct { + Type respjson.Field + MemoryStoreID respjson.Field + IdentityID respjson.Field + TemplateID respjson.Field + SessionIDs respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(DreamInput\) [RawJSON]() + +```go +func (r DreamInput) RawJSON() string +``` + + + + +### func \(\*DreamInput\) [UnmarshalJSON]() + +```go +func (r *DreamInput) UnmarshalJSON(data []byte) error +``` + + + + +## type [DreamInputParam]() + + + +```go +type DreamInputParam struct { + Type string `json:"type"` + MemoryStoreID param.Opt[string] `json:"memory_store_id,omitzero"` + IdentityID param.Opt[string] `json:"identity_id,omitzero"` + TemplateID param.Opt[string] `json:"template_id,omitzero"` + SessionIDs []string `json:"session_ids,omitzero"` + // contains filtered or unexported fields +} +``` + + +### func \(DreamInputParam\) [MarshalJSON]() + +```go +func (r DreamInputParam) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*DreamInputParam\) [UnmarshalJSON]() + +```go +func (r *DreamInputParam) UnmarshalJSON(data []byte) error +``` + + + + +## type [DreamListParams]() + + + +```go +type DreamListParams struct { + Limit param.Opt[int64] `query:"limit,omitzero" json:"-"` + AfterID param.Opt[string] `query:"after_id,omitzero" json:"-"` + BeforeID param.Opt[string] `query:"before_id,omitzero" json:"-"` + IncludeArchived param.Opt[bool] `query:"include_archived,omitzero" json:"-"` + // contains filtered or unexported fields +} +``` + + +### func \(DreamListParams\) [URLQuery]() + +```go +func (r DreamListParams) URLQuery() (url.Values, error) +``` + + + + +## type [DreamModelConfig]() + + + +```go +type DreamModelConfig struct { + ID string `json:"id"` + Effort string `json:"effort"` + ContextWindow int64 `json:"context_window"` + JSON struct { + ID respjson.Field + Effort respjson.Field + ContextWindow respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(DreamModelConfig\) [RawJSON]() + +```go +func (r DreamModelConfig) RawJSON() string +``` + + + + +### func \(\*DreamModelConfig\) [UnmarshalJSON]() + +```go +func (r *DreamModelConfig) UnmarshalJSON(data []byte) error +``` + + + + +## type [DreamModelConfigParam]() + + + +```go +type DreamModelConfigParam struct { + ID string `json:"id"` + Effort param.Opt[string] `json:"effort,omitzero"` + ContextWindow param.Opt[int64] `json:"context_window,omitzero"` + // contains filtered or unexported fields +} +``` + + +### func \(DreamModelConfigParam\) [MarshalJSON]() + +```go +func (r DreamModelConfigParam) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*DreamModelConfigParam\) [UnmarshalJSON]() + +```go +func (r *DreamModelConfigParam) UnmarshalJSON(data []byte) error +``` + + + + +## type [DreamNewParams]() + + + +```go +type DreamNewParams struct { + Inputs []DreamInputParam `json:"inputs,omitzero"` + Model DreamNewParamsModelUnion `json:"model,omitzero"` + Instructions param.Opt[string] `json:"instructions,omitzero"` + // contains filtered or unexported fields +} +``` + + +### func \(DreamNewParams\) [MarshalJSON]() + +```go +func (r DreamNewParams) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*DreamNewParams\) [UnmarshalJSON]() + +```go +func (r *DreamNewParams) UnmarshalJSON(data []byte) error +``` + + + + +## type [DreamNewParamsModelUnion]() + +DreamNewParamsModelUnion accepts a model ID or tuning object; omit to use auto. + +```go +type DreamNewParamsModelUnion struct { + OfString param.Opt[string] `json:",omitzero,inline"` + OfConfig *DreamModelConfigParam `json:",omitzero,inline"` + // contains filtered or unexported fields +} +``` + + +### func \(DreamNewParamsModelUnion\) [MarshalJSON]() + +```go +func (r DreamNewParamsModelUnion) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*DreamNewParamsModelUnion\) [UnmarshalJSON]() + +```go +func (r *DreamNewParamsModelUnion) UnmarshalJSON(data []byte) error +``` + + + + +## type [DreamOutput]() + + + +```go +type DreamOutput struct { + Type string `json:"type"` + MemoryStoreID string `json:"memory_store_id"` + FilesTouched []string `json:"files_touched"` + JSON struct { + Type respjson.Field + MemoryStoreID respjson.Field + FilesTouched respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(DreamOutput\) [RawJSON]() + +```go +func (r DreamOutput) RawJSON() string +``` + + + + +### func \(\*DreamOutput\) [UnmarshalJSON]() + +```go +func (r *DreamOutput) UnmarshalJSON(data []byte) error +``` + + + + +## type [DreamService]() + +DreamService consolidates Forward memory; requires PAT or an admin SAT. + +```go +type DreamService struct{ Options []option.RequestOption } +``` + + +### func [NewDreamService]() + +```go +func NewDreamService(opts ...option.RequestOption) DreamService +``` + + + + +### func \(\*DreamService\) [Archive]() + +```go +func (r *DreamService) Archive(ctx context.Context, dreamID string, opts ...option.RequestOption) (res *Dream, err error) +``` + + + + +### func \(\*DreamService\) [Cancel]() + +```go +func (r *DreamService) Cancel(ctx context.Context, dreamID string, opts ...option.RequestOption) (res *Dream, err error) +``` + + + + +### func \(\*DreamService\) [Get]() + +```go +func (r *DreamService) Get(ctx context.Context, dreamID string, opts ...option.RequestOption) (res *Dream, err error) +``` + + + + +### func \(\*DreamService\) [List]() + +```go +func (r *DreamService) List(ctx context.Context, params DreamListParams, opts ...option.RequestOption) (res *pagination.Page[Dream], err error) +``` + + + + +### func \(\*DreamService\) [ListAutoPaging]() + +```go +func (r *DreamService) ListAutoPaging(ctx context.Context, params DreamListParams, opts ...option.RequestOption) *pagination.PageAutoPager[Dream] +``` + + + + +### func \(\*DreamService\) [New]() + +```go +func (r *DreamService) New(ctx context.Context, params DreamNewParams, opts ...option.RequestOption) (res *Dream, err error) +``` + + + + +## type [DreamUsage]() + + + +```go +type DreamUsage struct { + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` + CacheReadInputTokens int64 `json:"cache_read_input_tokens"` + JSON struct { + InputTokens respjson.Field + OutputTokens respjson.Field + CacheCreationInputTokens respjson.Field + CacheReadInputTokens respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(DreamUsage\) [RawJSON]() + +```go +func (r DreamUsage) RawJSON() string +``` + + + + +### func \(\*DreamUsage\) [UnmarshalJSON]() + +```go +func (r *DreamUsage) UnmarshalJSON(data []byte) error +``` + + + ## type [DriveClearParams]() @@ -4968,6 +5620,7 @@ func (r *GitHubRepositoryParam) UnmarshalJSON(data []byte) error ```go type Identity struct { + AutoMemoryStore bool `json:"auto_memory_store"` // Forward Identity ID; the `idn_` prefix is recommended. ID string `json:"id"` // End-user ID in the integrator's system. @@ -4985,15 +5638,16 @@ type Identity struct { // Last update time, in RFC 3339 format. UpdatedAt time.Time `json:"updated_at" format:"date-time"` JSON struct { - ID respjson.Field - ExternalID respjson.Field - Name respjson.Field - IdentityType respjson.Field - Enabled respjson.Field - Metadata respjson.Field - CreatedAt respjson.Field - UpdatedAt respjson.Field - ExtraFields map[string]respjson.Field + AutoMemoryStore respjson.Field + ID respjson.Field + ExternalID respjson.Field + Name respjson.Field + IdentityType respjson.Field + Enabled respjson.Field + Metadata respjson.Field + CreatedAt respjson.Field + UpdatedAt respjson.Field + ExtraFields map[string]respjson.Field // contains filtered or unexported fields } `json:"-"` } @@ -5296,9 +5950,10 @@ Create or update Identity Config ```go type IdentityConfigSpec struct { + ManagedToolConfig map[string]*ManagedCapabilityOverride `json:"managed_tool_config"` System SystemOverride `json:"system"` Model ModelConfig `json:"model"` - Tools map[string]ToolOverride `json:"tools"` + Tools IdentityToolsUnion `json:"tools"` MCPServers map[string]MCPServerOverride `json:"mcp_servers"` Skills map[string]SkillOverride `json:"skills"` Toolsets map[string]any `json:"toolsets"` @@ -5308,6 +5963,7 @@ type IdentityConfigSpec struct { GitHubRepositories map[string]GitHubRepository `json:"github_repositories"` EnvironmentVariables map[string]EnvironmentVariableOverride `json:"environment_variables"` JSON struct { + ManagedToolConfig respjson.Field System respjson.Field Model respjson.Field Tools respjson.Field @@ -5350,9 +6006,10 @@ func (r *IdentityConfigSpec) UnmarshalJSON(data []byte) error ```go type IdentityConfigSpecParam struct { + ManagedToolConfig map[string]ManagedCapabilityOverrideParam `json:"managed_tool_config,omitzero"` System SystemOverrideParam `json:"system,omitzero"` Model ModelConfigUnionParam `json:"model,omitzero"` - Tools map[string]ToolOverrideParam `json:"tools,omitzero"` + Tools IdentityToolsUnionParam `json:"tools,omitzero"` MCPServers map[string]MCPServerOverrideParam `json:"mcp_servers,omitzero"` Skills map[string]SkillOverrideParam `json:"skills,omitzero"` Toolsets map[string]any `json:"toolsets,omitzero"` @@ -5489,6 +6146,29 @@ func (r *IdentityListTemplatesResponse) UnmarshalJSON(data []byte) error + +## type [IdentityMemoryStoreListParams]() + + + +```go +type IdentityMemoryStoreListParams struct { + Limit param.Opt[int64] `query:"limit,omitzero" json:"-"` + AfterID param.Opt[string] `query:"after_id,omitzero" json:"-"` + BeforeID param.Opt[string] `query:"before_id,omitzero" json:"-"` + // contains filtered or unexported fields +} +``` + + +### func \(IdentityMemoryStoreListParams\) [URLQuery]() + +```go +func (r IdentityMemoryStoreListParams) URLQuery() (url.Values, error) +``` + + + ## type [IdentityMemoryStoreListResponse]() @@ -5496,9 +6176,13 @@ func (r *IdentityListTemplatesResponse) UnmarshalJSON(data []byte) error ```go type IdentityMemoryStoreListResponse struct { + FirstID string `json:"first_id"` + LastID string `json:"last_id"` Data []MemoryStoreMount `json:"data"` HasMore bool `json:"has_more"` JSON struct { + FirstID respjson.Field + LastID respjson.Field Data respjson.Field HasMore respjson.Field ExtraFields map[string]respjson.Field @@ -5532,6 +6216,7 @@ func (r *IdentityMemoryStoreListResponse) UnmarshalJSON(data []byte) error ```go type IdentityMemoryStoreMountParams struct { + Access param.Opt[string] `json:"access,omitzero"` // ID of the Memory Store to mount (`memstore_...`). It must be an active Store // visible to the caller. MemoryStoreID string `json:"memory_store_id" api:"required"` @@ -5590,11 +6275,20 @@ Unmount Memory Store from an Identity ### func \(\*IdentityMemoryStoreService\) [List]() ```go -func (r *IdentityMemoryStoreService) List(ctx context.Context, identityID string, templateID string, opts ...option.RequestOption) (res *IdentityMemoryStoreListResponse, err error) +func (r *IdentityMemoryStoreService) List(ctx context.Context, identityID string, templateID string, params IdentityMemoryStoreListParams, opts ...option.RequestOption) (res *pagination.Page[MemoryStoreMount], err error) ``` List Memory Store mounts on an Identity + +### func \(\*IdentityMemoryStoreService\) [ListAutoPaging]() + +```go +func (r *IdentityMemoryStoreService) ListAutoPaging(ctx context.Context, identityID, templateID string, params IdentityMemoryStoreListParams, opts ...option.RequestOption) *pagination.PageAutoPager[MemoryStoreMount] +``` + + + ### func \(\*IdentityMemoryStoreService\) [Mount]() @@ -5611,6 +6305,7 @@ Mount Memory Store on an Identity ```go type IdentityNewParams struct { + AutoMemoryStore param.Opt[bool] `json:"auto_memory_store,omitzero"` // End-user ID in the integrator's system; cannot be empty or whitespace only. ExternalID string `json:"external_id" api:"required"` // Display name; when provided it cannot be empty or whitespace only. @@ -5887,6 +6582,68 @@ func (r *IdentityTemplate) UnmarshalJSON(data []byte) error + +## type [IdentityToolsUnion]() + + + +```go +type IdentityToolsUnion struct { + OfOverrides map[string]*ToolOverride + OfArray []Tool + // contains filtered or unexported fields +} +``` + + +### func \(IdentityToolsUnion\) [RawJSON]() + +```go +func (r IdentityToolsUnion) RawJSON() string +``` + + + + +### func \(\*IdentityToolsUnion\) [UnmarshalJSON]() + +```go +func (r *IdentityToolsUnion) UnmarshalJSON(data []byte) error +``` + + + + +## type [IdentityToolsUnionParam]() + +IdentityToolsUnionParam overrides individual tools or replaces the complete tools array. + +```go +type IdentityToolsUnionParam struct { + OfOverrides map[string]ToolOverrideParam `json:",omitzero,inline"` + OfArray []ToolParam `json:",omitzero,inline"` + // contains filtered or unexported fields +} +``` + + +### func \(IdentityToolsUnionParam\) [MarshalJSON]() + +```go +func (r IdentityToolsUnionParam) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*IdentityToolsUnionParam\) [UnmarshalJSON]() + +```go +func (r *IdentityToolsUnionParam) UnmarshalJSON(data []byte) error +``` + + + ## type [IdentityUpdateParams]() @@ -5894,6 +6651,7 @@ func (r *IdentityTemplate) UnmarshalJSON(data []byte) error ```go type IdentityUpdateParams struct { + AutoMemoryStore param.Opt[bool] `json:"auto_memory_store,omitzero"` // Replaces the existing end-user ID. ExternalID param.Opt[string] `json:"external_id,omitzero"` // Replaces the display name. @@ -6250,6 +7008,70 @@ func (r *MCPServerParam) UnmarshalJSON(data []byte) error + +## type [ManagedCapabilityOverride]() + + + +```go +type ManagedCapabilityOverride struct { + Enabled bool `json:"enabled"` + JSON struct { + Enabled respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(ManagedCapabilityOverride\) [RawJSON]() + +```go +func (r ManagedCapabilityOverride) RawJSON() string +``` + + + + +### func \(\*ManagedCapabilityOverride\) [UnmarshalJSON]() + +```go +func (r *ManagedCapabilityOverride) UnmarshalJSON(data []byte) error +``` + + + + +## type [ManagedCapabilityOverrideParam]() + +ManagedCapabilityOverrideParam is a sparse Identity capability switch. Use param.NullStruct to restore inheritance for one selector. + +```go +type ManagedCapabilityOverrideParam struct { + Enabled bool `json:"enabled" api:"required"` + // contains filtered or unexported fields +} +``` + + +### func \(ManagedCapabilityOverrideParam\) [MarshalJSON]() + +```go +func (r ManagedCapabilityOverrideParam) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*ManagedCapabilityOverrideParam\) [UnmarshalJSON]() + +```go +func (r *ManagedCapabilityOverrideParam) UnmarshalJSON(data []byte) error +``` + + + ## type [ManagedToolConfig]() @@ -7320,10 +8142,13 @@ func (r *MultiagentConfigParam) UnmarshalJSON(data []byte) error ```go type MultiagentEntry struct { + OfString string `json:"-"` + Model string `json:"model"` Type string `json:"type" api:"required"` TemplateID string `json:"template_id"` Name string `json:"name"` JSON struct { + Model respjson.Field Type respjson.Field TemplateID respjson.Field Name respjson.Field @@ -7342,6 +8167,15 @@ func (r MultiagentEntry) RawJSON() string + +### func \(\*MultiagentEntry\) [UnmarshalAPIJSON]() + +```go +func (r *MultiagentEntry) UnmarshalAPIJSON(data []byte) error +``` + +UnmarshalAPIJSON keeps string roster entries intact when nested in a Template. + ### func \(\*MultiagentEntry\) [UnmarshalJSON]() @@ -7358,6 +8192,8 @@ func (r *MultiagentEntry) UnmarshalJSON(data []byte) error ```go type MultiagentEntryParam struct { + OfString param.Opt[string] `json:"-"` + Model param.Opt[string] `json:"model,omitzero"` Type string `json:"type" api:"required"` TemplateID param.Opt[string] `json:"template_id,omitzero"` Name param.Opt[string] `json:"name,omitzero"` @@ -7374,6 +8210,15 @@ func (r MultiagentEntryParam) MarshalJSON() ([]byte, error) + +### func \(\*MultiagentEntryParam\) [UnmarshalAPIJSON]() + +```go +func (r *MultiagentEntryParam) UnmarshalAPIJSON(data []byte) error +``` + + + ### func \(\*MultiagentEntryParam\) [UnmarshalJSON]() @@ -7383,6 +8228,76 @@ func (r *MultiagentEntryParam) UnmarshalJSON(data []byte) error + +## type [OutcomeRubric]() + +OutcomeRubric contains the text or file reference used to evaluate an Outcome. + +```go +type OutcomeRubric struct { + Type string `json:"type"` + Content string `json:"content"` + FileID string `json:"file_id"` + JSON struct { + Type respjson.Field + Content respjson.Field + FileID respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(OutcomeRubric\) [RawJSON]() + +```go +func (r OutcomeRubric) RawJSON() string +``` + + + + +### func \(\*OutcomeRubric\) [UnmarshalJSON]() + +```go +func (r *OutcomeRubric) UnmarshalJSON(data []byte) error +``` + + + + +## type [OutcomeRubricParam]() + +OutcomeRubricParam defines acceptance criteria as inline text or an uploaded file. Set Type to "text" with Content, or "file" with FileID. + +```go +type OutcomeRubricParam struct { + Type string `json:"type" api:"required"` + Content param.Opt[string] `json:"content,omitzero"` + FileID param.Opt[string] `json:"file_id,omitzero"` + // contains filtered or unexported fields +} +``` + + +### func \(OutcomeRubricParam\) [MarshalJSON]() + +```go +func (r OutcomeRubricParam) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*OutcomeRubricParam\) [UnmarshalJSON]() + +```go +func (r *OutcomeRubricParam) UnmarshalJSON(data []byte) error +``` + + + ## type [PermissionPolicy]() @@ -8436,6 +9351,7 @@ func (s *ResumableSessionEventStream) Next() bool ```go type Schedule struct { + Resources []ScheduleFileResource `json:"resources"` // Authoritative creation method of the Schedule: api or tool. Source string `json:"source"` SourceSessionID string `json:"source_session_id" api:"nullable"` @@ -8458,6 +9374,7 @@ type Schedule struct { ArchivedAt time.Time `json:"archived_at" format:"date-time"` PausedReason SchedulePausedReason `json:"paused_reason"` JSON struct { + Resources respjson.Field Source respjson.Field SourceSessionID respjson.Field InitialEventsVersion respjson.Field @@ -8635,6 +9552,76 @@ func (r *ScheduleExecution) UnmarshalJSON(data []byte) error + +## type [ScheduleFileResource]() + + + +```go +type ScheduleFileResource struct { + Type string `json:"type"` + FileID string `json:"file_id"` + MountPath string `json:"mount_path"` + JSON struct { + Type respjson.Field + FileID respjson.Field + MountPath respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(ScheduleFileResource\) [RawJSON]() + +```go +func (r ScheduleFileResource) RawJSON() string +``` + + + + +### func \(\*ScheduleFileResource\) [UnmarshalJSON]() + +```go +func (r *ScheduleFileResource) UnmarshalJSON(data []byte) error +``` + + + + +## type [ScheduleFileResourceParam]() + +ScheduleFileResourceParam attaches a Forward file to each Schedule run. + +```go +type ScheduleFileResourceParam struct { + Type string `json:"type" api:"required"` + FileID string `json:"file_id" api:"required"` + MountPath param.Opt[string] `json:"mount_path,omitzero"` + // contains filtered or unexported fields +} +``` + + +### func \(ScheduleFileResourceParam\) [MarshalJSON]() + +```go +func (r ScheduleFileResourceParam) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*ScheduleFileResourceParam\) [UnmarshalJSON]() + +```go +func (r *ScheduleFileResourceParam) UnmarshalJSON(data []byte) error +``` + + + ## type [ScheduleInitialEventsItem]() @@ -8718,6 +9705,7 @@ func (r ScheduleListParams) URLQuery() (url.Values, error) ```go type ScheduleNewParams struct { + Resources []ScheduleFileResourceParam `json:"resources,omitzero"` // Forward Identity ID the Schedule belongs to. IdentityID string `json:"identity_id" api:"required"` // Forward Template ID to execute. @@ -8826,6 +9814,7 @@ func (r *SchedulePausedReason) UnmarshalJSON(data []byte) error ```go type ScheduleRun struct { + InitialEvents []SessionEvent `json:"initial_events"` // Authoritative creation method of the Schedule: api or tool. Source string `json:"source"` SourceSessionID string `json:"source_session_id" api:"nullable"` @@ -8872,6 +9861,7 @@ type ScheduleRun struct { // null|Human-readable error message; structured details stay in `error`. ErrorMessage string `json:"error_message"` JSON struct { + InitialEvents respjson.Field Source respjson.Field SourceSessionID respjson.Field InitialEventsVersion respjson.Field @@ -8925,6 +9915,7 @@ func (r *ScheduleRun) UnmarshalJSON(data []byte) error ```go type ScheduleRunGetParams struct { + IncludeInitialEvents param.Opt[bool] `query:"include_initial_events,omitzero" json:"-"` // Additional ownership constraint. IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"` // contains filtered or unexported fields @@ -9344,6 +10335,7 @@ func (r ScheduleUnpauseParams) URLQuery() (url.Values, error) ```go type ScheduleUpdateParams struct { + Resources []ScheduleFileResourceParam `json:"resources,omitzero"` // New Schedule name. Name param.Opt[string] `json:"name,omitzero"` // New Schedule description. @@ -9807,7 +10799,7 @@ type SessionEvent struct { Result string `json:"result"` DenyMessage string `json:"deny_message"` Description string `json:"description"` - Rubric string `json:"rubric"` + Rubric OutcomeRubric `json:"rubric"` OutcomeID string `json:"outcome_id"` MaxIterations int64 `json:"max_iterations"` MessageID string `json:"message_id"` @@ -9956,7 +10948,7 @@ type SessionEventParam struct { DenyMessage param.Opt[string] `json:"deny_message,omitzero"` IsError param.Opt[bool] `json:"is_error,omitzero"` Description param.Opt[string] `json:"description,omitzero"` - Rubric param.Opt[string] `json:"rubric,omitzero"` + Rubric OutcomeRubricParam `json:"rubric,omitzero"` OutcomeID param.Opt[string] `json:"outcome_id,omitzero"` MaxIterations param.Opt[int64] `json:"max_iterations,omitzero"` // contains filtered or unexported fields @@ -13847,6 +14839,12 @@ Qoder managed API definitions. - [type BrowserStateTabEntryParam](<#BrowserStateTabEntryParam>) - [func \(r BrowserStateTabEntryParam\) MarshalJSON\(\) \(data \[\]byte, err error\)](<#BrowserStateTabEntryParam.MarshalJSON>) - [func \(r \*BrowserStateTabEntryParam\) UnmarshalJSON\(data \[\]byte\) error](<#BrowserStateTabEntryParam.UnmarshalJSON>) +- [type BucketStorage](<#BucketStorage>) + - [func \(r BucketStorage\) RawJSON\(\) string](<#BucketStorage.RawJSON>) + - [func \(r \*BucketStorage\) UnmarshalJSON\(data \[\]byte\) error](<#BucketStorage.UnmarshalJSON>) +- [type BucketStorageParam](<#BucketStorageParam>) + - [func \(r BucketStorageParam\) MarshalJSON\(\) \(\[\]byte, error\)](<#BucketStorageParam.MarshalJSON>) + - [func \(r \*BucketStorageParam\) UnmarshalJSON\(data \[\]byte\) error](<#BucketStorageParam.UnmarshalJSON>) - [type CacheControlEphemeralParam](<#CacheControlEphemeralParam>) - [func \(r CacheControlEphemeralParam\) MarshalJSON\(\) \(data \[\]byte, err error\)](<#CacheControlEphemeralParam.MarshalJSON>) - [func \(r \*CacheControlEphemeralParam\) UnmarshalJSON\(data \[\]byte\) error](<#CacheControlEphemeralParam.UnmarshalJSON>) @@ -14091,10 +15089,8 @@ Qoder managed API definitions. - [func \(r DreamModelConfig\) RawJSON\(\) string](<#DreamModelConfig.RawJSON>) - [func \(r \*DreamModelConfig\) UnmarshalJSON\(data \[\]byte\) error](<#DreamModelConfig.UnmarshalJSON>) - [type DreamModelConfigParam](<#DreamModelConfigParam>) - - [func \(r DreamModelConfigParam\) MarshalJSON\(\) \(data \[\]byte, err error\)](<#DreamModelConfigParam.MarshalJSON>) + - [func \(r DreamModelConfigParam\) MarshalJSON\(\) \(\[\]byte, error\)](<#DreamModelConfigParam.MarshalJSON>) - [func \(r \*DreamModelConfigParam\) UnmarshalJSON\(data \[\]byte\) error](<#DreamModelConfigParam.UnmarshalJSON>) -- [type DreamModelConfigParamSpeed](<#DreamModelConfigParamSpeed>) -- [type DreamModelConfigSpeed](<#DreamModelConfigSpeed>) - [type DreamNewParams](<#DreamNewParams>) - [func \(r DreamNewParams\) MarshalJSON\(\) \(data \[\]byte, err error\)](<#DreamNewParams.MarshalJSON>) - [func \(r \*DreamNewParams\) UnmarshalJSON\(data \[\]byte\) error](<#DreamNewParams.UnmarshalJSON>) @@ -16345,6 +17341,27 @@ Qoder managed API definitions. - [type MonetaryAmountParam](<#MonetaryAmountParam>) - [func \(r MonetaryAmountParam\) MarshalJSON\(\) \(data \[\]byte, err error\)](<#MonetaryAmountParam.MarshalJSON>) - [func \(r \*MonetaryAmountParam\) UnmarshalJSON\(data \[\]byte\) error](<#MonetaryAmountParam.UnmarshalJSON>) +- [type OAuthService](<#OAuthService>) + - [func NewOAuthService\(opts ...option.RequestOption\) OAuthService](<#NewOAuthService>) + - [func \(r \*OAuthService\) Start\(ctx context.Context, params OAuthStartParams, opts ...option.RequestOption\) \(res \*OAuthStartResponse, err error\)](<#OAuthService.Start>) +- [type OAuthStartParams](<#OAuthStartParams>) + - [func \(r OAuthStartParams\) MarshalJSON\(\) \(\[\]byte, error\)](<#OAuthStartParams.MarshalJSON>) + - [func \(r \*OAuthStartParams\) UnmarshalJSON\(data \[\]byte\) error](<#OAuthStartParams.UnmarshalJSON>) +- [type OAuthStartResponse](<#OAuthStartResponse>) + - [func \(r OAuthStartResponse\) RawJSON\(\) string](<#OAuthStartResponse.RawJSON>) + - [func \(r \*OAuthStartResponse\) UnmarshalJSON\(data \[\]byte\) error](<#OAuthStartResponse.UnmarshalJSON>) +- [type OSSBucket](<#OSSBucket>) + - [func \(r OSSBucket\) RawJSON\(\) string](<#OSSBucket.RawJSON>) + - [func \(r \*OSSBucket\) UnmarshalJSON\(data \[\]byte\) error](<#OSSBucket.UnmarshalJSON>) +- [type OSSBucketCredential](<#OSSBucketCredential>) + - [func \(r OSSBucketCredential\) RawJSON\(\) string](<#OSSBucketCredential.RawJSON>) + - [func \(r \*OSSBucketCredential\) UnmarshalJSON\(data \[\]byte\) error](<#OSSBucketCredential.UnmarshalJSON>) +- [type OSSBucketCredentialParam](<#OSSBucketCredentialParam>) + - [func \(r OSSBucketCredentialParam\) MarshalJSON\(\) \(\[\]byte, error\)](<#OSSBucketCredentialParam.MarshalJSON>) + - [func \(r \*OSSBucketCredentialParam\) UnmarshalJSON\(data \[\]byte\) error](<#OSSBucketCredentialParam.UnmarshalJSON>) +- [type OSSBucketParam](<#OSSBucketParam>) + - [func \(r OSSBucketParam\) MarshalJSON\(\) \(\[\]byte, error\)](<#OSSBucketParam.MarshalJSON>) + - [func \(r \*OSSBucketParam\) UnmarshalJSON\(data \[\]byte\) error](<#OSSBucketParam.UnmarshalJSON>) - [type OutputBehaviorCreateNew](<#OutputBehaviorCreateNew>) - [func \(r OutputBehaviorCreateNew\) RawJSON\(\) string](<#OutputBehaviorCreateNew.RawJSON>) - [func \(r OutputBehaviorCreateNew\) ToParam\(\) OutputBehaviorCreateNewParam](<#OutputBehaviorCreateNew.ToParam>) @@ -18426,6 +19443,70 @@ func (r *BrowserStateTabEntryParam) UnmarshalJSON(data []byte) error + +## type [BucketStorage]() + + + +```go +type BucketStorage struct { + Buckets []OSSBucket `json:"buckets"` + JSON struct { + Buckets respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(BucketStorage\) [RawJSON]() + +```go +func (r BucketStorage) RawJSON() string +``` + + + + +### func \(\*BucketStorage\) [UnmarshalJSON]() + +```go +func (r *BucketStorage) UnmarshalJSON(data []byte) error +``` + + + + +## type [BucketStorageParam]() + +BucketStorageParam declares up to eight OSS bucket mounts in a cloud Environment. + +```go +type BucketStorageParam struct { + Buckets []OSSBucketParam `json:"buckets" api:"required"` + // contains filtered or unexported fields +} +``` + + +### func \(BucketStorageParam\) [MarshalJSON]() + +```go +func (r BucketStorageParam) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*BucketStorageParam\) [UnmarshalJSON]() + +```go +func (r *BucketStorageParam) UnmarshalJSON(data []byte) error +``` + + + ## type [CacheControlEphemeralParam]() @@ -18768,6 +19849,7 @@ Client exposes concrete services; nested services follow the upstream hierarchy. ```go type Client struct { + OAuth OAuthService Options []option.RequestOption Agents AgentService Sessions SessionService @@ -18799,7 +19881,9 @@ NewClient uses common authentication defaults and QODER\_BASE\_URL. Explicit opt ```go type CloudConfig struct { - SetupScript string `json:"setup_script"` + // Set Betas to include "oss-bucket-2026-09-23" when buckets remain configured. + Storage BucketStorage `json:"storage"` + SetupScript string `json:"setup_script"` // Network configuration policy. Networking CloudConfigNetworkingUnion `json:"networking" api:"required"` // Package manager configuration. @@ -18808,6 +19892,7 @@ type CloudConfig struct { Type constant.Cloud `json:"type" default:"cloud"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { + Storage respjson.Field SetupScript respjson.Field Networking respjson.Field Packages respjson.Field @@ -18930,7 +20015,9 @@ The property Type is required. ```go type CloudConfigParams struct { - SetupScript param.Opt[string] `json:"setup_script,omitzero"` + // Set Betas to include "oss-bucket-2026-09-23" when buckets remain configured. + Storage BucketStorageParam `json:"storage,omitzero"` + SetupScript param.Opt[string] `json:"setup_script,omitzero"` // Network configuration policy. Omit on update to preserve the existing value. Networking CloudConfigParamsNetworkingUnion `json:"networking,omitzero"` // Specify packages (and optionally their versions) available in this environment. @@ -21363,19 +22450,14 @@ Model identifier and configuration applied to every pipeline stage. Same wire sh ```go type DreamModelConfig struct { - // Model identifier. 1-256 characters. - ID string `json:"id" api:"required"` - // 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. - // - // Any of "standard", "fast". - Speed DreamModelConfigSpeed `json:"speed"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - Speed respjson.Field - ExtraFields map[string]respjson.Field + ID string `json:"id"` + Effort string `json:"effort"` + ContextWindow int64 `json:"context_window"` + JSON struct { + ID respjson.Field + Effort respjson.Field + ContextWindow respjson.Field + ExtraFields map[string]respjson.Field // contains filtered or unexported fields } `json:"-"` } @@ -21388,7 +22470,7 @@ type DreamModelConfig struct { func (r DreamModelConfig) RawJSON() string ``` -Returns the unmodified JSON received from the API + ### func \(\*DreamModelConfig\) [UnmarshalJSON]() @@ -21402,20 +22484,13 @@ func (r *DreamModelConfig) UnmarshalJSON(data []byte) error ## type [DreamModelConfigParam]() -Model identifier and configuration applied to every pipeline stage. - -The property ID is required. +DreamModelConfigParam selects an available model and optional reasoning settings. ```go type DreamModelConfigParam struct { - // Model identifier. 1-256 characters. - ID string `json:"id" api:"required"` - // 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. - // - // Any of "standard", "fast". - Speed DreamModelConfigParamSpeed `json:"speed,omitzero"` + ID string `json:"id" api:"required"` + Effort param.Opt[string] `json:"effort,omitzero"` + ContextWindow param.Opt[int64] `json:"context_window,omitzero"` // contains filtered or unexported fields } ``` @@ -21424,7 +22499,7 @@ type DreamModelConfigParam struct { ### func \(DreamModelConfigParam\) [MarshalJSON]() ```go -func (r DreamModelConfigParam) MarshalJSON() (data []byte, err error) +func (r DreamModelConfigParam) MarshalJSON() ([]byte, error) ``` @@ -21438,42 +22513,6 @@ func (r *DreamModelConfigParam) UnmarshalJSON(data []byte) error - -## type [DreamModelConfigParamSpeed]() - -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. - -```go -type DreamModelConfigParamSpeed string -``` - - - -```go -const ( - DreamModelConfigParamSpeedStandard DreamModelConfigParamSpeed = "standard" - DreamModelConfigParamSpeedFast DreamModelConfigParamSpeed = "fast" -) -``` - - -## type [DreamModelConfigSpeed]() - -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. - -```go -type DreamModelConfigSpeed string -``` - - - -```go -const ( - DreamModelConfigSpeedStandard DreamModelConfigSpeed = "standard" - DreamModelConfigSpeedFast DreamModelConfigSpeed = "fast" -) -``` - ## type [DreamNewParams]() @@ -21483,7 +22522,7 @@ const ( type DreamNewParams struct { Inputs []DreamInputUnionParam `json:"inputs,omitzero" api:"required"` // Model identifier and configuration applied to every pipeline stage. - Model DreamNewParamsModelUnion `json:"model,omitzero" api:"required"` + Model DreamNewParamsModelUnion `json:"model,omitzero"` Instructions param.Opt[string] `json:"instructions,omitzero"` // The default destination: the job creates a new output memory store as a clone of // the memory_store input and writes the consolidated memories into it. The input @@ -22042,7 +23081,9 @@ Use the methods beginning with 'As' to cast the union to one of its variants. ```go type EnvironmentConfigUnion struct { - SetupScript string `json:"setup_script"` + // Set Betas to include "oss-bucket-2026-09-23" when buckets remain configured. + Storage BucketStorage `json:"storage"` + SetupScript string `json:"setup_script"` // This field is from variant [CloudConfig]. Networking CloudConfigNetworkingUnion `json:"networking"` // This field is from variant [CloudConfig]. @@ -22050,6 +23091,7 @@ type EnvironmentConfigUnion struct { // Any of "cloud", "self_hosted". Type string `json:"type"` JSON struct { + Storage respjson.Field SetupScript respjson.Field Networking respjson.Field Packages respjson.Field @@ -37367,34 +38409,18 @@ Model identifier and configuration. ```go type ManagedAgentsModelConfig struct { - ContextWindow int64 `json:"context_window"` - - // The model that will power your agent. - // - // See [models](https://docs.qoder.com/cloud-agents/api/models/list) for additional - // details and options. - ID ManagedAgentsModel `json:"id" api:"required"` - // How hard the model works on each turn. Sets `output_config.effort` on every - // Messages call the session makes. - Effort ManagedAgentsModelConfigEffortUnion `json:"effort"` - // Geographic region for model inference. When unset, requests fall through to the - // workspace's default_inference_geo. - InferenceGeo string `json:"inference_geo"` - // 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. - // - // Any of "standard", "fast". - Speed ManagedAgentsModelConfigSpeed `json:"speed"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ContextWindow respjson.Field - - ID respjson.Field - Effort respjson.Field - InferenceGeo respjson.Field - Speed respjson.Field - ExtraFields map[string]respjson.Field + InferenceGeo string `json:"inference_geo"` + ID string `json:"id" api:"required"` + Effort string `json:"effort"` + ContextWindow int64 `json:"context_window"` + EffectiveContextWindow int64 `json:"effective_context_window"` + JSON struct { + InferenceGeo respjson.Field + ID respjson.Field + Effort respjson.Field + ContextWindow respjson.Field + EffectiveContextWindow respjson.Field + ExtraFields map[string]respjson.Field // contains filtered or unexported fields } `json:"-"` } @@ -37531,27 +38557,10 @@ The property ID is required. ```go type ManagedAgentsModelConfigParams struct { - ContextWindow param.Opt[int64] `json:"context_window,omitzero"` - - // The model that will power your agent. - // - // See [models](https://docs.qoder.com/cloud-agents/api/models/list) for additional - // details and options. - ID ManagedAgentsModel `json:"id,omitzero" api:"required"` - // Geographic region for model inference. When unset, requests fall through to the - // workspace's default_inference_geo. On update, `model` is whole-object - // replacement — omitting inference_geo clears it. - InferenceGeo param.Opt[string] `json:"inference_geo,omitzero"` - // How hard the model works on each inference call. Accepts a bare level string - // (`"high"`) or `{"type": "high"}`. On create, omitting it resolves the per-model - // default; on update, omitting it leaves the stored value unchanged. - Effort ManagedAgentsModelConfigParamsEffortUnion `json:"effort,omitzero"` - // 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. - // - // Any of "standard", "fast". - Speed ManagedAgentsModelConfigParamsSpeed `json:"speed,omitzero"` + InferenceGeo param.Opt[string] `json:"inference_geo,omitzero"` + ID string `json:"id" api:"required"` + Effort param.Opt[string] `json:"effort,omitzero"` + ContextWindow param.Opt[int64] `json:"context_window,omitzero"` // contains filtered or unexported fields } ``` @@ -51601,7 +52610,9 @@ type MemoryStoreGetParams struct { ```go type MemoryStoreListParams struct { - Name param.Opt[string] `query:"name,omitzero" json:"-"` + AfterID param.Opt[string] `query:"after_id,omitzero" json:"-"` + BeforeID param.Opt[string] `query:"before_id,omitzero" json:"-"` + Name param.Opt[string] `query:"name,omitzero" json:"-"` // Return only stores whose `created_at` is at or after this time (inclusive). Sent // on the wire as `created_at[gte]`. @@ -52629,6 +53640,260 @@ func (r *MonetaryAmountParam) UnmarshalJSON(data []byte) error + +## type [OAuthService]() + + + +```go +type OAuthService struct{ Options []option.RequestOption } +``` + + +### func [NewOAuthService]() + +```go +func NewOAuthService(opts ...option.RequestOption) OAuthService +``` + + + + +### func \(\*OAuthService\) [Start]() + +```go +func (r *OAuthService) Start(ctx context.Context, params OAuthStartParams, opts ...option.RequestOption) (res *OAuthStartResponse, err error) +``` + +Start returns an authorization URL; the user completes authorization in their browser. + + +## type [OAuthStartParams]() + + + +```go +type OAuthStartParams struct { + WorkspaceID param.Opt[string] `header:"qoder-workspace-id,omitzero" json:"-"` + + VaultID string `json:"vault_id"` + MCPServerURL string `json:"mcp_server_url"` + ClientID param.Opt[string] `json:"client_id,omitzero"` + ClientSecret param.Opt[string] `json:"client_secret,omitzero"` + // contains filtered or unexported fields +} +``` + + +### func \(OAuthStartParams\) [MarshalJSON]() + +```go +func (r OAuthStartParams) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*OAuthStartParams\) [UnmarshalJSON]() + +```go +func (r *OAuthStartParams) UnmarshalJSON(data []byte) error +``` + + + + +## type [OAuthStartResponse]() + + + +```go +type OAuthStartResponse struct { + AuthorizationURL string `json:"authorization_url"` + State string `json:"state"` + CallbackOrigin string `json:"callback_origin"` + JSON struct { + AuthorizationURL respjson.Field + State respjson.Field + CallbackOrigin respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(OAuthStartResponse\) [RawJSON]() + +```go +func (r OAuthStartResponse) RawJSON() string +``` + + + + +### func \(\*OAuthStartResponse\) [UnmarshalJSON]() + +```go +func (r *OAuthStartResponse) UnmarshalJSON(data []byte) error +``` + + + + +## type [OSSBucket]() + + + +```go +type OSSBucket struct { + Name string `json:"name"` + Provider string `json:"provider"` + Region string `json:"region"` + Bucket string `json:"bucket"` + Prefix string `json:"prefix"` + Access string `json:"access"` + AcceleratorEndpoint string `json:"accelerator_endpoint"` + Credential OSSBucketCredential `json:"credential"` + JSON struct { + Name respjson.Field + Provider respjson.Field + Region respjson.Field + Bucket respjson.Field + Prefix respjson.Field + Access respjson.Field + AcceleratorEndpoint respjson.Field + Credential respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(OSSBucket\) [RawJSON]() + +```go +func (r OSSBucket) RawJSON() string +``` + + + + +### func \(\*OSSBucket\) [UnmarshalJSON]() + +```go +func (r *OSSBucket) UnmarshalJSON(data []byte) error +``` + + + + +## type [OSSBucketCredential]() + +OSSBucketCredential is the API read view. It contains no role ARN or secret. + +```go +type OSSBucketCredential struct { + Type string `json:"type"` + AccessKeyID string `json:"access_key_id"` + JSON struct { + Type respjson.Field + AccessKeyID respjson.Field + ExtraFields map[string]respjson.Field + // contains filtered or unexported fields + } `json:"-"` +} +``` + + +### func \(OSSBucketCredential\) [RawJSON]() + +```go +func (r OSSBucketCredential) RawJSON() string +``` + + + + +### func \(\*OSSBucketCredential\) [UnmarshalJSON]() + +```go +func (r *OSSBucketCredential) UnmarshalJSON(data []byte) error +``` + + + + +## type [OSSBucketCredentialParam]() + +OSSBucketCredentialParam accepts ram\_role with RoleArn, or access\_key with ID and secret. A read\-view credential without secrets may be echoed to retain it on update. + +```go +type OSSBucketCredentialParam struct { + Type string `json:"type" api:"required"` + RoleArn param.Opt[string] `json:"role_arn,omitzero"` + AccessKeyID param.Opt[string] `json:"access_key_id,omitzero"` + AccessKeySecret param.Opt[string] `json:"access_key_secret,omitzero"` + // contains filtered or unexported fields +} +``` + + +### func \(OSSBucketCredentialParam\) [MarshalJSON]() + +```go +func (r OSSBucketCredentialParam) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*OSSBucketCredentialParam\) [UnmarshalJSON]() + +```go +func (r *OSSBucketCredentialParam) UnmarshalJSON(data []byte) error +``` + + + + +## type [OSSBucketParam]() + +OSSBucketParam requires credentials for new buckets. Omit Credential on update to retain it. + +```go +type OSSBucketParam struct { + Name string `json:"name" api:"required"` + Provider string `json:"provider" api:"required"` + Region string `json:"region" api:"required"` + Bucket string `json:"bucket" api:"required"` + Prefix param.Opt[string] `json:"prefix,omitzero"` + Access param.Opt[string] `json:"access,omitzero"` + AcceleratorEndpoint param.Opt[string] `json:"accelerator_endpoint,omitzero"` + Credential OSSBucketCredentialParam `json:"credential,omitzero"` + // contains filtered or unexported fields +} +``` + + +### func \(OSSBucketParam\) [MarshalJSON]() + +```go +func (r OSSBucketParam) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*OSSBucketParam\) [UnmarshalJSON]() + +```go +func (r *OSSBucketParam) UnmarshalJSON(data []byte) error +``` + + + ## type [OutputBehaviorCreateNew]() diff --git a/examples/forward/memory/main.go b/examples/forward/memory/main.go index 7ad1956..7199352 100644 --- a/examples/forward/memory/main.go +++ b/examples/forward/memory/main.go @@ -103,7 +103,7 @@ func run(ctx context.Context, r *live.Run, s *forwardutil.Example) error { return err }) r.Step("查询绑定,确认记忆库已关联") - mounts, err := s.Client.Identities.MemoryStores.List(ctx, identity.ID, template) + mounts, err := s.Client.Identities.MemoryStores.List(ctx, identity.ID, template, forward.IdentityMemoryStoreListParams{}) if err != nil { return err } diff --git a/forward/channelpairing.go b/forward/channelpairing.go index 9ff8632..8c52f3a 100644 --- a/forward/channelpairing.go +++ b/forward/channelpairing.go @@ -9,7 +9,9 @@ import ( "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/apijson" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/apiquery" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/pagination" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/param" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/respjson" ) @@ -127,3 +129,70 @@ type ChannelPairing struct { func (r ChannelPairing) RawJSON() string { return r.JSON.raw } func (r *ChannelPairing) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +func (r *ChannelPairingService) Get(ctx context.Context, pairingID string, opts ...option.RequestOption) (res *ChannelPairing, err error) { + if pairingID == "" { + return nil, fmt.Errorf("missing required pairing_id parameter") + } + opts = slices.Concat(r.Options, opts) + err = convention.ExecuteNewRequest(ctx, http.MethodGet, fmt.Sprintf("channel_pairings/%s", url.PathEscape(pairingID)), nil, &res, opts...) + return res, err +} + +// Update changes an active binding. Supply at least one nonempty field. +func (r *ChannelPairingService) Update(ctx context.Context, pairingID string, params ChannelPairingUpdateParams, opts ...option.RequestOption) (res *ChannelPairing, err error) { + if pairingID == "" { + return nil, fmt.Errorf("missing required pairing_id parameter") + } + opts = slices.Concat(r.Options, opts) + err = convention.ExecuteNewRequest(ctx, http.MethodPost, fmt.Sprintf("channel_pairings/%s", url.PathEscape(pairingID)), params, &res, opts...) + return res, err +} +func (r *ChannelPairingService) List(ctx context.Context, params ChannelPairingListParams, opts ...option.RequestOption) (res *pagination.Page[ChannelPairing], err error) { + opts = slices.Concat(r.Options, opts) + var raw *http.Response + opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) + cfg, err := convention.NewRequestConfig(ctx, http.MethodGet, "channel_pairings", params, &res, opts...) + if err != nil { + return nil, err + } + if err = cfg.Execute(); err != nil { + return nil, err + } + res.SetPageConfig(cfg, raw) + return res, nil +} +func (r *ChannelPairingService) ListAutoPaging(ctx context.Context, params ChannelPairingListParams, opts ...option.RequestOption) *pagination.PageAutoPager[ChannelPairing] { + return pagination.NewPageAutoPager(r.List(ctx, params, opts...)) +} + +type ChannelPairingUpdateParams struct { + IdentityID param.Opt[string] `json:"identity_id,omitzero"` + TemplateID param.Opt[string] `json:"template_id,omitzero"` + paramObj +} + +func (r ChannelPairingUpdateParams) MarshalJSON() ([]byte, error) { + type shadow ChannelPairingUpdateParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *ChannelPairingUpdateParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type ChannelPairingListParams struct { + ChannelID param.Opt[string] `query:"channel_id,omitzero" json:"-"` + Status param.Opt[string] `query:"status,omitzero" json:"-"` + TargetType param.Opt[string] `query:"target_type,omitzero" json:"-"` + TargetExternalID param.Opt[string] `query:"target_external_id,omitzero" json:"-"` + IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"` + TemplateID param.Opt[string] `query:"template_id,omitzero" json:"-"` + Limit param.Opt[int64] `query:"limit,omitzero" json:"-"` + AfterID param.Opt[string] `query:"after_id,omitzero" json:"-"` + BeforeID param.Opt[string] `query:"before_id,omitzero" json:"-"` + paramObj +} + +func (r ChannelPairingListParams) URLQuery() (url.Values, error) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{}) +} diff --git a/forward/client.go b/forward/client.go index 006a58a..0782c12 100644 --- a/forward/client.go +++ b/forward/client.go @@ -12,6 +12,7 @@ const DefaultBaseURL = "https://api.qoder.com/api/v1/forward/" // Client exposes Forward resources. Configure options before sharing a client. type Client struct { + Dreams DreamService Webhooks WebhookService Options []option.RequestOption Templates TemplateService @@ -47,6 +48,7 @@ func NewClient(opts ...option.RequestOption) Client { defaults = append(defaults, convention.EnvironmentAuthentication()) opts = slices.Concat(defaults, opts) return Client{Options: opts, + Dreams: NewDreamService(opts...), Webhooks: NewWebhookService(opts...), Templates: NewTemplateService(opts...), Identities: NewIdentityService(opts...), diff --git a/forward/client_test.go b/forward/client_test.go index 517596e..83d8473 100644 --- a/forward/client_test.go +++ b/forward/client_test.go @@ -34,8 +34,8 @@ func TestForwardAPIInventory(t *testing.T) { } } walk(root) - if len(actual) != 138 { - t.Fatalf("HTTP methods: %d want 138", len(actual)) + if len(actual) != 146 { + t.Fatalf("HTTP methods: %d want 146", len(actual)) } baseline := map[string]bool{} for _, c := range operations(t) { diff --git a/forward/configuration_types.go b/forward/configuration_types.go index a0f7781..329802b 100644 --- a/forward/configuration_types.go +++ b/forward/configuration_types.go @@ -1,6 +1,8 @@ package forward import ( + "encoding/json" + "fmt" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/apijson" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/param" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/respjson" @@ -223,10 +225,13 @@ func (r SkillBindingParam) MarshalJSON() ([]byte, error) { func (r *SkillBindingParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } type MultiagentEntry struct { + OfString string `json:"-"` + Model string `json:"model"` Type string `json:"type" api:"required"` TemplateID string `json:"template_id"` Name string `json:"name"` JSON struct { + Model respjson.Field Type respjson.Field TemplateID respjson.Field Name respjson.Field @@ -235,10 +240,22 @@ type MultiagentEntry struct { } `json:"-"` } -func (r MultiagentEntry) RawJSON() string { return r.JSON.raw } -func (r *MultiagentEntry) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } +// UnmarshalAPIJSON keeps string roster entries intact when nested in a Template. +func (r *MultiagentEntry) UnmarshalAPIJSON(data []byte) error { return r.UnmarshalJSON(data) } + +func (r MultiagentEntry) RawJSON() string { return r.JSON.raw } +func (r *MultiagentEntry) UnmarshalJSON(data []byte) error { + *r = MultiagentEntry{} + if len(data) > 0 && data[0] == '"' { + r.JSON.raw = string(data) + return json.Unmarshal(data, &r.OfString) + } + return apijson.UnmarshalRoot(data, r) +} type MultiagentEntryParam struct { + OfString param.Opt[string] `json:"-"` + Model param.Opt[string] `json:"model,omitzero"` Type string `json:"type" api:"required"` TemplateID param.Opt[string] `json:"template_id,omitzero"` Name param.Opt[string] `json:"name,omitzero"` @@ -246,10 +263,27 @@ type MultiagentEntryParam struct { } func (r MultiagentEntryParam) MarshalJSON() ([]byte, error) { + if r.OfString.Valid() { + if r.Type != "" || r.Model.Valid() || r.TemplateID.Valid() || r.Name.Valid() { + return nil, fmt.Errorf("multiagent entry must be either a template ID or an object") + } + return json.Marshal(r.OfString.Value) + } type shadow MultiagentEntryParam return param.MarshalObject(r, (*shadow)(&r)) } +func (r *MultiagentEntryParam) UnmarshalAPIJSON(data []byte) error { return r.UnmarshalJSON(data) } + func (r *MultiagentEntryParam) UnmarshalJSON(data []byte) error { + *r = MultiagentEntryParam{} + if len(data) > 0 && data[0] == '"' { + var id string + if err := json.Unmarshal(data, &id); err != nil { + return err + } + *r = MultiagentEntryParam{OfString: String(id)} + return nil + } return apijson.UnmarshalRoot(data, r) } @@ -522,9 +556,10 @@ func (r SkillOverrideParam) MarshalJSON() ([]byte, error) { func (r *SkillOverrideParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } type IdentityConfigSpec struct { + ManagedToolConfig map[string]*ManagedCapabilityOverride `json:"managed_tool_config"` System SystemOverride `json:"system"` Model ModelConfig `json:"model"` - Tools map[string]ToolOverride `json:"tools"` + Tools IdentityToolsUnion `json:"tools"` MCPServers map[string]MCPServerOverride `json:"mcp_servers"` Skills map[string]SkillOverride `json:"skills"` Toolsets map[string]any `json:"toolsets"` @@ -534,6 +569,7 @@ type IdentityConfigSpec struct { GitHubRepositories map[string]GitHubRepository `json:"github_repositories"` EnvironmentVariables map[string]EnvironmentVariableOverride `json:"environment_variables"` JSON struct { + ManagedToolConfig respjson.Field System respjson.Field Model respjson.Field Tools respjson.Field @@ -554,9 +590,10 @@ func (r IdentityConfigSpec) RawJSON() string { return r.JSON.ra func (r *IdentityConfigSpec) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } type IdentityConfigSpecParam struct { + ManagedToolConfig map[string]ManagedCapabilityOverrideParam `json:"managed_tool_config,omitzero"` System SystemOverrideParam `json:"system,omitzero"` Model ModelConfigUnionParam `json:"model,omitzero"` - Tools map[string]ToolOverrideParam `json:"tools,omitzero"` + Tools IdentityToolsUnionParam `json:"tools,omitzero"` MCPServers map[string]MCPServerOverrideParam `json:"mcp_servers,omitzero"` Skills map[string]SkillOverrideParam `json:"skills,omitzero"` Toolsets map[string]any `json:"toolsets,omitzero"` @@ -585,7 +622,7 @@ type SessionEventParam struct { DenyMessage param.Opt[string] `json:"deny_message,omitzero"` IsError param.Opt[bool] `json:"is_error,omitzero"` Description param.Opt[string] `json:"description,omitzero"` - Rubric param.Opt[string] `json:"rubric,omitzero"` + Rubric OutcomeRubricParam `json:"rubric,omitzero"` OutcomeID param.Opt[string] `json:"outcome_id,omitzero"` MaxIterations param.Opt[int64] `json:"max_iterations,omitzero"` paramObj @@ -623,3 +660,61 @@ func (r ManagedToolConfigParam) MarshalJSON() ([]byte, error) { func (r *ManagedToolConfigParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +// IdentityToolsUnionParam overrides individual tools or replaces the complete tools array. +type IdentityToolsUnionParam struct { + OfOverrides map[string]ToolOverrideParam `json:",omitzero,inline"` + OfArray []ToolParam `json:",omitzero,inline"` + paramUnion +} + +func (r IdentityToolsUnionParam) MarshalJSON() ([]byte, error) { + return param.MarshalUnion(r, r.OfOverrides, r.OfArray) +} +func (r *IdentityToolsUnionParam) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type IdentityToolsUnion struct { + OfOverrides map[string]*ToolOverride + OfArray []Tool + raw string +} + +func (r IdentityToolsUnion) RawJSON() string { return r.raw } +func (r *IdentityToolsUnion) UnmarshalJSON(data []byte) error { + *r = IdentityToolsUnion{raw: string(data)} + if len(data) > 0 && data[0] == '[' { + return json.Unmarshal(data, &r.OfArray) + } + return json.Unmarshal(data, &r.OfOverrides) +} + +// ManagedCapabilityOverrideParam is a sparse Identity capability switch. +// Use param.NullStruct to restore inheritance for one selector. +type ManagedCapabilityOverrideParam struct { + Enabled bool `json:"enabled" api:"required"` + paramObj +} + +func (r ManagedCapabilityOverrideParam) MarshalJSON() ([]byte, error) { + type shadow ManagedCapabilityOverrideParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *ManagedCapabilityOverrideParam) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type ManagedCapabilityOverride struct { + Enabled bool `json:"enabled"` + JSON struct { + Enabled respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r ManagedCapabilityOverride) RawJSON() string { return r.JSON.raw } +func (r *ManagedCapabilityOverride) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} diff --git a/forward/dream.go b/forward/dream.go new file mode 100644 index 0000000..7a52f95 --- /dev/null +++ b/forward/dream.go @@ -0,0 +1,262 @@ +package forward + +import ( + "context" + "fmt" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/apijson" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/apiquery" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/pagination" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/param" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/respjson" + "net/http" + "net/url" + "slices" + "time" +) + +// DreamService consolidates Forward memory; requires PAT or an admin SAT. +type DreamService struct{ Options []option.RequestOption } + +func NewDreamService(opts ...option.RequestOption) DreamService { + return DreamService{Options: slices.Clone(opts)} +} +func (r *DreamService) New(ctx context.Context, params DreamNewParams, opts ...option.RequestOption) (res *Dream, err error) { + opts = slices.Concat(r.Options, opts) + err = convention.ExecuteNewRequest(ctx, http.MethodPost, "dreams", params, &res, opts...) + return res, err +} +func (r *DreamService) List(ctx context.Context, params DreamListParams, opts ...option.RequestOption) (res *pagination.Page[Dream], err error) { + opts = slices.Concat(r.Options, opts) + var raw *http.Response + opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) + cfg, err := convention.NewRequestConfig(ctx, http.MethodGet, "dreams", params, &res, opts...) + if err != nil { + return nil, err + } + if err = cfg.Execute(); err != nil { + return nil, err + } + res.SetPageConfig(cfg, raw) + return res, nil +} +func (r *DreamService) ListAutoPaging(ctx context.Context, params DreamListParams, opts ...option.RequestOption) *pagination.PageAutoPager[Dream] { + return pagination.NewPageAutoPager(r.List(ctx, params, opts...)) +} +func (r *DreamService) Get(ctx context.Context, dreamID string, opts ...option.RequestOption) (res *Dream, err error) { + if dreamID == "" { + return nil, fmt.Errorf("missing required dream_id parameter") + } + opts = slices.Concat(r.Options, opts) + path := fmt.Sprintf("dreams/%s", url.PathEscape(dreamID)) + err = convention.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return res, err +} +func (r *DreamService) Cancel(ctx context.Context, dreamID string, opts ...option.RequestOption) (res *Dream, err error) { + if dreamID == "" { + return nil, fmt.Errorf("missing required dream_id parameter") + } + opts = slices.Concat(r.Options, opts) + path := fmt.Sprintf("dreams/%s/cancel", url.PathEscape(dreamID)) + err = convention.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...) + return res, err +} +func (r *DreamService) Archive(ctx context.Context, dreamID string, opts ...option.RequestOption) (res *Dream, err error) { + if dreamID == "" { + return nil, fmt.Errorf("missing required dream_id parameter") + } + opts = slices.Concat(r.Options, opts) + path := fmt.Sprintf("dreams/%s/archive", url.PathEscape(dreamID)) + err = convention.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...) + return res, err +} + +type DreamListParams struct { + Limit param.Opt[int64] `query:"limit,omitzero" json:"-"` + AfterID param.Opt[string] `query:"after_id,omitzero" json:"-"` + BeforeID param.Opt[string] `query:"before_id,omitzero" json:"-"` + IncludeArchived param.Opt[bool] `query:"include_archived,omitzero" json:"-"` + paramObj +} + +func (r DreamListParams) URLQuery() (url.Values, error) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{}) +} + +type DreamNewParams struct { + Inputs []DreamInputParam `json:"inputs,omitzero"` + Model DreamNewParamsModelUnion `json:"model,omitzero"` + Instructions param.Opt[string] `json:"instructions,omitzero"` + paramObj +} + +func (r DreamNewParams) MarshalJSON() ([]byte, error) { + type shadow DreamNewParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *DreamNewParams) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +type DreamInputParam struct { + Type string `json:"type"` + MemoryStoreID param.Opt[string] `json:"memory_store_id,omitzero"` + IdentityID param.Opt[string] `json:"identity_id,omitzero"` + TemplateID param.Opt[string] `json:"template_id,omitzero"` + SessionIDs []string `json:"session_ids,omitzero"` + paramObj +} + +func (r DreamInputParam) MarshalJSON() ([]byte, error) { + type shadow DreamInputParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *DreamInputParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +type DreamModelConfigParam struct { + ID string `json:"id"` + Effort param.Opt[string] `json:"effort,omitzero"` + ContextWindow param.Opt[int64] `json:"context_window,omitzero"` + paramObj +} + +func (r DreamModelConfigParam) MarshalJSON() ([]byte, error) { + type shadow DreamModelConfigParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *DreamModelConfigParam) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// DreamNewParamsModelUnion accepts a model ID or tuning object; omit to use auto. +type DreamNewParamsModelUnion struct { + OfString param.Opt[string] `json:",omitzero,inline"` + OfConfig *DreamModelConfigParam `json:",omitzero,inline"` + paramUnion +} + +func (r DreamNewParamsModelUnion) MarshalJSON() ([]byte, error) { + return param.MarshalUnion(r, r.OfString, r.OfConfig) +} +func (r *DreamNewParamsModelUnion) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type Dream struct { + ID string `json:"id"` + Type string `json:"type"` + Status string `json:"status"` + Inputs []DreamInput `json:"inputs"` + Outputs []DreamOutput `json:"outputs"` + Model DreamModelConfig `json:"model"` + Instructions string `json:"instructions"` + Usage DreamUsage `json:"usage"` + Error DreamError `json:"error"` + CreatedAt time.Time `json:"created_at"` + EndedAt time.Time `json:"ended_at"` + ArchivedAt time.Time `json:"archived_at"` + JSON struct { + ID respjson.Field + Type respjson.Field + Status respjson.Field + Inputs respjson.Field + Outputs respjson.Field + Model respjson.Field + Instructions respjson.Field + Usage respjson.Field + Error respjson.Field + CreatedAt respjson.Field + EndedAt respjson.Field + ArchivedAt respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r Dream) RawJSON() string { return r.JSON.raw } +func (r *Dream) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +type DreamInput struct { + Type string `json:"type"` + MemoryStoreID string `json:"memory_store_id"` + IdentityID string `json:"identity_id"` + TemplateID string `json:"template_id"` + SessionIDs []string `json:"session_ids"` + JSON struct { + Type respjson.Field + MemoryStoreID respjson.Field + IdentityID respjson.Field + TemplateID respjson.Field + SessionIDs respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r DreamInput) RawJSON() string { return r.JSON.raw } +func (r *DreamInput) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +type DreamOutput struct { + Type string `json:"type"` + MemoryStoreID string `json:"memory_store_id"` + FilesTouched []string `json:"files_touched"` + JSON struct { + Type respjson.Field + MemoryStoreID respjson.Field + FilesTouched respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r DreamOutput) RawJSON() string { return r.JSON.raw } +func (r *DreamOutput) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +type DreamModelConfig struct { + ID string `json:"id"` + Effort string `json:"effort"` + ContextWindow int64 `json:"context_window"` + JSON struct { + ID respjson.Field + Effort respjson.Field + ContextWindow respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r DreamModelConfig) RawJSON() string { return r.JSON.raw } +func (r *DreamModelConfig) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +type DreamUsage struct { + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` + CacheReadInputTokens int64 `json:"cache_read_input_tokens"` + JSON struct { + InputTokens respjson.Field + OutputTokens respjson.Field + CacheCreationInputTokens respjson.Field + CacheReadInputTokens respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r DreamUsage) RawJSON() string { return r.JSON.raw } +func (r *DreamUsage) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +type DreamError struct { + Type string `json:"type"` + Code string `json:"code"` + Message string `json:"message"` + JSON struct { + Type respjson.Field + Code respjson.Field + Message respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r DreamError) RawJSON() string { return r.JSON.raw } +func (r *DreamError) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } diff --git a/forward/identity.go b/forward/identity.go index 6c0997a..3d98973 100644 --- a/forward/identity.go +++ b/forward/identity.go @@ -85,6 +85,7 @@ func (r *IdentityService) New(ctx context.Context, params IdentityNewParams, opt } type IdentityNewParams struct { + AutoMemoryStore param.Opt[bool] `json:"auto_memory_store,omitzero"` // End-user ID in the integrator's system; cannot be empty or whitespace only. ExternalID string `json:"external_id" api:"required"` // Display name; when provided it cannot be empty or whitespace only. @@ -149,6 +150,7 @@ func (r *IdentityService) Update(ctx context.Context, identityID string, params } type IdentityUpdateParams struct { + AutoMemoryStore param.Opt[bool] `json:"auto_memory_store,omitzero"` // Replaces the existing end-user ID. ExternalID param.Opt[string] `json:"external_id,omitzero"` // Replaces the display name. @@ -347,6 +349,7 @@ func (r IdentityStats) RawJSON() string { return r.JSON.raw } func (r *IdentityStats) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } type Identity struct { + AutoMemoryStore bool `json:"auto_memory_store"` // Forward Identity ID; the `idn_` prefix is recommended. ID string `json:"id"` // End-user ID in the integrator's system. @@ -364,16 +367,17 @@ type Identity struct { // Last update time, in RFC 3339 format. UpdatedAt time.Time `json:"updated_at" format:"date-time"` JSON struct { - ID respjson.Field - ExternalID respjson.Field - Name respjson.Field - IdentityType respjson.Field - Enabled respjson.Field - Metadata respjson.Field - CreatedAt respjson.Field - UpdatedAt respjson.Field - ExtraFields map[string]respjson.Field - raw string + AutoMemoryStore respjson.Field + ID respjson.Field + ExternalID respjson.Field + Name respjson.Field + IdentityType respjson.Field + Enabled respjson.Field + Metadata respjson.Field + CreatedAt respjson.Field + UpdatedAt respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } diff --git a/forward/identitymemorystore.go b/forward/identitymemorystore.go index 09f8a5a..65e6124 100644 --- a/forward/identitymemorystore.go +++ b/forward/identitymemorystore.go @@ -10,7 +10,9 @@ import ( "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/apijson" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/apiquery" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/pagination" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/param" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/respjson" ) @@ -25,7 +27,7 @@ func NewIdentityMemoryStoreService(opts ...option.RequestOption) IdentityMemoryS } // List Memory Store mounts on an Identity -func (r *IdentityMemoryStoreService) List(ctx context.Context, identityID string, templateID string, opts ...option.RequestOption) (res *IdentityMemoryStoreListResponse, err error) { +func (r *IdentityMemoryStoreService) List(ctx context.Context, identityID string, templateID string, params IdentityMemoryStoreListParams, opts ...option.RequestOption) (res *pagination.Page[MemoryStoreMount], err error) { if identityID == "" { return nil, fmt.Errorf("missing required identity_id parameter") } @@ -35,8 +37,17 @@ func (r *IdentityMemoryStoreService) List(ctx context.Context, identityID string opts = slices.Concat(r.Options, opts) path := fmt.Sprintf("identities/%s/templates/%s/memory_stores", url.PathEscape(identityID), url.PathEscape(templateID)) - err = convention.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return res, err + var raw *http.Response + opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) + cfg, err := convention.NewRequestConfig(ctx, http.MethodGet, path, params, &res, opts...) + if err != nil { + return nil, err + } + if err = cfg.Execute(); err != nil { + return nil, err + } + res.SetPageConfig(cfg, raw) + return res, nil } // Mount Memory Store on an Identity @@ -55,6 +66,7 @@ func (r *IdentityMemoryStoreService) Mount(ctx context.Context, identityID strin } type IdentityMemoryStoreMountParams struct { + Access param.Opt[string] `json:"access,omitzero"` // ID of the Memory Store to mount (`memstore_...`). It must be an active Store // visible to the caller. MemoryStoreID string `json:"memory_store_id" api:"required"` @@ -139,9 +151,13 @@ func (r MemoryStoreMount) RawJSON() string { return r.JSON.raw func (r *MemoryStoreMount) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } type IdentityMemoryStoreListResponse struct { + FirstID string `json:"first_id"` + LastID string `json:"last_id"` Data []MemoryStoreMount `json:"data"` HasMore bool `json:"has_more"` JSON struct { + FirstID respjson.Field + LastID respjson.Field Data respjson.Field HasMore respjson.Field ExtraFields map[string]respjson.Field @@ -153,3 +169,17 @@ func (r IdentityMemoryStoreListResponse) RawJSON() string { return r.JSON.raw } func (r *IdentityMemoryStoreListResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +type IdentityMemoryStoreListParams struct { + Limit param.Opt[int64] `query:"limit,omitzero" json:"-"` + AfterID param.Opt[string] `query:"after_id,omitzero" json:"-"` + BeforeID param.Opt[string] `query:"before_id,omitzero" json:"-"` + paramObj +} + +func (r IdentityMemoryStoreListParams) URLQuery() (url.Values, error) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{}) +} +func (r *IdentityMemoryStoreService) ListAutoPaging(ctx context.Context, identityID, templateID string, params IdentityMemoryStoreListParams, opts ...option.RequestOption) *pagination.PageAutoPager[MemoryStoreMount] { + return pagination.NewPageAutoPager(r.List(ctx, identityID, templateID, params, opts...)) +} diff --git a/forward/identitymemorystore_test.go b/forward/identitymemorystore_test.go index 8d3230f..d303aff 100644 --- a/forward/identitymemorystore_test.go +++ b/forward/identitymemorystore_test.go @@ -12,7 +12,7 @@ func TestIdentityMemoryStoreAPIContracts(t *testing.T) { for _, status := range []int{200, 400} { t.Run(statusName(status), func(t *testing.T) { client := contractClient(t, "listMemoryStoreMounts", status) - res, err := client.Identities.MemoryStores.List(context.Background(), pathSegment, pathSegment) + res, err := client.Identities.MemoryStores.List(context.Background(), pathSegment, pathSegment, contractParams[forward.IdentityMemoryStoreListParams](t, "listMemoryStoreMounts")) checkError(t, status, err) if status == 200 { checkDecoded(t, res) diff --git a/forward/memorystore_live_test.go b/forward/memorystore_live_test.go index 05a1d1b..3b7b02d 100644 --- a/forward/memorystore_live_test.go +++ b/forward/memorystore_live_test.go @@ -36,6 +36,6 @@ func TestMemoryStoreAndMemoryLifecycleLive(t *testing.T) { _, err := s.client.Identities.MemoryStores.Detach(ctx, identity.ID, template.ID, store.ID) return err }) - _, err = s.client.Identities.MemoryStores.List(ctx, identity.ID, template.ID) + _, err = s.client.Identities.MemoryStores.List(ctx, identity.ID, template.ID, forward.IdentityMemoryStoreListParams{}) liveCheck(t, err) } diff --git a/forward/official_contract_test.go b/forward/official_contract_test.go new file mode 100644 index 0000000..5bab9df --- /dev/null +++ b/forward/official_contract_test.go @@ -0,0 +1,215 @@ +package forward_test + +import ( + "context" + "encoding/json" + "fmt" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/param" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" + "io" + "net/http" + "reflect" + "testing" +) + +func TestOfficialOutcomeRubricContract(t *testing.T) { + for _, rubric := range []forward.OutcomeRubricParam{{Type: "text", Content: forward.String("report exists")}, {Type: "file", FileID: forward.String("file_test")}} { + wire, err := json.Marshal(forward.SessionEventParam{Type: "user.define_outcome", Rubric: rubric}) + if err != nil { + t.Fatal(err) + } + var event forward.SessionEvent + if err = json.Unmarshal(wire, &event); err != nil { + t.Fatal(err) + } + if !event.JSON.Rubric.Valid() || event.Rubric.Type != rubric.Type { + t.Fatalf("invalid rubric: %+v", event) + } + if rubric.Type == "text" && event.Rubric.Content != "report exists" { + t.Fatal(event.Rubric) + } + if rubric.Type == "file" && event.Rubric.FileID != "file_test" { + t.Fatal(event.Rubric) + } + } +} + +func TestOfficialNewAPIContracts(t *testing.T) { + for _, tc := range []struct { + id string + call func(forward.Client) (any, error) + }{ + {"createDream", func(c forward.Client) (any, error) { + return c.Dreams.New(context.Background(), contractParams[forward.DreamNewParams](t, "createDream")) + }}, + {"listDreams", func(c forward.Client) (any, error) { + return c.Dreams.List(context.Background(), contractParams[forward.DreamListParams](t, "listDreams")) + }}, + {"getDream", func(c forward.Client) (any, error) { return c.Dreams.Get(context.Background(), pathSegment) }}, + {"cancelDream", func(c forward.Client) (any, error) { return c.Dreams.Cancel(context.Background(), pathSegment) }}, + {"archiveDream", func(c forward.Client) (any, error) { return c.Dreams.Archive(context.Background(), pathSegment) }}, + {"listChannelPairings", func(c forward.Client) (any, error) { + return c.ChannelPairings.List(context.Background(), contractParams[forward.ChannelPairingListParams](t, "listChannelPairings")) + }}, + {"getChannelPairing", func(c forward.Client) (any, error) { return c.ChannelPairings.Get(context.Background(), pathSegment) }}, + {"updateChannelPairing", func(c forward.Client) (any, error) { + return c.ChannelPairings.Update(context.Background(), pathSegment, contractParams[forward.ChannelPairingUpdateParams](t, "updateChannelPairing")) + }}, + } { + t.Run(tc.id, func(t *testing.T) { + res, err := tc.call(contractClient(t, tc.id, 200)) + if err != nil { + t.Fatal(err) + } + checkDecoded(t, res) + }) + } +} + +func TestOfficialIdentityNullAndArrayOverrides(t *testing.T) { + for _, tc := range []struct { + spec forward.IdentityConfigSpecParam + want string + }{ + {forward.IdentityConfigSpecParam{Tools: forward.IdentityToolsUnionParam{OfArray: []forward.ToolParam{}}}, `{"tools":[]}`}, + {forward.IdentityConfigSpecParam{Tools: forward.IdentityToolsUnionParam{OfOverrides: map[string]forward.ToolOverrideParam{"Bash": {Enabled: forward.Bool(false)}}}}, `{"tools":{"Bash":{"enabled":false}}}`}, + {forward.IdentityConfigSpecParam{Tools: param.NullStruct[forward.IdentityToolsUnionParam](), ManagedToolConfig: param.NullMap[map[string]forward.ManagedCapabilityOverrideParam]()}, `{"tools":null,"managed_tool_config":null}`}, + {forward.IdentityConfigSpecParam{ManagedToolConfig: map[string]forward.ManagedCapabilityOverrideParam{"schedule": {Enabled: false}, "drive": param.NullStruct[forward.ManagedCapabilityOverrideParam]()}}, `{"managed_tool_config":{"schedule":{"enabled":false},"drive":null}}`}, + } { + var want any + _ = json.Unmarshal([]byte(tc.want), &want) + body, err := json.Marshal(tc.spec) + if err != nil { + t.Fatal(err) + } + var got any + _ = json.Unmarshal(body, &got) + if !reflect.DeepEqual(got, want) { + t.Fatalf("got %s want %s", body, tc.want) + } + var spec forward.IdentityConfigSpec + if err = json.Unmarshal(body, &spec); err != nil { + t.Fatal(err) + } + } + var roster forward.MultiagentConfig + raw := `{"type":"roster","agents":["tmpl_helper",{"type":"advisor","model":"ultimate"},{"type":"self"}]}` + if err := json.Unmarshal([]byte(raw), &roster); err != nil { + t.Fatal(err) + } + if roster.Agents[0].OfString != "tmpl_helper" || roster.Agents[1].Model != "ultimate" { + t.Fatal(roster) + } + var params forward.MultiagentConfigParam + if err := json.Unmarshal([]byte(raw), ¶ms); err != nil { + t.Fatal(err) + } + body, err := json.Marshal(params) + if err != nil { + t.Fatal(err) + } + var got, want any + _ = json.Unmarshal(body, &got) + _ = json.Unmarshal([]byte(raw), &want) + if !reflect.DeepEqual(got, want) { + t.Fatalf("roster: %s", body) + } +} + +func TestOfficialMountPaginationRetainsScope(t *testing.T) { + for _, reverse := range []bool{false, true} { + calls := 0 + key := "after_id" + if reverse { + key = "before_id" + } + client := testClient(func(r *http.Request) (*http.Response, error) { + calls++ + q := r.URL.Query() + if r.URL.Path != "/api/v1/forward/identities/idn/templates/tmpl/memory_stores" || q.Get("limit") != "1" { + t.Fatal(r.URL) + } + want := "start" + if calls == 2 { + want = "mem_one" + } + if q.Get(key) != want { + t.Fatal(r.URL) + } + id := "mem_one" + more := true + if calls == 2 { + id = "mem_two" + more = false + } + return reply(r, 200, fmt.Sprintf(`{"data":[{"memory_store_id":%q,"access":"read_write"}],"first_id":%q,"last_id":%q,"has_more":%t}`, id, id, id, more)), nil + }) + p := forward.IdentityMemoryStoreListParams{Limit: forward.Int(1)} + if reverse { + p.BeforeID = forward.String("start") + } else { + p.AfterID = forward.String("start") + } + pager := client.Identities.MemoryStores.ListAutoPaging(context.Background(), "idn", "tmpl", p) + var ids []string + for pager.Next() { + ids = append(ids, pager.Current().MemoryStoreID) + } + if pager.Err() != nil || !reflect.DeepEqual(ids, []string{"mem_one", "mem_two"}) || calls != 2 { + t.Fatalf("%v %v calls=%d", ids, pager.Err(), calls) + } + } +} + +func TestOfficialForwardParameterPlacement(t *testing.T) { + client := testClient(func(r *http.Request) (*http.Response, error) { + var body []byte + if r.Body != nil { + body, _ = io.ReadAll(r.Body) + } + switch r.URL.Path { + case "/api/v1/forward/identities/idn": + if string(body) != `{"auto_memory_store":false}` { + t.Fatal(string(body)) + } + return reply(r, 200, `{"id":"idn","auto_memory_store":false}`), nil + case "/api/v1/forward/identities/idn/templates/tmpl/memory_stores": + var b map[string]any + _ = json.Unmarshal(body, &b) + if b["access"] != "read_write" || b["memory_store_id"] != "mem" { + t.Fatal(b) + } + return reply(r, 200, `{"memory_store_id":"mem","access":"read_write"}`), nil + case "/api/v1/forward/schedules/sched": + var b map[string]any + _ = json.Unmarshal(body, &b) + if len(b) != 1 || b["resources"] == nil { + t.Fatal(b) + } + return reply(r, 200, `{"resources":[{"type":"file","file_id":"file_test","mount_path":"/data/test.txt"}]}`), nil + case "/api/v1/forward/schedule_runs/run": + if r.URL.Query().Get("include_initial_events") != "true" || len(body) != 0 { + t.Fatalf("%s %s", r.URL, body) + } + return reply(r, 200, `{"initial_events":[{"type":"user.message","content":[{"type":"text","text":"run input"}]}]}`), nil + } + t.Fatal(r.URL) + return nil, nil + }) + ctx := context.Background() + id, err := client.Identities.Update(ctx, "idn", forward.IdentityUpdateParams{AutoMemoryStore: forward.Bool(false)}) + if err != nil || !id.JSON.AutoMemoryStore.Valid() || id.AutoMemoryStore { + t.Fatalf("%+v %v", id, err) + } + if _, err = client.Identities.MemoryStores.Mount(ctx, "idn", "tmpl", forward.IdentityMemoryStoreMountParams{MemoryStoreID: "mem", Access: forward.String("read_write")}); err != nil { + t.Fatal(err) + } + sched, err := client.Schedules.Update(ctx, "sched", forward.ScheduleUpdateParams{Resources: []forward.ScheduleFileResourceParam{{Type: "file", FileID: "file_test", MountPath: forward.String("/data/test.txt")}}}) + if err != nil || len(sched.Resources) != 1 || !sched.JSON.Resources.Valid() { + t.Fatalf("%+v %v", sched, err) + } + run, err := client.ScheduleRuns.Get(ctx, "run", forward.ScheduleRunGetParams{IncludeInitialEvents: forward.Bool(true)}) + if err != nil || len(run.InitialEvents) != 1 || !run.JSON.InitialEvents.Valid() { + t.Fatalf("%+v %v", run, err) + } +} diff --git a/forward/outcomerubric.go b/forward/outcomerubric.go new file mode 100644 index 0000000..a1636e7 --- /dev/null +++ b/forward/outcomerubric.go @@ -0,0 +1,39 @@ +package forward + +import ( + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/apijson" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/param" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/respjson" +) + +// OutcomeRubricParam defines acceptance criteria as inline text or an uploaded file. +// Set Type to "text" with Content, or "file" with FileID. +type OutcomeRubricParam struct { + Type string `json:"type" api:"required"` + Content param.Opt[string] `json:"content,omitzero"` + FileID param.Opt[string] `json:"file_id,omitzero"` + paramObj +} + +func (r OutcomeRubricParam) MarshalJSON() ([]byte, error) { + type shadow OutcomeRubricParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *OutcomeRubricParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +// OutcomeRubric contains the text or file reference used to evaluate an Outcome. +type OutcomeRubric struct { + Type string `json:"type"` + Content string `json:"content"` + FileID string `json:"file_id"` + JSON struct { + Type respjson.Field + Content respjson.Field + FileID respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r OutcomeRubric) RawJSON() string { return r.JSON.raw } +func (r *OutcomeRubric) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } diff --git a/forward/protocol_test.go b/forward/protocol_test.go index ec6c73d..fd18ab4 100644 --- a/forward/protocol_test.go +++ b/forward/protocol_test.go @@ -65,7 +65,7 @@ func TestOptionalNullAndUnionParameters(t *testing.T) { func TestIdentityOverridesPreserveDynamicKeys(t *testing.T) { params := forward.IdentityConfigUpsertParams{IdentityConfig: forward.IdentityConfigSpecParam{ - Tools: map[string]forward.ToolOverrideParam{"Read": {Enabled: forward.Bool(false)}}, + Tools: forward.IdentityToolsUnionParam{OfOverrides: map[string]forward.ToolOverrideParam{"Read": {Enabled: forward.Bool(false)}}}, Skills: map[string]forward.SkillOverrideParam{"skill_one": {Enabled: forward.Bool(false)}, "skill_inherit": param.NullStruct[forward.SkillOverrideParam]()}, MCPServers: map[string]forward.MCPServerOverrideParam{"crm": {Enabled: forward.Bool(true), URL: forward.String("https://crm.test/mcp")}}, GitHubRepositories: map[string]forward.GitHubRepositoryParam{"custom_binding": {AuthorizationToken: forward.String("test_token"), Enabled: param.Null[bool]()}}, diff --git a/forward/schedule.go b/forward/schedule.go index bba10ae..fc8d3d7 100644 --- a/forward/schedule.go +++ b/forward/schedule.go @@ -87,6 +87,7 @@ func (r *ScheduleService) New(ctx context.Context, params ScheduleNewParams, opt } type ScheduleNewParams struct { + Resources []ScheduleFileResourceParam `json:"resources,omitzero"` // Forward Identity ID the Schedule belongs to. IdentityID string `json:"identity_id" api:"required"` // Forward Template ID to execute. @@ -176,6 +177,7 @@ func (r *ScheduleService) Update(ctx context.Context, scheduleID string, params } type ScheduleUpdateParams struct { + Resources []ScheduleFileResourceParam `json:"resources,omitzero"` // New Schedule name. Name param.Opt[string] `json:"name,omitzero"` // New Schedule description. @@ -321,6 +323,7 @@ func (r *ScheduleArchiveManyResponse) UnmarshalJSON(data []byte) error { } type Schedule struct { + Resources []ScheduleFileResource `json:"resources"` // Authoritative creation method of the Schedule: api or tool. Source string `json:"source"` SourceSessionID string `json:"source_session_id" api:"nullable"` @@ -343,6 +346,7 @@ type Schedule struct { ArchivedAt time.Time `json:"archived_at" format:"date-time"` PausedReason SchedulePausedReason `json:"paused_reason"` JSON struct { + Resources respjson.Field Source respjson.Field SourceSessionID respjson.Field InitialEventsVersion respjson.Field @@ -475,3 +479,37 @@ func (r ScheduleInitialEventsItem) RawJSON() string { return r.JSON.raw } func (r *ScheduleInitialEventsItem) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +// ScheduleFileResourceParam attaches a Forward file to each Schedule run. +type ScheduleFileResourceParam struct { + Type string `json:"type" api:"required"` + FileID string `json:"file_id" api:"required"` + MountPath param.Opt[string] `json:"mount_path,omitzero"` + paramObj +} + +func (r ScheduleFileResourceParam) MarshalJSON() ([]byte, error) { + type shadow ScheduleFileResourceParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *ScheduleFileResourceParam) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type ScheduleFileResource struct { + Type string `json:"type"` + FileID string `json:"file_id"` + MountPath string `json:"mount_path"` + JSON struct { + Type respjson.Field + FileID respjson.Field + MountPath respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r ScheduleFileResource) RawJSON() string { return r.JSON.raw } +func (r *ScheduleFileResource) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} diff --git a/forward/schedulerun.go b/forward/schedulerun.go index 8a2d705..fc565bc 100644 --- a/forward/schedulerun.go +++ b/forward/schedulerun.go @@ -88,6 +88,7 @@ func (r *ScheduleRunService) Get(ctx context.Context, runID string, params Sched } type ScheduleRunGetParams struct { + IncludeInitialEvents param.Opt[bool] `query:"include_initial_events,omitzero" json:"-"` // Additional ownership constraint. IdentityID param.Opt[string] `query:"identity_id,omitzero" json:"-"` paramObj @@ -98,6 +99,7 @@ func (r ScheduleRunGetParams) URLQuery() (url.Values, error) { } type ScheduleRun struct { + InitialEvents []SessionEvent `json:"initial_events"` // Authoritative creation method of the Schedule: api or tool. Source string `json:"source"` SourceSessionID string `json:"source_session_id" api:"nullable"` @@ -144,6 +146,7 @@ type ScheduleRun struct { // null|Human-readable error message; structured details stay in `error`. ErrorMessage string `json:"error_message"` JSON struct { + InitialEvents respjson.Field Source respjson.Field SourceSessionID respjson.Field InitialEventsVersion respjson.Field diff --git a/forward/sessionevent.go b/forward/sessionevent.go index 1083b09..d754e17 100644 --- a/forward/sessionevent.go +++ b/forward/sessionevent.go @@ -157,7 +157,7 @@ type SessionEvent struct { Result string `json:"result"` DenyMessage string `json:"deny_message"` Description string `json:"description"` - Rubric string `json:"rubric"` + Rubric OutcomeRubric `json:"rubric"` OutcomeID string `json:"outcome_id"` MaxIterations int64 `json:"max_iterations"` MessageID string `json:"message_id"` diff --git a/forward/test_helpers_test.go b/forward/test_helpers_test.go index fc1b1f4..f566ba6 100644 --- a/forward/test_helpers_test.go +++ b/forward/test_helpers_test.go @@ -86,8 +86,8 @@ func operations(t *testing.T) []operationCase { Cases []operationCase `json:"cases"` } readJSON(t, "testdata/api-operation-cases.json", &fixture) - if fixture.Count != 138 || len(fixture.Cases) != 138 { - t.Fatal("Forward API scope must contain 138 operations") + if fixture.Count != 146 || len(fixture.Cases) != 146 { + t.Fatal("Forward API scope must contain 146 operations") } return fixture.Cases } diff --git a/forward/testdata/api-contracts.json b/forward/testdata/api-contracts.json index eb71878..594a5e8 100644 --- a/forward/testdata/api-contracts.json +++ b/forward/testdata/api-contracts.json @@ -4047,5 +4047,315 @@ "download": false, "raw": false, "empty": false + }, + { + "operation_id": "createDream", + "service": "DreamService", + "entry": "Dreams", + "name": "New", + "response": { + "id": "drm_contract", + "type": "dream", + "status": "completed", + "inputs": [ + { + "type": "memory_store", + "memory_store_id": "mem_contract" + } + ], + "outputs": [ + { + "type": "memory_store", + "memory_store_id": "mem_contract", + "files_touched": [ + "MEMORY.md" + ] + } + ], + "model": { + "id": "auto", + "effort": "none", + "context_window": 400000 + }, + "instructions": "", + "usage": { + "input_tokens": 5, + "output_tokens": 3, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "error": null, + "created_at": "2026-10-27T00:00:00Z", + "ended_at": "2026-10-27T00:01:00Z", + "archived_at": null + }, + "stream": false, + "download": false, + "raw": false, + "empty": false + }, + { + "operation_id": "listDreams", + "service": "DreamService", + "entry": "Dreams", + "name": "List", + "response": { + "data": [ + { + "id": "drm_contract", + "type": "dream", + "status": "completed", + "inputs": [ + { + "type": "memory_store", + "memory_store_id": "mem_contract" + } + ], + "outputs": [ + { + "type": "memory_store", + "memory_store_id": "mem_contract", + "files_touched": [ + "MEMORY.md" + ] + } + ], + "model": { + "id": "auto", + "effort": "none", + "context_window": 400000 + }, + "instructions": "", + "usage": { + "input_tokens": 5, + "output_tokens": 3, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "error": null, + "created_at": "2026-10-27T00:00:00Z", + "ended_at": "2026-10-27T00:01:00Z", + "archived_at": null + } + ], + "first_id": "drm_contract", + "last_id": "drm_contract", + "has_more": false + }, + "stream": false, + "download": false, + "raw": false, + "empty": false + }, + { + "operation_id": "getDream", + "service": "DreamService", + "entry": "Dreams", + "name": "Get", + "response": { + "id": "drm_contract", + "type": "dream", + "status": "completed", + "inputs": [ + { + "type": "memory_store", + "memory_store_id": "mem_contract" + } + ], + "outputs": [ + { + "type": "memory_store", + "memory_store_id": "mem_contract", + "files_touched": [ + "MEMORY.md" + ] + } + ], + "model": { + "id": "auto", + "effort": "none", + "context_window": 400000 + }, + "instructions": "", + "usage": { + "input_tokens": 5, + "output_tokens": 3, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "error": null, + "created_at": "2026-10-27T00:00:00Z", + "ended_at": "2026-10-27T00:01:00Z", + "archived_at": null + }, + "stream": false, + "download": false, + "raw": false, + "empty": false + }, + { + "operation_id": "cancelDream", + "service": "DreamService", + "entry": "Dreams", + "name": "Cancel", + "response": { + "id": "drm_contract", + "type": "dream", + "status": "completed", + "inputs": [ + { + "type": "memory_store", + "memory_store_id": "mem_contract" + } + ], + "outputs": [ + { + "type": "memory_store", + "memory_store_id": "mem_contract", + "files_touched": [ + "MEMORY.md" + ] + } + ], + "model": { + "id": "auto", + "effort": "none", + "context_window": 400000 + }, + "instructions": "", + "usage": { + "input_tokens": 5, + "output_tokens": 3, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "error": null, + "created_at": "2026-10-27T00:00:00Z", + "ended_at": "2026-10-27T00:01:00Z", + "archived_at": null + }, + "stream": false, + "download": false, + "raw": false, + "empty": false + }, + { + "operation_id": "archiveDream", + "service": "DreamService", + "entry": "Dreams", + "name": "Archive", + "response": { + "id": "drm_contract", + "type": "dream", + "status": "completed", + "inputs": [ + { + "type": "memory_store", + "memory_store_id": "mem_contract" + } + ], + "outputs": [ + { + "type": "memory_store", + "memory_store_id": "mem_contract", + "files_touched": [ + "MEMORY.md" + ] + } + ], + "model": { + "id": "auto", + "effort": "none", + "context_window": 400000 + }, + "instructions": "", + "usage": { + "input_tokens": 5, + "output_tokens": 3, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "error": null, + "created_at": "2026-10-27T00:00:00Z", + "ended_at": "2026-10-27T00:01:00Z", + "archived_at": null + }, + "stream": false, + "download": false, + "raw": false, + "empty": false + }, + { + "operation_id": "listChannelPairings", + "service": "ChannelPairingService", + "entry": "ChannelPairings", + "name": "List", + "response": { + "data": [ + { + "id": "cp_contract", + "channel_id": "ch_contract", + "identity_id": "idn_contract", + "template_id": "tmpl_contract", + "scope_type": "direct", + "scope_external_id": "user_contract", + "scope_display_name": "Contract user", + "status": "active", + "paired_at": "2026-10-27T00:00:00Z", + "created_at": "2026-10-27T00:00:00Z" + } + ], + "first_id": "cp_contract", + "last_id": "cp_contract", + "has_more": false + }, + "stream": false, + "download": false, + "raw": false, + "empty": false + }, + { + "operation_id": "getChannelPairing", + "service": "ChannelPairingService", + "entry": "ChannelPairings", + "name": "Get", + "response": { + "id": "cp_contract", + "channel_id": "ch_contract", + "identity_id": "idn_contract", + "template_id": "tmpl_contract", + "scope_type": "direct", + "scope_external_id": "user_contract", + "scope_display_name": "Contract user", + "status": "active", + "paired_at": "2026-10-27T00:00:00Z", + "created_at": "2026-10-27T00:00:00Z" + }, + "stream": false, + "download": false, + "raw": false, + "empty": false + }, + { + "operation_id": "updateChannelPairing", + "service": "ChannelPairingService", + "entry": "ChannelPairings", + "name": "Update", + "response": { + "id": "cp_contract", + "channel_id": "ch_contract", + "identity_id": "idn_contract", + "template_id": "tmpl_contract", + "scope_type": "direct", + "scope_external_id": "user_contract", + "scope_display_name": "Contract user", + "status": "active", + "paired_at": "2026-10-27T00:00:00Z", + "created_at": "2026-10-27T00:00:00Z" + }, + "stream": false, + "download": false, + "raw": false, + "empty": false } ] diff --git a/forward/testdata/api-operation-cases.json b/forward/testdata/api-operation-cases.json index 99eb27f..23edb39 100644 --- a/forward/testdata/api-operation-cases.json +++ b/forward/testdata/api-operation-cases.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "operation_count": 138, + "operation_count": 146, "cases": [ { "operation_id": "addSessionResource", @@ -5752,6 +5752,282 @@ "page": "start +/=?" } } + }, + { + "operation_id": "createDream", + "resource": "Dreams", + "clients": { + "python": "dreams", + "typescript": "dreams", + "go": "Dreams" + }, + "methods": { + "python": "create", + "typescript": "create", + "go": "New" + }, + "http_method": "POST", + "path": "/dreams", + "parameters": [], + "request_body": { + "kind": "json", + "value": { + "inputs": [ + { + "type": "identity_template", + "identity_id": "idn_contract", + "template_id": "tmpl_contract" + }, + { + "type": "sessions", + "session_ids": [ + "sess_contract" + ] + } + ], + "model": { + "id": "auto", + "effort": "none", + "context_window": 400000 + }, + "instructions": "Keep facts" + } + } + }, + { + "operation_id": "listDreams", + "resource": "Dreams", + "clients": { + "python": "dreams", + "typescript": "dreams", + "go": "Dreams" + }, + "methods": { + "python": "list", + "typescript": "list", + "go": "List" + }, + "http_method": "GET", + "path": "/dreams", + "parameters": [ + { + "wire_name": "limit", + "location": "query", + "required": false, + "value": 1 + }, + { + "wire_name": "after_id", + "location": "query", + "required": false, + "value": "drm_before" + }, + { + "wire_name": "include_archived", + "location": "query", + "required": false, + "value": true + } + ] + }, + { + "operation_id": "getDream", + "resource": "Dreams", + "clients": { + "python": "dreams", + "typescript": "dreams", + "go": "Dreams" + }, + "methods": { + "python": "retrieve", + "typescript": "retrieve", + "go": "Get" + }, + "http_method": "GET", + "path": "/dreams/{dream_id}", + "parameters": [ + { + "wire_name": "dream_id", + "location": "path", + "required": true, + "value": "contract" + } + ] + }, + { + "operation_id": "cancelDream", + "resource": "Dreams", + "clients": { + "python": "dreams", + "typescript": "dreams", + "go": "Dreams" + }, + "methods": { + "python": "cancel", + "typescript": "cancel", + "go": "Cancel" + }, + "http_method": "POST", + "path": "/dreams/{dream_id}/cancel", + "parameters": [ + { + "wire_name": "dream_id", + "location": "path", + "required": true, + "value": "contract" + } + ] + }, + { + "operation_id": "archiveDream", + "resource": "Dreams", + "clients": { + "python": "dreams", + "typescript": "dreams", + "go": "Dreams" + }, + "methods": { + "python": "archive", + "typescript": "archive", + "go": "Archive" + }, + "http_method": "POST", + "path": "/dreams/{dream_id}/archive", + "parameters": [ + { + "wire_name": "dream_id", + "location": "path", + "required": true, + "value": "contract" + } + ] + }, + { + "operation_id": "listChannelPairings", + "resource": "ChannelPairings", + "clients": { + "python": "channel_pairings", + "typescript": "channelPairings", + "go": "ChannelPairings" + }, + "methods": { + "python": "list", + "typescript": "list", + "go": "List" + }, + "http_method": "GET", + "path": "/channel_pairings", + "parameters": [ + { + "wire_name": "channel_id", + "location": "query", + "required": false, + "value": "ch_contract" + }, + { + "wire_name": "status", + "location": "query", + "required": false, + "value": "active" + }, + { + "wire_name": "target_type", + "location": "query", + "required": false, + "value": "user" + }, + { + "wire_name": "target_external_id", + "location": "query", + "required": false, + "value": "user_contract" + }, + { + "wire_name": "identity_id", + "location": "query", + "required": false, + "value": "idn_contract" + }, + { + "wire_name": "template_id", + "location": "query", + "required": false, + "value": "tmpl_contract" + }, + { + "wire_name": "limit", + "location": "query", + "required": false, + "value": 1 + }, + { + "wire_name": "after_id", + "location": "query", + "required": false, + "value": "cp_before" + } + ] + }, + { + "operation_id": "getChannelPairing", + "resource": "ChannelPairings", + "clients": { + "python": "channel_pairings", + "typescript": "channelPairings", + "go": "ChannelPairings" + }, + "methods": { + "python": "retrieve", + "typescript": "retrieve", + "go": "Get" + }, + "http_method": "GET", + "path": "/channel_pairings/{pairing_id}", + "parameters": [ + { + "wire_name": "pairing_id", + "location": "path", + "required": true, + "value": "contract" + } + ] + }, + { + "operation_id": "updateChannelPairing", + "resource": "ChannelPairings", + "clients": { + "python": "channel_pairings", + "typescript": "channelPairings", + "go": "ChannelPairings" + }, + "methods": { + "python": "update", + "typescript": "update", + "go": "Update" + }, + "http_method": "POST", + "path": "/channel_pairings/{pairing_id}", + "parameters": [ + { + "wire_name": "pairing_id", + "location": "path", + "required": true, + "value": "contract" + } + ], + "request_body": { + "kind": "json", + "value": { + "identity_id": "idn_contract", + "template_id": "tmpl_contract" + } + } + } + ], + "extensions": [ + { + "source": "https://docs.qoder.com/cloud-agents/api/forward/dreams/create; https://docs.qoder.com/cloud-agents/api/forward/channels/list-channel-pairings; https://docs.qoder.com/cloud-agents/api/vaults/start-oauth (2026-10-27)", + "operations": 8 } ] } diff --git a/managed/agent.go b/managed/agent.go index 00cae55..c4dfa33 100644 --- a/managed/agent.go +++ b/managed/agent.go @@ -2852,35 +2852,19 @@ type ManagedAgentsModel = string // Model identifier and configuration. type ManagedAgentsModelConfig struct { - ContextWindow int64 `json:"context_window"` - - // The model that will power your agent. - // - // See [models](https://docs.qoder.com/cloud-agents/api/models/list) for additional - // details and options. - ID ManagedAgentsModel `json:"id" api:"required"` - // How hard the model works on each turn. Sets `output_config.effort` on every - // Messages call the session makes. - Effort ManagedAgentsModelConfigEffortUnion `json:"effort"` - // Geographic region for model inference. When unset, requests fall through to the - // workspace's default_inference_geo. - InferenceGeo string `json:"inference_geo"` - // 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. - // - // Any of "standard", "fast". - Speed ManagedAgentsModelConfigSpeed `json:"speed"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ContextWindow respjson.Field - - ID respjson.Field - Effort respjson.Field - InferenceGeo respjson.Field - Speed respjson.Field - ExtraFields map[string]respjson.Field - raw string + InferenceGeo string `json:"inference_geo"` + ID string `json:"id" api:"required"` + Effort string `json:"effort"` + ContextWindow int64 `json:"context_window"` + EffectiveContextWindow int64 `json:"effective_context_window"` + JSON struct { + InferenceGeo respjson.Field + ID respjson.Field + Effort respjson.Field + ContextWindow respjson.Field + EffectiveContextWindow respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } @@ -2994,34 +2978,14 @@ const ( // // The property ID is required. type ManagedAgentsModelConfigParams struct { - ContextWindow param.Opt[int64] `json:"context_window,omitzero"` - - // The model that will power your agent. - // - // See [models](https://docs.qoder.com/cloud-agents/api/models/list) for additional - // details and options. - ID ManagedAgentsModel `json:"id,omitzero" api:"required"` - // Geographic region for model inference. When unset, requests fall through to the - // workspace's default_inference_geo. On update, `model` is whole-object - // replacement — omitting inference_geo clears it. - InferenceGeo param.Opt[string] `json:"inference_geo,omitzero"` - // How hard the model works on each inference call. Accepts a bare level string - // (`"high"`) or `{"type": "high"}`. On create, omitting it resolves the per-model - // default; on update, omitting it leaves the stored value unchanged. - Effort ManagedAgentsModelConfigParamsEffortUnion `json:"effort,omitzero"` - // 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. - // - // Any of "standard", "fast". - Speed ManagedAgentsModelConfigParamsSpeed `json:"speed,omitzero"` + InferenceGeo param.Opt[string] `json:"inference_geo,omitzero"` + ID string `json:"id" api:"required"` + Effort param.Opt[string] `json:"effort,omitzero"` + ContextWindow param.Opt[int64] `json:"context_window,omitzero"` paramObj } func (r ManagedAgentsModelConfigParams) MarshalJSON() (data []byte, err error) { - if r.Speed != "" { - return nil, fmt.Errorf("Qoder does not support model.speed") - } type shadow ManagedAgentsModelConfigParams return param.MarshalObject(r, (*shadow)(&r)) } diff --git a/managed/agent_test.go b/managed/agent_test.go index 37b3c08..62acb25 100644 --- a/managed/agent_test.go +++ b/managed/agent_test.go @@ -91,14 +91,13 @@ func TestRequestFieldSemantics(t *testing.T) { if _, ok := m["authorization_token"]; ok { t.Fatal("empty legacy authorization_token sent") } - model := managed.ManagedAgentsModelConfigParams{ID: "qoder-model", ContextWindow: managed.Int(128000), Effort: managed.ManagedAgentsModelConfigParamsEffortUnion{OfManagedAgentsModelConfigsEffortManagedAgentsEffortLevel: managed.String("high")}} + model := managed.ManagedAgentsModelConfigParams{ID: "qoder-model", ContextWindow: managed.Int(128000), Effort: managed.String("high")} m = jsonObject(t, model) if m["effort"] != "high" || m["context_window"] != float64(128000) { t.Fatal(m) } - model.Speed = "fast" - if _, err := json.Marshal(model); err == nil { - t.Fatal("unsupported model.speed should be rejected") + if _, ok := m["speed"]; ok { + t.Fatal("unsupported model.speed was sent") } } @@ -115,7 +114,7 @@ func TestQoderResponseFields(t *testing.T) { t.Fatal("raw/unknown JSON lost") } } - if a.Model.Effort.Type != "high" || a.Model.Effort.AsHigh().Type != "high" || a.Model.ContextWindow != 128000 { + if a.Model.Effort != "high" || a.Model.ContextWindow != 128000 { t.Fatalf("model fields: %+v", a.Model) } var skill managed.Skill diff --git a/managed/bucketstorage.go b/managed/bucketstorage.go new file mode 100644 index 0000000..9096e46 --- /dev/null +++ b/managed/bucketstorage.go @@ -0,0 +1,109 @@ +package managed + +import ( + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/apijson" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/param" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/respjson" +) + +// BucketStorageParam declares up to eight OSS bucket mounts in a cloud Environment. +type BucketStorageParam struct { + Buckets []OSSBucketParam `json:"buckets" api:"required"` + paramObj +} + +func (r BucketStorageParam) MarshalJSON() ([]byte, error) { + type shadow BucketStorageParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *BucketStorageParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +// OSSBucketParam requires credentials for new buckets. Omit Credential on update to retain it. +type OSSBucketParam struct { + Name string `json:"name" api:"required"` + Provider string `json:"provider" api:"required"` + Region string `json:"region" api:"required"` + Bucket string `json:"bucket" api:"required"` + Prefix param.Opt[string] `json:"prefix,omitzero"` + Access param.Opt[string] `json:"access,omitzero"` + AcceleratorEndpoint param.Opt[string] `json:"accelerator_endpoint,omitzero"` + Credential OSSBucketCredentialParam `json:"credential,omitzero"` + paramObj +} + +func (r OSSBucketParam) MarshalJSON() ([]byte, error) { + type shadow OSSBucketParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *OSSBucketParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +// OSSBucketCredentialParam accepts ram_role with RoleArn, or access_key with ID and secret. +// A read-view credential without secrets may be echoed to retain it on update. +type OSSBucketCredentialParam struct { + Type string `json:"type" api:"required"` + RoleArn param.Opt[string] `json:"role_arn,omitzero"` + AccessKeyID param.Opt[string] `json:"access_key_id,omitzero"` + AccessKeySecret param.Opt[string] `json:"access_key_secret,omitzero"` + paramObj +} + +func (r OSSBucketCredentialParam) MarshalJSON() ([]byte, error) { + type shadow OSSBucketCredentialParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *OSSBucketCredentialParam) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type BucketStorage struct { + Buckets []OSSBucket `json:"buckets"` + JSON struct { + Buckets respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r BucketStorage) RawJSON() string { return r.JSON.raw } +func (r *BucketStorage) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +type OSSBucket struct { + Name string `json:"name"` + Provider string `json:"provider"` + Region string `json:"region"` + Bucket string `json:"bucket"` + Prefix string `json:"prefix"` + Access string `json:"access"` + AcceleratorEndpoint string `json:"accelerator_endpoint"` + Credential OSSBucketCredential `json:"credential"` + JSON struct { + Name respjson.Field + Provider respjson.Field + Region respjson.Field + Bucket respjson.Field + Prefix respjson.Field + Access respjson.Field + AcceleratorEndpoint respjson.Field + Credential respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r OSSBucket) RawJSON() string { return r.JSON.raw } +func (r *OSSBucket) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +// OSSBucketCredential is the API read view. It contains no role ARN or secret. +type OSSBucketCredential struct { + Type string `json:"type"` + AccessKeyID string `json:"access_key_id"` + JSON struct { + Type respjson.Field + AccessKeyID respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r OSSBucketCredential) RawJSON() string { return r.JSON.raw } +func (r *OSSBucketCredential) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } diff --git a/managed/client.go b/managed/client.go index 1b9db20..991010a 100644 --- a/managed/client.go +++ b/managed/client.go @@ -15,6 +15,7 @@ const DefaultBaseURL = "https://api.qoder.com/api/v1/cloud/" // Client exposes concrete services; nested services follow the upstream hierarchy. // A client can be shared between goroutines. Configure options before first use. type Client struct { + OAuth OAuthService Options []option.RequestOption Agents AgentService Sessions SessionService @@ -40,7 +41,8 @@ func NewClient(opts ...option.RequestOption) Client { } defaults = append(defaults, convention.EnvironmentAuthentication()) opts = slices.Concat(defaults, opts) - return Client{Options: opts, Agents: NewAgentService(opts...), Sessions: NewSessionService(opts...), Deployments: NewDeploymentService(opts...), DeploymentRuns: NewDeploymentRunService(opts...), Dreams: NewDreamService(opts...), Environments: NewEnvironmentService(opts...), Skills: NewSkillService(opts...), Vaults: NewVaultService(opts...), Files: NewFileService(opts...), MemoryStores: NewMemoryStoreService(opts...), Models: NewModelService(opts...)} + return Client{Options: opts, Agents: NewAgentService(opts...), Sessions: NewSessionService(opts...), Deployments: NewDeploymentService(opts...), DeploymentRuns: NewDeploymentRunService(opts...), OAuth: NewOAuthService(opts...), + Dreams: NewDreamService(opts...), Environments: NewEnvironmentService(opts...), Skills: NewSkillService(opts...), Vaults: NewVaultService(opts...), Files: NewFileService(opts...), MemoryStores: NewMemoryStoreService(opts...), Models: NewModelService(opts...)} } func String(v string) param.Opt[string] { return param.NewOpt(v) } diff --git a/managed/client_test.go b/managed/client_test.go index 80cda84..9bb1051 100644 --- a/managed/client_test.go +++ b/managed/client_test.go @@ -14,7 +14,7 @@ import ( func TestManagedAPIInventory(t *testing.T) { fixtures := contracts(t) - if len(fixtures) != 106 { + if len(fixtures) != 107 { t.Fatalf("got %d API contracts", len(fixtures)) } expected := map[string]bool{} diff --git a/managed/dream.go b/managed/dream.go index b154b98..2d85748 100644 --- a/managed/dream.go +++ b/managed/dream.go @@ -418,55 +418,30 @@ func (r *DreamMemoryStoreInputParam) UnmarshalJSON(data []byte) error { // Model identifier and configuration applied to every pipeline stage. Same wire // shape as the Agents API ModelConfig. type DreamModelConfig struct { - // Model identifier. 1-256 characters. - ID string `json:"id" api:"required"` - // 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. - // - // Any of "standard", "fast". - Speed DreamModelConfigSpeed `json:"speed"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - Speed respjson.Field - ExtraFields map[string]respjson.Field - raw string + ID string `json:"id"` + Effort string `json:"effort"` + ContextWindow int64 `json:"context_window"` + JSON struct { + ID respjson.Field + Effort respjson.Field + ContextWindow respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } -// Returns the unmodified JSON received from the API -func (r DreamModelConfig) RawJSON() string { return r.JSON.raw } -func (r *DreamModelConfig) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} +func (r DreamModelConfig) RawJSON() string { return r.JSON.raw } +func (r *DreamModelConfig) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// 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. -type DreamModelConfigSpeed string - -const ( - DreamModelConfigSpeedStandard DreamModelConfigSpeed = "standard" - DreamModelConfigSpeedFast DreamModelConfigSpeed = "fast" -) - -// Model identifier and configuration applied to every pipeline stage. -// -// The property ID is required. +// DreamModelConfigParam selects an available model and optional reasoning settings. type DreamModelConfigParam struct { - // Model identifier. 1-256 characters. - ID string `json:"id" api:"required"` - // 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. - // - // Any of "standard", "fast". - Speed DreamModelConfigParamSpeed `json:"speed,omitzero"` + ID string `json:"id" api:"required"` + Effort param.Opt[string] `json:"effort,omitzero"` + ContextWindow param.Opt[int64] `json:"context_window,omitzero"` paramObj } -func (r DreamModelConfigParam) MarshalJSON() (data []byte, err error) { +func (r DreamModelConfigParam) MarshalJSON() ([]byte, error) { type shadow DreamModelConfigParam return param.MarshalObject(r, (*shadow)(&r)) } @@ -474,16 +449,6 @@ func (r *DreamModelConfigParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// 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. -type DreamModelConfigParamSpeed string - -const ( - DreamModelConfigParamSpeedStandard DreamModelConfigParamSpeed = "standard" - DreamModelConfigParamSpeedFast DreamModelConfigParamSpeed = "fast" -) - // An output memory store the dream writes consolidated memories into. type DreamOutput struct { // Paths of memory files created, modified or deleted. @@ -855,7 +820,7 @@ func (r *OutputBehaviorUpdateExistingParam) UnmarshalJSON(data []byte) error { type DreamNewParams struct { Inputs []DreamInputUnionParam `json:"inputs,omitzero" api:"required"` // Model identifier and configuration applied to every pipeline stage. - Model DreamNewParamsModelUnion `json:"model,omitzero" api:"required"` + Model DreamNewParamsModelUnion `json:"model,omitzero"` Instructions param.Opt[string] `json:"instructions,omitzero"` // The default destination: the job creates a new output memory store as a clone of // the memory_store input and writes the consolidated memories into it. The input diff --git a/managed/environment.go b/managed/environment.go index e5549e1..be27f34 100644 --- a/managed/environment.go +++ b/managed/environment.go @@ -147,7 +147,9 @@ func (r *EnvironmentService) Archive(ctx context.Context, environmentID string, // `cloud` environment configuration. type CloudConfig struct { - SetupScript string `json:"setup_script"` + // Set Betas to include "oss-bucket-2026-09-23" when buckets remain configured. + Storage BucketStorage `json:"storage"` + SetupScript string `json:"setup_script"` // Network configuration policy. Networking CloudConfigNetworkingUnion `json:"networking" api:"required"` // Package manager configuration. @@ -156,6 +158,7 @@ type CloudConfig struct { Type constant.Cloud `json:"type" default:"cloud"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { + Storage respjson.Field SetupScript respjson.Field Networking respjson.Field Packages respjson.Field @@ -246,7 +249,9 @@ func (r *CloudConfigNetworkingUnion) UnmarshalJSON(data []byte) error { // // The property Type is required. type CloudConfigParams struct { - SetupScript param.Opt[string] `json:"setup_script,omitzero"` + // Set Betas to include "oss-bucket-2026-09-23" when buckets remain configured. + Storage BucketStorageParam `json:"storage,omitzero"` + SetupScript param.Opt[string] `json:"setup_script,omitzero"` // Network configuration policy. Omit on update to preserve the existing value. Networking CloudConfigParamsNetworkingUnion `json:"networking,omitzero"` // Specify packages (and optionally their versions) available in this environment. @@ -395,7 +400,9 @@ func (r *Environment) UnmarshalJSON(data []byte) error { // // Use the methods beginning with 'As' to cast the union to one of its variants. type EnvironmentConfigUnion struct { - SetupScript string `json:"setup_script"` + // Set Betas to include "oss-bucket-2026-09-23" when buckets remain configured. + Storage BucketStorage `json:"storage"` + SetupScript string `json:"setup_script"` // This field is from variant [CloudConfig]. Networking CloudConfigNetworkingUnion `json:"networking"` // This field is from variant [CloudConfig]. @@ -403,6 +410,7 @@ type EnvironmentConfigUnion struct { // Any of "cloud", "self_hosted". Type string `json:"type"` JSON struct { + Storage respjson.Field SetupScript respjson.Field Networking respjson.Field Packages respjson.Field diff --git a/managed/memorystore.go b/managed/memorystore.go index e3f9575..680a4e4 100644 --- a/managed/memorystore.go +++ b/managed/memorystore.go @@ -292,7 +292,9 @@ func (r *MemoryStoreUpdateParams) UnmarshalJSON(data []byte) error { } type MemoryStoreListParams struct { - Name param.Opt[string] `query:"name,omitzero" json:"-"` + AfterID param.Opt[string] `query:"after_id,omitzero" json:"-"` + BeforeID param.Opt[string] `query:"before_id,omitzero" json:"-"` + Name param.Opt[string] `query:"name,omitzero" json:"-"` // Return only stores whose `created_at` is at or after this time (inclusive). Sent // on the wire as `created_at[gte]`. diff --git a/managed/oauth.go b/managed/oauth.go new file mode 100644 index 0000000..b0d5e40 --- /dev/null +++ b/managed/oauth.go @@ -0,0 +1,60 @@ +package managed + +import ( + "context" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention" + "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" + "net/http" + "slices" +) + +type OAuthService struct{ Options []option.RequestOption } + +func NewOAuthService(opts ...option.RequestOption) OAuthService { + return OAuthService{Options: slices.Clone(opts)} +} + +// Start returns an authorization URL; the user completes authorization in their browser. +func (r *OAuthService) Start(ctx context.Context, params OAuthStartParams, opts ...option.RequestOption) (res *OAuthStartResponse, err error) { + if params.WorkspaceID.Valid() { + opts = append([]option.RequestOption{option.WithHeader("qoder-workspace-id", params.WorkspaceID.Value)}, opts...) + } + opts = slices.Concat(r.Options, opts) + err = convention.ExecuteNewRequest(ctx, http.MethodPost, "oauth/start", params, &res, opts...) + return res, err +} + +type OAuthStartParams struct { + WorkspaceID param.Opt[string] `header:"qoder-workspace-id,omitzero" json:"-"` + + VaultID string `json:"vault_id"` + MCPServerURL string `json:"mcp_server_url"` + ClientID param.Opt[string] `json:"client_id,omitzero"` + ClientSecret param.Opt[string] `json:"client_secret,omitzero"` + paramObj +} + +func (r OAuthStartParams) MarshalJSON() ([]byte, error) { + type shadow OAuthStartParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *OAuthStartParams) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } + +type OAuthStartResponse struct { + AuthorizationURL string `json:"authorization_url"` + State string `json:"state"` + CallbackOrigin string `json:"callback_origin"` + JSON struct { + AuthorizationURL respjson.Field + State respjson.Field + CallbackOrigin respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +func (r OAuthStartResponse) RawJSON() string { return r.JSON.raw } +func (r *OAuthStartResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } diff --git a/managed/official_contract_test.go b/managed/official_contract_test.go new file mode 100644 index 0000000..94e302e --- /dev/null +++ b/managed/official_contract_test.go @@ -0,0 +1,111 @@ +package managed_test + +import ( + "context" + "encoding/json" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/managed" + "io" + "net/http" + "reflect" + "testing" +) + +func TestOfficialDreamModelContract(t *testing.T) { + body := jsonObject(t, managed.DreamNewParams{Inputs: []managed.DreamInputUnionParam{}}) + if _, ok := body["model"]; ok { + t.Fatal("default model should be omitted", body) + } + cfg := managed.DreamModelConfigParam{ID: "auto", Effort: managed.String("none"), ContextWindow: managed.Int(400000)} + body = jsonObject(t, cfg) + if body["effort"] != "none" || body["context_window"] != float64(400000) { + t.Fatal(body) + } + for _, raw := range []string{`{"model":{"id":"auto","effort":"none","context_window":400000}}`} { + var dream managed.Dream + if err := json.Unmarshal([]byte(raw), &dream); err != nil { + t.Fatal(err) + } + if dream.Model.ID != "auto" || dream.Model.Effort != "none" || dream.Model.ContextWindow != 400000 { + t.Fatalf("lost model: %+v", dream.Model) + } + } +} + +func TestOfficialOAuthAPIContract(t *testing.T) { + c := contractClient(t, "OAuthService", "Start") + result, err := c.OAuth.Start(context.Background(), managed.OAuthStartParams{VaultID: "vault", MCPServerURL: "https://mcp.example.com"}) + if err != nil { + t.Fatal(err) + } + checkFields(t, reflect.ValueOf(*result), "oauth") +} +func TestOfficialOAuthSecretAndMemoryCursors(t *testing.T) { + calls := 0 + c := managed.NewClient(option.WithPAT("test"), option.WithBaseURL("https://qoder.test/api/v1/cloud"), option.WithMaxRetries(0), option.WithHTTPClient(&http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { + calls++ + if calls == 1 { + if r.URL.Path != "/api/v1/cloud/oauth/start" || r.Method != "POST" || r.URL.RawQuery != "" || r.Header.Get("qoder-workspace-id") != "workspace" { + t.Fatal(r.URL) + } + b, _ := io.ReadAll(r.Body) + var body map[string]any + _ = json.Unmarshal(b, &body) + if !reflect.DeepEqual(body, map[string]any{"vault_id": "vault", "mcp_server_url": "https://mcp.example.com", "client_id": "client", "client_secret": "test-secret"}) { + t.Fatal(body) + } + return reply(r, 200, `{"authorization_url":"https://auth.example.com","state":"opaque","callback_origin":"https://api.example.com"}`), nil + } + if r.URL.Path != "/api/v1/cloud/memory_stores" || r.URL.Query().Get("before_id") != "mem_before" || r.URL.Query().Get("include_archived") != "false" { + t.Fatal(r.URL) + } + return reply(r, 200, `{"data":[],"has_more":false}`), nil + })})) + result, err := c.OAuth.Start(context.Background(), managed.OAuthStartParams{VaultID: "vault", MCPServerURL: "https://mcp.example.com", ClientID: managed.String("client"), ClientSecret: managed.String("test-secret"), WorkspaceID: managed.String("workspace")}) + if err != nil || result.State != "opaque" || !result.JSON.AuthorizationURL.Valid() { + t.Fatalf("%+v %v", result, err) + } + if _, err = c.MemoryStores.List(context.Background(), managed.MemoryStoreListParams{BeforeID: managed.String("mem_before"), IncludeArchived: managed.Bool(false)}); err != nil { + t.Fatal(err) + } + if calls != 2 { + t.Fatal(calls) + } +} +func TestOfficialOSSStorageWriteReadAndClear(t *testing.T) { + for _, bucket := range []managed.OSSBucketParam{ + {Name: "assets", Provider: "aliyun-oss", Region: "cn-hangzhou", Bucket: "example", Credential: managed.OSSBucketCredentialParam{Type: "ram_role", RoleArn: managed.String("test-role")}}, + {Name: "assets", Provider: "aliyun-oss", Region: "cn-hangzhou", Bucket: "example", Credential: managed.OSSBucketCredentialParam{Type: "access_key", AccessKeyID: managed.String("test-ak"), AccessKeySecret: managed.String("test-secret")}}, + {Name: "assets", Provider: "aliyun-oss", Region: "cn-hangzhou", Bucket: "example"}, + } { + body := jsonObject(t, managed.CloudConfigParams{Storage: managed.BucketStorageParam{Buckets: []managed.OSSBucketParam{bucket}}}) + stored := body["storage"].(map[string]any)["buckets"].([]any)[0].(map[string]any) + if bucket.Credential.Type == "" { + if _, ok := stored["credential"]; ok { + t.Fatal("must preserve stored credentials by omission", stored) + } + } else { + cred := stored["credential"].(map[string]any) + if cred["type"] != bucket.Credential.Type { + t.Fatal(cred) + } + if bucket.Credential.Type == "access_key" && cred["access_key_secret"] != "test-secret" { + t.Fatal(cred) + } + } + } + body := jsonObject(t, managed.CloudConfigParams{Storage: managed.BucketStorageParam{Buckets: []managed.OSSBucketParam{}}}) + if len(body["storage"].(map[string]any)["buckets"].([]any)) != 0 { + t.Fatal(body) + } + var env managed.Environment + if err := json.Unmarshal([]byte(`{"config":{"type":"cloud","storage":{"buckets":[{"name":"assets","provider":"aliyun-oss","region":"cn-hangzhou","bucket":"example","credential":{"type":"access_key","access_key_id":"test****"}}]}}}`), &env); err != nil { + t.Fatal(err) + } + if !env.Config.JSON.Storage.Valid() || env.Config.Storage.Buckets[0].Credential.AccessKeyID != "test****" { + t.Fatalf("%+v", env.Config) + } + if _, ok := reflect.TypeOf(managed.OSSBucketCredential{}).FieldByName("AccessKeySecret"); ok { + t.Fatal("read view must not expose secret") + } +} diff --git a/managed/qoder_types_test.go b/managed/qoder_types_test.go index 1bc9df1..515ac8d 100644 --- a/managed/qoder_types_test.go +++ b/managed/qoder_types_test.go @@ -72,6 +72,11 @@ func TestQoderFieldAlignment(t *testing.T) { if (name == "ManagedAgentsBudgetLimit" || name == "ManagedAgentsBudgetLimitParam") && f == "MaxListCost" { f = "MaxCreditCost" } + // QCA rejects speed for Agent/Dream model configuration. + // https://docs.qoder.com/cloud-agents/api/dreams/create#model-object + if f == "Speed" && (name == "ManagedAgentsModelConfig" || name == "ManagedAgentsModelConfigParams" || name == "DreamModelConfig" || name == "DreamModelConfigParam") { + f = "Effort" + } if !actual[name][f] { t.Errorf("upstream field missing: %s.%s", name, f) } diff --git a/managed/test_helpers_test.go b/managed/test_helpers_test.go index 86739c1..39df09b 100644 --- a/managed/test_helpers_test.go +++ b/managed/test_helpers_test.go @@ -51,7 +51,7 @@ func services(v reflect.Value, into map[string]reflect.Value) { } // Contract fixtures come from official documentation, independently of the adapted services. -// The inventory also detects extra HTTP methods outside the authorized 106 endpoints. +// The inventory also detects extra HTTP methods outside the authorized 107 endpoints. func checkFields(t *testing.T, v reflect.Value, path string) { t.Helper() diff --git a/managed/testdata/api-contracts.json b/managed/testdata/api-contracts.json index 6da29a7..87fc408 100644 --- a/managed/testdata/api-contracts.json +++ b/managed/testdata/api-contracts.json @@ -3156,5 +3156,19 @@ "next_page": null, "total": null } + }, + { + "service": "OAuthService", + "name": "Start", + "method": "POST", + "path": "/oauth/start", + "file": "managed/oauth.go", + "doc": "oauth/start.md", + "route": "/oauth/start", + "response": { + "authorization_url": "https://mcp.example.com/authorize?state=opaque", + "state": "opaque", + "callback_origin": "https://console.example.com" + } } ] From e465318d23d7f171968a3b5ef6e251795d01f2c6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:02:23 +0800 Subject: [PATCH 2/2] feat(sessions): identify SDK-created sessions without overwriting metadata --- CHANGELOG.md | 3 + README.md | 26 ++++ convention/session_metadata.go | 62 +++++++++ convention/session_metadata_test.go | 206 ++++++++++++++++++++++++++++ docs/api/reference.md | 14 +- forward/session.go | 4 +- forward/test_helpers_test.go | 17 ++- managed/request_contracts_test.go | 11 ++ managed/session.go | 4 +- 9 files changed, 339 insertions(+), 8 deletions(-) create mode 100644 convention/session_metadata.go create mode 100644 convention/session_metadata_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index ac87012..bc95e9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ existing `0.1.0` release; earlier development prereleases are not listed. ### Added +- Automatically mark direct Forward and Managed Session creation with `__qca_sdk_created`, `__qca_sdk_language`, and the actual `__qca_sdk_version`, preserving caller metadata. - Add Forward Dreams (create, list, retrieve, cancel, archive), channel pairing list/retrieve/update, and Managed OAuth start with typed requests and responses. - Add Identity automatic memory-store control, mount access and cursor pagination, sparse managed-capability overrides, and full tools-array replacement. - Support string Template references and Advisor model entries in Forward Multiagent rosters, Schedule file resources, and optional Run input snapshots. @@ -18,6 +19,7 @@ existing `0.1.0` release; earlier development prereleases are not listed. ### Breaking changes +- Session creation rejects conflicting values for the three reserved SDK metadata keys before sending; matching string values remain accepted. Missing or null metadata now contains the markers, which count toward API metadata limits. - Forward event `rubric` is a text/content or file/file_id object instead of a string. - Managed Agent `effort` is a level string instead of an object; Agent and Dream model configuration no longer declares the unsupported `speed` field. - Identity mount `List` now takes `IdentityMemoryStoreListParams` and returns `pagination.Page[MemoryStoreMount]`; supply an empty params struct to preserve an unfiltered call. @@ -25,6 +27,7 @@ existing `0.1.0` release; earlier development prereleases are not listed. ### Migration +- Rename business fields that collide with the three `__qca_sdk_` keys and leave space for automatic markers. See “SDK-created Session metadata” in the README. - See the README's “Official contract alignment” section for updated model/rubric types, pagination calls and explicit null clearing. ## [0.12.0] diff --git a/README.md b/README.md index 7073f84..293d917 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,32 @@ This SDK is pre-1.0, so pin a version explicitly and keep the version your build - Go **1.23** or later. - A PAT or Service Account credential for the target environment, with access to the resources you call. Managed SA access also needs a workspace binding. +## SDK-created Session metadata + +Direct Forward and Managed Session creation automatically adds these string-valued +metadata fields: + +| Field | Value | +| --- | --- | +| `__qca_sdk_created` | `"true"` | +| `__qca_sdk_language` | `"go"` | +| `__qca_sdk_version` | The installed SDK's version | + +Reserve the `__qca_sdk_` prefix for SDK provenance. Business metadata is preserved, +and the caller's metadata object is not mutated. Omitted or null metadata becomes +an object containing the markers. Passing an existing marker with the exact same +string value is accepted; a different value or type (including null) fails locally +before any HTTP request. This also applies after request body/JSON options are merged. + +Session updates, reads and event operations do not inject or repair these fields. +Sessions created indirectly through Schedules, Batches or Realtime are outside this +behavior. These markers describe the creating SDK; they are client-supplied metadata, +not an authentication or authorization mechanism. + +Migration: rename any business fields that already use these three keys before +upgrading. Automatic markers count toward API metadata limits; the SDK never drops +business fields to make room. No additional parameters or opt-in are required. + ## Usage The complete program below reads a PAT from the environment, connects to the CN environment and lists the models enabled for the account. diff --git a/convention/session_metadata.go b/convention/session_metadata.go new file mode 100644 index 0000000..ac49101 --- /dev/null +++ b/convention/session_metadata.go @@ -0,0 +1,62 @@ +package convention + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" +) + +// ExecuteNewSessionRequest adds SDK provenance to a Session creation request +// after request options have been applied. Conflicting reserved metadata fails +// before transport; caller-owned params and metadata are never modified. +func ExecuteNewSessionRequest(ctx context.Context, method string, u string, body any, dst any, opts ...RequestOption) error { + cfg, err := NewRequestConfig(ctx, method, u, body, dst, opts...) + if err != nil { + return err + } + if cfg.Body == nil { + return fmt.Errorf("session creation body must be a JSON object") + } + if closer, ok := cfg.Body.(io.Closer); ok { + defer closer.Close() + } + data, err := io.ReadAll(cfg.Body) + if err != nil { + return err + } + var payload map[string]json.RawMessage + if err := json.Unmarshal(data, &payload); err != nil || payload == nil { + return fmt.Errorf("session creation body must be a JSON object") + } + metadata := map[string]json.RawMessage{} + if raw, ok := payload["metadata"]; ok && !bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { + if err := json.Unmarshal(raw, &metadata); err != nil { + return fmt.Errorf("session creation metadata must be a JSON object or null") + } + } + for _, field := range []struct{ key, value string }{ + {"__qca_sdk_created", "true"}, + {"__qca_sdk_language", "go"}, + {"__qca_sdk_version", packageVersion}, + } { + if raw, ok := metadata[field.key]; ok { + var value string + if err := json.Unmarshal(raw, &value); err != nil || value != field.value { + return fmt.Errorf("conflicting SDK metadata field: %s", field.key) + } + } + metadata[field.key], _ = json.Marshal(field.value) + } + payload["metadata"], err = json.Marshal(metadata) + if err != nil { + return err + } + data, err = json.Marshal(payload) + if err != nil { + return err + } + cfg.Body = bytes.NewBuffer(data) + return cfg.Execute() +} diff --git a/convention/session_metadata_test.go b/convention/session_metadata_test.go new file mode 100644 index 0000000..b5dd1b1 --- /dev/null +++ b/convention/session_metadata_test.go @@ -0,0 +1,206 @@ +package convention_test + +import ( + "context" + "encoding/json" + "io" + "maps" + "net/http" + "reflect" + "strings" + "testing" + + "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/managed" +) + +func createMarkedSession(mode string, opts []option.RequestOption, forwardMetadata map[string]any, managedMetadata map[string]string) error { + if mode == "forward" { + c := forward.NewClient(opts...) + _, err := c.Sessions.New(context.Background(), forward.SessionNewParams{IdentityID: "idn", TemplateID: "tmpl", Metadata: forwardMetadata}) + return err + } + c := managed.NewClient(opts...) + _, err := c.Sessions.New(context.Background(), managed.SessionNewParams{Metadata: managedMetadata}) + return err +} + +func TestSessionCreationMetadata(t *testing.T) { + for _, mode := range []string{"forward", "managed"} { + for _, populated := range []bool{false, true} { + t.Run(mode+map[bool]string{false: "/omitted", true: "/business"}[populated], func(t *testing.T) { + var f map[string]any + var m map[string]string + if populated { + f = map[string]any{"ticket": "A-123", "nested": map[string]any{"count": int64(9007199254740993)}} + m = map[string]string{"ticket": "A-123"} + } + beforeF, beforeM := maps.Clone(f), maps.Clone(m) + calls := 0 + var sentMetadata map[string]string + opts := append(requestOptions(func(r *http.Request) (*http.Response, error) { + calls++ + if r.Method != "POST" || !strings.HasSuffix(r.URL.Path, "/sessions") { + t.Fatalf("unexpected request: %s %s", r.Method, r.URL) + } + var body struct { + Metadata map[string]json.RawMessage `json:"metadata"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Fatal(err) + } + want := map[string]string{"__qca_sdk_created": "true", "__qca_sdk_language": "go", "__qca_sdk_version": strings.TrimPrefix(r.Header.Get("User-Agent"), "qca-go/")} + for key, expected := range want { + var got string + if err := json.Unmarshal(body.Metadata[key], &got); err != nil || got != expected || got == "" { + t.Fatalf("%s: %s, want %q", key, body.Metadata[key], expected) + } + } + if populated && string(body.Metadata["ticket"]) != `"A-123"` { + t.Fatal(body.Metadata) + } + if populated && mode == "forward" && string(body.Metadata["nested"]) != `{"count":9007199254740993}` { + t.Fatal("business metadata changed", body.Metadata) + } + sentMetadata = want + return &http.Response{StatusCode: 200, Header: http.Header{"Content-Type": {"application/json"}}, Body: io.NopCloser(strings.NewReader(`{"id":"sess_test"}`))}, nil + }), option.WithPAT("test"), option.WithHeader("X-Qoder-Package-Version", "custom-header")) + for range 2 { + if err := createMarkedSession(mode, opts, f, m); err != nil { + t.Fatal(err) + } + } + if !reflect.DeepEqual(f, beforeF) || !reflect.DeepEqual(m, beforeM) { + t.Fatal("caller metadata mutated") + } + // Reusing exactly matching markers is accepted. + extra := append(opts, option.WithJSONSet("metadata", sentMetadata)) + if populated { + extra = append(extra, option.WithJSONSet("metadata.ticket", "A-123")) + if mode == "forward" { + extra = append(extra, option.WithJSONSet("metadata.nested", f["nested"])) + } + } + if err := createMarkedSession(mode, extra, nil, nil); err != nil { + t.Fatal(err) + } + if calls != 3 { + t.Fatal(calls) + } + }) + } + } +} + +func TestSessionMetadataConflictsBeforeTransport(t *testing.T) { + for _, mode := range []string{"forward", "managed"} { + for _, key := range []string{"__qca_sdk_created", "__qca_sdk_language", "__qca_sdk_version"} { + for _, value := range []any{"user-value", nil, true, 12} { + t.Run(mode+"/"+key+"/"+string(mustJSON(t, value)), func(t *testing.T) { + calls := 0 + opts := append(requestOptions(func(r *http.Request) (*http.Response, error) { + calls++ + t.Fatal("conflict reached transport") + return nil, nil + }), option.WithPAT("test")) + metadata := map[string]any{key: value} + for _, override := range []option.RequestOption{ + option.WithJSONSet("metadata", metadata), + option.WithRequestBody("application/json", mustJSON(t, map[string]any{"metadata": metadata})), + } { + err := createMarkedSession(mode, append(opts, override), nil, nil) + if err == nil || err.Error() != "conflicting SDK metadata field: "+key { + t.Fatalf("wrong error: %v", err) + } + } + if mode == "forward" { + if err := createMarkedSession(mode, opts, metadata, nil); err == nil { + t.Fatal("typed conflict accepted") + } + } else if s, ok := value.(string); ok { + if err := createMarkedSession(mode, opts, nil, map[string]string{key: s}); err == nil { + t.Fatal("typed conflict accepted") + } + } + if calls != 0 || !reflect.DeepEqual(metadata, map[string]any{key: value}) { + t.Fatal("unexpected mutation or request") + } + }) + } + } + } +} + +func mustJSON(t *testing.T, value any) []byte { + t.Helper() + b, err := json.Marshal(value) + if err != nil { + t.Fatal(err) + } + return b +} + +func TestSessionMetadataRawBody(t *testing.T) { + for _, mode := range []string{"forward", "managed"} { + for _, raw := range []string{`{}`, `{"metadata":null}`, `{"metadata":{}}`, `{"metadata":{"__qca_sdk_future":"preserved"}}`} { + calls := 0 + opts := append(requestOptions(func(r *http.Request) (*http.Response, error) { + calls++ + var body map[string]map[string]string + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Fatal(err) + } + if body["metadata"]["__qca_sdk_created"] != "true" { + t.Fatal(body) + } + if strings.Contains(raw, "future") && body["metadata"]["__qca_sdk_future"] != "preserved" { + t.Fatal(body) + } + return &http.Response{StatusCode: 200, Header: http.Header{"Content-Type": {"application/json"}}, Body: io.NopCloser(strings.NewReader(`{}`))}, nil + }), option.WithPAT("test")) + reader := &trackedBody{Reader: strings.NewReader(raw)} + if err := createMarkedSession(mode, append(opts, option.WithRequestBody("application/json", reader)), nil, nil); err != nil { + t.Fatal(err) + } + if calls != 1 || reader.closes != 1 { + t.Fatalf("calls %d, closes %d", calls, reader.closes) + } + } + for _, raw := range []string{`null`, `[]`, `invalid`, `{"metadata":[]}`, `{"metadata":false}`, `{"metadata":"invalid"}`} { + opts := append(requestOptions(func(r *http.Request) (*http.Response, error) { + t.Fatal("invalid metadata reached transport") + return nil, nil + }), option.WithPAT("test"), option.WithRequestBody("application/json", strings.NewReader(raw))) + if err := createMarkedSession(mode, opts, nil, nil); err == nil { + t.Fatal("invalid body accepted:", raw) + } + } + } +} + +func TestSessionUpdateDoesNotStampMetadata(t *testing.T) { + for _, mode := range []string{"forward", "managed"} { + opts := append(requestOptions(func(r *http.Request) (*http.Response, error) { + var body map[string]any + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(body, map[string]any{"metadata": map[string]any{"__qca_sdk_language": "historic"}}) { + t.Fatal(body) + } + return &http.Response{StatusCode: 200, Header: http.Header{"Content-Type": {"application/json"}}, Body: io.NopCloser(strings.NewReader(`{}`))}, nil + }), option.WithPAT("test")) + if mode == "forward" { + c := forward.NewClient(opts...) + if _, err := c.Sessions.Update(context.Background(), "sess_test", forward.SessionUpdateParams{Metadata: map[string]any{"__qca_sdk_language": "historic"}}); err != nil { + t.Fatal(err) + } + } else { + c := managed.NewClient(opts...) + if _, err := c.Sessions.Update(context.Background(), "sess_test", managed.SessionUpdateParams{Metadata: map[string]any{"__qca_sdk_language": "historic"}}); err != nil { + t.Fatal(err) + } + } + } +} diff --git a/docs/api/reference.md b/docs/api/reference.md index a3732bd..4944ff0 100644 --- a/docs/api/reference.md +++ b/docs/api/reference.md @@ -11565,7 +11565,7 @@ func (r *SessionService) ListAutoPaging(ctx context.Context, params SessionListP func (r *SessionService) New(ctx context.Context, params SessionNewParams, opts ...option.RequestOption) (res *Session, err error) ``` -Create Session +Create Session. Adds \_\_qca\_sdk\_ metadata; conflicting reserved values fail before sending. ### func \(\*SessionService\) [Update]() @@ -56872,7 +56872,7 @@ List Sessions func (r *SessionService) New(ctx context.Context, params SessionNewParams, opts ...option.RequestOption) (res *ManagedAgentsSession, err error) ``` -Create Session +Create Session. Adds \_\_qca\_sdk\_ metadata; conflicting reserved values fail before sending. ### func \(\*SessionService\) [Search]() @@ -60482,6 +60482,7 @@ Package convention implements Qoder authentication, transport, request options, - [func DownloadFile\(ctx context.Context, path string, opts ...RequestOption\) \(\*http.Response, error\)](<#DownloadFile>) - [func DownloadTo\(ctx context.Context, client \*http.Client, url string, destination io.Writer\) error](<#DownloadTo>) - [func ExecuteNewRequest\(ctx context.Context, method string, u string, body any, dst any, opts ...RequestOption\) error](<#ExecuteNewRequest>) +- [func ExecuteNewSessionRequest\(ctx context.Context, method string, u string, body any, dst any, opts ...RequestOption\) error](<#ExecuteNewSessionRequest>) - [func NewCredentialTransport\(credential Credential, next http.RoundTripper\) http.RoundTripper](<#NewCredentialTransport>) - [type APIResponse](<#APIResponse>) - [func NewAPIResponse\(r \*http.Response\) \*APIResponse](<#NewAPIResponse>) @@ -60579,6 +60580,15 @@ func ExecuteNewRequest(ctx context.Context, method string, u string, body any, d + +## func [ExecuteNewSessionRequest]() + +```go +func ExecuteNewSessionRequest(ctx context.Context, method string, u string, body any, dst any, opts ...RequestOption) error +``` + +ExecuteNewSessionRequest adds SDK provenance to a Session creation request after request options have been applied. Conflicting reserved metadata fails before transport; caller\-owned params and metadata are never modified. + ## func [NewCredentialTransport]() diff --git a/forward/session.go b/forward/session.go index e3313db..b8d42fa 100644 --- a/forward/session.go +++ b/forward/session.go @@ -92,14 +92,14 @@ func (r SessionListParams) URLQuery() (url.Values, error) { return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ArrayFormat: apiquery.ArrayQueryFormatRepeat, NestedFormat: apiquery.NestedQueryFormatBrackets}) } -// Create Session +// Create Session. Adds __qca_sdk_ metadata; conflicting reserved values fail before sending. func (r *SessionService) New(ctx context.Context, params SessionNewParams, opts ...option.RequestOption) (res *Session, err error) { if params.IdempotencyKey.Valid() { opts = append([]option.RequestOption{option.WithHeader("Idempotency-Key", fmt.Sprint(params.IdempotencyKey.Value))}, opts...) } opts = slices.Concat(r.Options, opts) path := "sessions" - err = convention.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...) + err = convention.ExecuteNewSessionRequest(ctx, http.MethodPost, path, params, &res, opts...) return res, err } diff --git a/forward/test_helpers_test.go b/forward/test_helpers_test.go index f566ba6..36c66dd 100644 --- a/forward/test_helpers_test.go +++ b/forward/test_helpers_test.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "maps" "net/http" "net/url" "os" @@ -265,8 +266,20 @@ func contractClient(t *testing.T, id string, status int) forward.Client { if err := json.NewDecoder(r.Body).Decode(&actual); err != nil { t.Error(err) } - if !reflect.DeepEqual(actual, bodyValue(c)) { - t.Errorf("body: %#v want %#v", actual, bodyValue(c)) + want := maps.Clone(bodyValue(c)) + if c.OperationID == "createSession" { + metadata, _ := want["metadata"].(map[string]any) + metadata = maps.Clone(metadata) + if metadata == nil { + metadata = map[string]any{} + } + metadata["__qca_sdk_created"] = "true" + metadata["__qca_sdk_language"] = "go" + metadata["__qca_sdk_version"] = r.Header.Get("X-Qoder-Package-Version") + want["metadata"] = metadata + } + if !reflect.DeepEqual(actual, want) { + t.Errorf("body: %#v want %#v", actual, want) } } else if c.Body != nil && c.Body.Kind == "multipart" { if err := r.ParseMultipartForm(1 << 20); err != nil { diff --git a/managed/request_contracts_test.go b/managed/request_contracts_test.go index 3d35500..0b4a646 100644 --- a/managed/request_contracts_test.go +++ b/managed/request_contracts_test.go @@ -52,6 +52,17 @@ func TestManagedDocumentedRequestBodies(t *testing.T) { if err = json.Unmarshal(actual, &got); err != nil { t.Fatal(err) } + if c.Service == "SessionService" && c.Method == "New" { + body := want.(map[string]any) + metadata, _ := body["metadata"].(map[string]any) + if metadata == nil { + metadata = map[string]any{} + } + metadata["__qca_sdk_created"] = "true" + metadata["__qca_sdk_language"] = "go" + metadata["__qca_sdk_version"] = r.Header.Get("X-Qoder-Package-Version") + body["metadata"] = metadata + } if !reflect.DeepEqual(got, want) { t.Errorf("%s request body\n got: %s\nwant: %s", c.Doc, actual, c.Body) } diff --git a/managed/session.go b/managed/session.go index 8fe0271..3c9df5f 100644 --- a/managed/session.go +++ b/managed/session.go @@ -46,7 +46,7 @@ func NewSessionService(opts ...option.RequestOption) (r SessionService) { return } -// Create Session +// Create Session. Adds __qca_sdk_ metadata; conflicting reserved values fail before sending. func (r *SessionService) New(ctx context.Context, params SessionNewParams, opts ...option.RequestOption) (res *ManagedAgentsSession, err error) { for _, v := range params.Betas { opts = append(opts, option.WithHeaderAdd("x-qoder-beta", fmt.Sprintf("%v", v))) @@ -54,7 +54,7 @@ func (r *SessionService) New(ctx context.Context, params SessionNewParams, opts opts = slices.Concat(r.Options, opts) path := "sessions" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...) + err = requestconfig.ExecuteNewSessionRequest(ctx, http.MethodPost, path, params, &res, opts...) return res, err }