Skip to main content
SDK

Go SDK

安装和使用 Qoder Cloud Agents Go SDK,了解鉴权、请求类型、流式响应、分页、重试和超时配置。

Qoder Cloud Agents Go SDK 提供类型化的 Forward 和 Managed 客户端,支持通过 context 控制请求、逐条读取流式响应,以及配置认证、重试和 HTTP 传输。

安装

需要 Go 1.23 或更高版本。在已有 Go Modules 项目中安装:
go get github.com/QoderAI/qoder-cloud-agents-sdk-go
将 go.mod 和 go.sum 纳入版本控制,固定应用使用的 SDK 版本。

基本用法

将个人访问令牌(PAT)保存到环境变量:
export QODER_PAT="替换为你的 PAT"
使用 Forward 客户端创建模板和身份,发起会话并发送消息:
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/QoderAI/qoder-cloud-agents-sdk-go/forward"
)

func main() {
	ctx := context.Background()
	client := forward.NewClient()

	env, err := client.Environments.New(ctx, forward.EnvironmentNewParams{
		Name:   "demo",
		Config: map[string]any{"type": "cloud"},
	})
	if err != nil {
		log.Fatal(err)
	}

	identity, err := client.Identities.New(ctx, forward.IdentityNewParams{
		ExternalID: "demo-user",
		Name:       forward.String("Demo User"),
	})
	if err != nil {
		log.Fatal(err)
	}

	template, err := client.Templates.New(ctx, forward.TemplateNewParams{
		Name:          "assistant",
		Model:         forward.ModelConfigUnionParam{OfString: forward.String("auto")},
		EnvironmentID: env.ID,
	})
	if err != nil {
		log.Fatal(err)
	}

	session, err := client.Sessions.New(ctx, forward.SessionNewParams{
		IdentityID: identity.ID,
		TemplateID: template.ID,
	})
	if err != nil {
		log.Fatal(err)
	}

	message := forward.SessionEventParam{
		Type: "user.message",
		Content: forward.EventContentUnionParam{
			OfBlocks: []forward.ContentBlockParam{
				{Type: "text", Text: forward.String("你好")},
			},
		},
	}
	_, err = client.Sessions.Events.Send(ctx, session.ID, forward.SessionEventSendParams{
		Events: []forward.SessionEventParam{message},
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(session.ID)
}
使用 Managed 客户端创建 Agent 和环境,发起会话并发送消息。将以下代码保存为 main.go,在已安装 SDK 的项目中执行 go run main.go:
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/QoderAI/qoder-cloud-agents-sdk-go/managed"
)

func main() {
	ctx := context.Background()
	client := managed.NewClient()

	agent, err := client.Agents.New(ctx, managed.AgentNewParams{
		Name:  "assistant",
		Model: managed.ManagedAgentsModelConfigParams{ID: "auto"},
	})
	if err != nil {
		log.Fatal(err)
	}

	env, err := client.Environments.New(ctx, managed.EnvironmentNewParams{
		Name: "demo",
		Config: managed.EnvironmentNewParamsConfigUnion{
			OfCloud: &managed.CloudConfigParams{Type: "cloud"},
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	session, err := client.Sessions.New(ctx, managed.SessionNewParams{
		Agent:         managed.SessionNewParamsAgentUnion{OfString: managed.String(agent.ID)},
		EnvironmentID: env.ID,
	})
	if err != nil {
		log.Fatal(err)
	}

	message := managed.ManagedAgentsEventParamsUnion{
		OfUserMessage: &managed.ManagedAgentsUserMessageEventParams{
			Type: "user.message",
			Content: []managed.ManagedAgentsUserMessageEventParamsContentUnion{
				{OfText: &managed.ManagedAgentsTextBlockParam{Type: "text", Text: "你好"}},
			},
		},
	}
	_, err = client.Sessions.Events.Send(ctx, session.ID, managed.SessionEventSendParams{
		Events: []managed.ManagedAgentsEventParamsUnion{message},
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(session.ID)
}
后续 Go 片段省略 package 和 import。除标准库外,使用到的 convention、option、param 分别位于模块下的 convention、convention/option、convention/param 包。示例中的 client 为已配置的 Forward 客户端;资源 ID 需要替换为当前凭证可访问的真实 ID。 更多接入说明见 Managed 快速入门和 Forward API 参考。

客户端配置

通过 option 包向构造函数或单次调用传入配置。同一配置通常由单次调用覆盖客户端默认值;追加请求头和中间件等可累积选项按各自规则组合。
配置ForwardManaged
构造函数forward.NewClientmanaged.NewClient
Base URL 环境变量QODER_FORWARD_BASE_URLQODER_BASE_URL
Global 默认地址https://api.qoder.com/api/v1/forwardhttps://api.qoder.com/api/v1/cloud
CN 地址https://api.qoder.com.cn/api/v1/forwardhttps://api.qoder.com.cn/api/v1/cloud
显式传入的配置优先于环境变量。默认 PAT 模式先读取 QODER_ACCESS_TOKEN,再读取 QODER_PAT。客户端不会自动加载 .env 文件。 Base URL 必须包含完整 API 前缀,并与账号、凭证及资源所在区域一致。在多个 goroutine 中复用客户端前,先完成配置;共享后不要并发修改它的 Options。

鉴权

PAT

使用 option.WithPAT(token) 显式配置个人访问令牌。令牌应保存在应用的服务端环境中。

Service Account

设置 QODER_SA_KEY 后,显式创建凭证组件。组件负责置换、缓存 SAT(服务账号令牌),并在后续请求需要时更新令牌:
func serviceAccountClients() (forward.Client, managed.Client, error) {
	credential, err := convention.ServiceAccountCredentialFromEnv(
		convention.ServiceAccountCredentialConfig{
			Region: "global",
			Scopes: []string{"qca.access", "forward.access"},
		},
	)
	if err != nil {
		return forward.Client{}, managed.Client{}, err
	}
	return forward.NewClient(
		option.WithCredential(credential),
		option.WithBaseURL("https://api.qoder.com/api/v1/forward"),
	), managed.NewClient(
		option.WithCredential(credential),
		option.WithBaseURL("https://api.qoder.com/api/v1/cloud"),
	), nil
}
两个客户端共享同一凭证实例及其令牌缓存。中国站需要同时使用 Region: "cn" 和 CN Base URL。仅访问 Managed 时可以申请 qca.access scope;权限及 Managed 工作空间绑定需在服务端配置。 普通客户端不会因为环境中存在 QODER_SA_KEY 就自动启用凭证置换。SA Key 只用于换取 SAT,不能直接作为业务接口的访问令牌。SA 鉴权失败也不会回退到 PAT。 如果应用自行管理 SAT,使用 option.WithAuthMode("sa") 和 option.WithAuthToken(token);静态 SAT 的更新由应用负责。同一客户端选择一个明确的凭证来源,避免将原生 SA 凭证与静态令牌组合使用。更多说明见认证文档。

请求与响应类型

可选字段

必填字段通常直接使用 Go 值,可选标量使用 param.Opt[T]。字段未设置与显式发送零值的含义不同:
写法JSON 中的行为
未设置的 param.Opt[T]不发送该字段
forward.String("")发送空字符串
forward.Bool(false)发送 false
forward.Int(0)发送 0
param.Null[string]()发送 null
带 omitzero 的 nil map 或 slice不发送该字段
非 nil 的空 map 或 slice发送 {} 或 []
下面的函数只演示参数编码,不会发起请求:
func encodeParameters() ([]byte, error) {
	params := forward.SessionUpdateParams{
		Title: param.Null[string](),
	}
	return json.Marshal(params)
}
字段是否接受 null,以及服务端如何处理该值,以对应接口定义为准。Managed 提供同样的 managed.String、Bool、Int 等辅助函数。

联合类型和响应字段

请求联合类型通过 Of... 字段选择一种表示,只设置一个分支。响应联合类型使用该类型提供的 As... 或 AsAny() 方法读取具体分支,具体方法见 API Reference。 响应结构中的 JSON 元数据可用于区分缺失字段、显式 null 和正常值;不要仅凭 Go 零值判断字段是否存在。需要原始 JSON 时使用响应模型的 RawJSON()。尚未建模的字段保留在响应的额外字段信息中。

流式响应

使用 StreamEvents 建立单次 SSE 连接,按顺序读取事件,并在结束后检查 Err():
func readEvents(ctx context.Context, client forward.Client, sessionID string) error {
	stream := client.Sessions.Events.StreamEvents(
		ctx, sessionID, forward.SessionEventStreamParams{},
	)
	defer stream.Close()

	for stream.Next() {
		event := stream.Current()
		fmt.Println(event.Type)
	}
	return stream.Err()
}
单次流在 EOF 时结束;如果希望在可恢复的断连后继续订阅,显式使用 NewResumableStream:
func readResumableEvents(
	ctx context.Context,
	client forward.Client,
	sessionID string,
	cursor string,
) error {
	params := forward.SessionEventStreamParams{}
	if cursor != "" {
		params.LastEventID = forward.String(cursor)
	}
	stream := client.Sessions.Events.NewResumableStream(ctx, sessionID, params)
	defer stream.Close()

	for stream.Next() {
		event := stream.Current()
		fmt.Println(event.Type)
		// 应用成功处理事件后,再持久化该恢复位置。
		fmt.Println("cursor:", stream.LastEventID())
	}
	return stream.Err()
}
恢复流沿用最近交付事件的 SSE 游标重新连接,不重新发送业务请求。它可能持续运行,应通过 context 设置订阅期限或主动取消。HTTP 重试次数控制单次连接请求,不能当作整个订阅的重连次数上限。 没有游标时从当前事件流尾部开始;需要补读历史时,应先获取有效的历史恢复位置。自动重连不提供“恰好一次”处理保证;同一事件的多个增量可能共用 ID,不应仅按 ID 丢弃所有重复帧。具体事件的含义与任务完成条件见对应 API 定义。 关闭流或取消本地 context 只停止当前订阅,不会自动停止云端已经开始的执行。

错误处理与 Request ID

HTTP 错误使用 *convention.Error。网络、编码、解码及上下文错误需要分别处理:
func retrieveWithErrors(ctx context.Context, client forward.Client, sessionID string) error {
	_, err := client.Sessions.Get(ctx, sessionID)
	if err == nil {
		return nil
	}
	var apiErr *convention.Error
	switch {
	case errors.Is(err, context.Canceled):
		fmt.Println("请求已取消")
	case errors.Is(err, context.DeadlineExceeded):
		fmt.Println("请求超过期限")
	case errors.As(err, &apiErr):
		fmt.Printf("status=%d code=%s request_id=%s\n",
			apiErr.StatusCode, apiErr.Code, apiErr.RequestID)
	default:
		fmt.Printf("请求失败:%v\n", err)
	}
	return err
}
错误还提供 Message、Type()、Request、Response 和 RawJSON()。排查问题时保留状态码、错误码和 Request ID;避免将含访问令牌的完整请求直接写入日志。

重试与幂等性

默认最多重试 2 次,即最多尝试 3 次。重试要求请求体可重复读取,且调用方的 context 仍有效。
请求默认可重试情况
GET、HEAD,或携带幂等键的请求连接错误、408、429、5xx
未携带幂等键的其他请求429
409 响应不自动重试
在这些安全规则内,SDK 还会处理服务端的重试提示和等待时间;部分接口有更严格的重试限制。使用 option.WithMaxRetries(0) 关闭重试,或在单次调用中覆盖次数。 幂等键由应用生成和保存,可通过请求参数中的 IdempotencyKey 或 option.WithHeader("Idempotency-Key", key) 传入。同一次逻辑操作的所有重试复用同一个键;新的操作使用新的键。

超时与取消

默认 Transport 等待响应头的超时为 10 分钟。这个限制不覆盖响应体读取,也不是整个调用的总期限。 通过 context 控制整个调用,通过 WithRequestTimeout 控制单次尝试:
func retrieveWithTimeout(parent context.Context, client forward.Client, sessionID string) error {
	ctx, cancel := context.WithTimeout(parent, time.Minute)
	defer cancel()
	_, err := client.Sessions.Get(ctx, sessionID,
		option.WithRequestTimeout(15*time.Second),
		option.WithMaxRetries(1),
	)
	return err
}
上例最多等待 1 分钟,每次尝试最多 15 秒,先到达的期限生效。WithRequestTimeout 也覆盖响应体读取,因此长时间订阅 SSE 时应配置适合订阅的期限。

分页

自动分页按需读取后续页面:
func listAll(ctx context.Context, client forward.Client) error {
	pager := client.Sessions.ListAutoPaging(ctx, forward.SessionListParams{
		Limit: forward.Int(20),
	})
	for pager.Next() {
		fmt.Println(pager.Current().ID)
	}
	return pager.Err()
}
如果需要逐页处理,使用 List 和 GetNextPage()。没有下一页时返回 nil:
func listPages(ctx context.Context, client forward.Client) error {
	page, err := client.Sessions.List(ctx, forward.SessionListParams{Limit: forward.Int(20)})
	for err == nil && page != nil {
		fmt.Println(len(page.Data))
		page, err = page.GetNextPage()
	}
	return err
}
分页器保留查询条件和请求配置。不同接口的游标形式可能不同,优先使用返回页面提供的翻页方法。

高级配置

原始响应

使用 WithResponseInto 读取状态码和响应头:
func inspectResponse(ctx context.Context, client forward.Client, sessionID string) error {
	var response *http.Response
	_, err := client.Sessions.Get(ctx, sessionID, option.WithResponseInto(&response))
	if response != nil {
		fmt.Println(response.StatusCode, response.Header.Get("X-Request-ID"))
	}
	return err
}
这里的响应体可能已经被 SDK 读取。需要响应 JSON 时使用解析结果的 RawJSON();需要自行指定解码目标时使用 WithResponseBodyInto。

HTTP 客户端和请求扩展

WithHTTPClient 可设置连接池、代理和自定义 Transport。传入自定义客户端后,应用负责该客户端的超时与连接池配置。
func customHTTPClient(token string) forward.Client {
	transport := http.DefaultTransport.(*http.Transport).Clone()
	transport.ResponseHeaderTimeout = time.Minute
	transport.MaxIdleConnsPerHost = 20
	return forward.NewClient(
		option.WithPAT(token),
		option.WithHTTPClient(&http.Client{Transport: transport}),
	)
}
需要观测每次请求尝试时使用 WithMiddleware。自定义 Header、Query 和 JSON 字段可使用 WithHeader、WithQuery、WithJSONSet;参数结构的 SetExtraFields 用于传入服务端已支持、当前 SDK 尚未建模的字段。只覆盖应用明确需要调整的字段,避免意外替换认证或路由配置。