diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 3d2ac0b..10f3091 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.1.0"
+ ".": "0.2.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 57e5ba5..b2181d4 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 64
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/calvinfo-o4h6u5/cerca-a3610b6045d1d701859276e09dff35829bdf50509b762116860d8005831b0297.yml
-openapi_spec_hash: 2a240632b5ef4814881e36247ec3c8dc
-config_hash: 5d687451efd1d2898e81fa3ddb25f8ed
+configured_endpoints: 66
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/calvinfo-o4h6u5/cerca-b1933e28ba1d2a81cae6514fd41ec2ac5289660666174bfa4466d456e1740fb1.yml
+openapi_spec_hash: d1e63b49a56f6c27dc3dac475c34d612
+config_hash: 411e4c3ec8f57219c56018ea49c09614
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9743757..196e94b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 0.2.0 (2026-05-06)
+
+Full Changelog: [v0.1.0...v0.2.0](https://github.com/matrices/cerca-go/compare/v0.1.0...v0.2.0)
+
+### Features
+
+* **api:** api update ([d64f019](https://github.com/matrices/cerca-go/commit/d64f019fa5da77a390e42a75244c1fc96f762865))
+
## 0.1.0 (2026-05-01)
Full Changelog: [v0.0.1...v0.1.0](https://github.com/matrices/cerca-go/compare/v0.0.1...v0.1.0)
diff --git a/README.md b/README.md
index c7bf64a..6ed1e74 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ Or to pin the version:
```sh
-go get -u 'github.com/matrices/cerca-go@v0.1.0'
+go get -u 'github.com/matrices/cerca-go@v0.2.0'
```
diff --git a/api.md b/api.md
index d429f6d..3a8c6c0 100644
--- a/api.md
+++ b/api.md
@@ -196,14 +196,18 @@ Params Types:
Response Types:
+- cercago.ActivityDetail
+- cercago.ActivitySummary
- cercago.CompiledContext
- cercago.ContentBlock
- cercago.Message
+- cercago.MessagePage
- cercago.Status
- cercago.SteerResult
- cercago.SubThreadSummary
- cercago.Thread
- cercago.ThreadSummary
+- cercago.ThreadTurnSummary
- cercago.TokenUsage
- cercago.Turn
@@ -212,9 +216,11 @@ Methods:
- client.Threads.New(ctx context.Context, agentID string, body cercago.ThreadNewParams) (\*cercago.Thread, error)
- client.Threads.Get(ctx context.Context, agentID string, threadID string, query cercago.ThreadGetParams) (\*cercago.Thread, error)
- client.Threads.List(ctx context.Context, agentID string, query cercago.ThreadListParams) (\*pagination.ThreadsCursorPage[cercago.ThreadSummary], error)
+- client.Threads.Activity(ctx context.Context, agentID string, threadID string, query cercago.ThreadActivityParams) (\*cercago.ActivityDetail, error)
- client.Threads.Cancel(ctx context.Context, agentID string, threadID string) (\*cercago.Thread, error)
- client.Threads.Close(ctx context.Context, agentID string, threadID string) (\*cercago.Thread, error)
- client.Threads.Compact(ctx context.Context, agentID string, threadID string) (\*cercago.Thread, error)
+- client.Threads.ListMessages(ctx context.Context, agentID string, threadID string, query cercago.ThreadListMessagesParams) (\*pagination.ThreadMessagesCursorPage[cercago.Message], error)
- client.Threads.StartTurn(ctx context.Context, agentID string, threadID string, body cercago.ThreadStartTurnParams) (\*cercago.Turn, error)
- client.Threads.Steer(ctx context.Context, agentID string, threadID string, body cercago.ThreadSteerParams) (\*cercago.SteerResult, error)
diff --git a/internal/version.go b/internal/version.go
index 02eac73..774c6c4 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -2,4 +2,4 @@
package internal
-const PackageVersion = "0.1.0" // x-release-please-version
+const PackageVersion = "0.2.0" // x-release-please-version
diff --git a/packages/pagination/pagination.go b/packages/pagination/pagination.go
index e8bc7ef..dd0c27c 100644
--- a/packages/pagination/pagination.go
+++ b/packages/pagination/pagination.go
@@ -1219,3 +1219,113 @@ func (r *ThreadsCursorPageAutoPager[T]) Err() error {
func (r *ThreadsCursorPageAutoPager[T]) Index() int {
return r.run
}
+
+type ThreadMessagesCursorPage[T any] struct {
+ Messages []T `json:"messages"`
+ Cursor string `json:"cursor" api:"nullable"`
+ HasMore bool `json:"hasMore"`
+ JSON threadMessagesCursorPageJSON `json:"-"`
+ cfg *requestconfig.RequestConfig
+ res *http.Response
+}
+
+// threadMessagesCursorPageJSON contains the JSON metadata for the struct
+// [ThreadMessagesCursorPage[T]]
+type threadMessagesCursorPageJSON struct {
+ Messages apijson.Field
+ Cursor apijson.Field
+ HasMore apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *ThreadMessagesCursorPage[T]) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r threadMessagesCursorPageJSON) RawJSON() string {
+ return r.raw
+}
+
+// GetNextPage returns the next page as defined by this pagination style. When
+// there is no next page, this function will return a 'nil' for the page value, but
+// will not return an error
+func (r *ThreadMessagesCursorPage[T]) GetNextPage() (res *ThreadMessagesCursorPage[T], err error) {
+ if len(r.Messages) == 0 {
+ return nil, nil
+ }
+
+ if !r.JSON.HasMore.IsMissing() && r.HasMore == false {
+ return nil, nil
+ }
+ next := r.Cursor
+ if len(next) == 0 {
+ return nil, nil
+ }
+ cfg := r.cfg.Clone(r.cfg.Context)
+ err = cfg.Apply(option.WithQuery("cursor", next))
+ if err != nil {
+ return nil, err
+ }
+ var raw *http.Response
+ cfg.ResponseInto = &raw
+ cfg.ResponseBodyInto = &res
+ err = cfg.Execute()
+ if err != nil {
+ return nil, err
+ }
+ res.SetPageConfig(cfg, raw)
+ return res, nil
+}
+
+func (r *ThreadMessagesCursorPage[T]) SetPageConfig(cfg *requestconfig.RequestConfig, res *http.Response) {
+ if r == nil {
+ r = &ThreadMessagesCursorPage[T]{}
+ }
+ r.cfg = cfg
+ r.res = res
+}
+
+type ThreadMessagesCursorPageAutoPager[T any] struct {
+ page *ThreadMessagesCursorPage[T]
+ cur T
+ idx int
+ run int
+ err error
+}
+
+func NewThreadMessagesCursorPageAutoPager[T any](page *ThreadMessagesCursorPage[T], err error) *ThreadMessagesCursorPageAutoPager[T] {
+ return &ThreadMessagesCursorPageAutoPager[T]{
+ page: page,
+ err: err,
+ }
+}
+
+func (r *ThreadMessagesCursorPageAutoPager[T]) Next() bool {
+ if r.page == nil || len(r.page.Messages) == 0 {
+ return false
+ }
+ if r.idx >= len(r.page.Messages) {
+ r.idx = 0
+ r.page, r.err = r.page.GetNextPage()
+ if r.err != nil || r.page == nil || len(r.page.Messages) == 0 {
+ return false
+ }
+ }
+ r.cur = r.page.Messages[r.idx]
+ r.run += 1
+ r.idx += 1
+ return true
+}
+
+func (r *ThreadMessagesCursorPageAutoPager[T]) Current() T {
+ return r.cur
+}
+
+func (r *ThreadMessagesCursorPageAutoPager[T]) Err() error {
+ return r.err
+}
+
+func (r *ThreadMessagesCursorPageAutoPager[T]) Index() int {
+ return r.run
+}
diff --git a/thread.go b/thread.go
index 892e5a7..6bc03f3 100644
--- a/thread.go
+++ b/thread.go
@@ -95,6 +95,23 @@ func (r *ThreadService) ListAutoPaging(ctx context.Context, agentID string, quer
return pagination.NewThreadsCursorPageAutoPager(r.List(ctx, agentID, query, opts...))
}
+// Fetch compact current and recent activity for a thread without returning
+// transcript content or runtime debug state.
+func (r *ThreadService) Activity(ctx context.Context, agentID string, threadID string, query ThreadActivityParams, opts ...option.RequestOption) (res *ActivityDetail, err error) {
+ opts = slices.Concat(r.Options, opts)
+ if agentID == "" {
+ err = errors.New("missing required agentId parameter")
+ return nil, err
+ }
+ if threadID == "" {
+ err = errors.New("missing required threadId parameter")
+ return nil, err
+ }
+ path := fmt.Sprintf("agents/%s/threads/%s/activity", agentID, threadID)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
+ return res, err
+}
+
// Cancel a running or awaiting thread. The underlying runtime treats repeat
// cancellation as an idempotent lifecycle operation when possible.
func (r *ThreadService) Cancel(ctx context.Context, agentID string, threadID string, opts ...option.RequestOption) (res *Thread, err error) {
@@ -146,6 +163,39 @@ func (r *ThreadService) Compact(ctx context.Context, agentID string, threadID st
return res, err
}
+// List a bounded page of transcript messages for a thread, newest first. Use the
+// returned `cursor` to page older messages.
+func (r *ThreadService) ListMessages(ctx context.Context, agentID string, threadID string, query ThreadListMessagesParams, opts ...option.RequestOption) (res *pagination.ThreadMessagesCursorPage[Message], err error) {
+ var raw *http.Response
+ opts = slices.Concat(r.Options, opts)
+ opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...)
+ if agentID == "" {
+ err = errors.New("missing required agentId parameter")
+ return nil, err
+ }
+ if threadID == "" {
+ err = errors.New("missing required threadId parameter")
+ return nil, err
+ }
+ path := fmt.Sprintf("agents/%s/threads/%s/messages", agentID, threadID)
+ cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, query, &res, opts...)
+ if err != nil {
+ return nil, err
+ }
+ err = cfg.Execute()
+ if err != nil {
+ return nil, err
+ }
+ res.SetPageConfig(cfg, raw)
+ return res, nil
+}
+
+// List a bounded page of transcript messages for a thread, newest first. Use the
+// returned `cursor` to page older messages.
+func (r *ThreadService) ListMessagesAutoPaging(ctx context.Context, agentID string, threadID string, query ThreadListMessagesParams, opts ...option.RequestOption) *pagination.ThreadMessagesCursorPageAutoPager[Message] {
+ return pagination.NewThreadMessagesCursorPageAutoPager(r.ListMessages(ctx, agentID, threadID, query, opts...))
+}
+
// Create turn
func (r *ThreadService) StartTurn(ctx context.Context, agentID string, threadID string, body ThreadStartTurnParams, opts ...option.RequestOption) (res *Turn, err error) {
opts = slices.Concat(r.Options, opts)
@@ -179,6 +229,78 @@ func (r *ThreadService) Steer(ctx context.Context, agentID string, threadID stri
return res, err
}
+type ActivityDetail struct {
+ Error string `json:"error" api:"required,nullable"`
+ RecentTurns []ThreadTurnSummary `json:"recentTurns" api:"required"`
+ JSON activityDetailJSON `json:"-"`
+ ActivitySummary
+}
+
+// activityDetailJSON contains the JSON metadata for the struct [ActivityDetail]
+type activityDetailJSON struct {
+ Error apijson.Field
+ RecentTurns apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *ActivityDetail) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r activityDetailJSON) RawJSON() string {
+ return r.raw
+}
+
+type ActivitySummary struct {
+ ID string `json:"id" api:"required"`
+ CompletedAt string `json:"completedAt" api:"required,nullable"`
+ CreatedAt string `json:"createdAt" api:"required"`
+ Goal string `json:"goal" api:"required"`
+ LatestActivity string `json:"latestActivity" api:"required,nullable"`
+ MessageCount float64 `json:"messageCount" api:"required"`
+ Model string `json:"model" api:"required"`
+ NextStep string `json:"nextStep" api:"required,nullable"`
+ ParentThreadID string `json:"parentThreadId" api:"required,nullable"`
+ Result string `json:"result" api:"required,nullable"`
+ ScheduleID string `json:"scheduleId" api:"required,nullable"`
+ // `idle` threads can accept a new turn or be closed. `running` threads have an
+ // active turn. `awaiting` threads are paused on external input such as approvals.
+ // `closed` threads are terminal.
+ Status Status `json:"status" api:"required"`
+ StepCount float64 `json:"stepCount" api:"required"`
+ UpdatedAt string `json:"updatedAt" api:"required"`
+ JSON activitySummaryJSON `json:"-"`
+}
+
+// activitySummaryJSON contains the JSON metadata for the struct [ActivitySummary]
+type activitySummaryJSON struct {
+ ID apijson.Field
+ CompletedAt apijson.Field
+ CreatedAt apijson.Field
+ Goal apijson.Field
+ LatestActivity apijson.Field
+ MessageCount apijson.Field
+ Model apijson.Field
+ NextStep apijson.Field
+ ParentThreadID apijson.Field
+ Result apijson.Field
+ ScheduleID apijson.Field
+ Status apijson.Field
+ StepCount apijson.Field
+ UpdatedAt apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *ActivitySummary) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r activitySummaryJSON) RawJSON() string {
+ return r.raw
+}
+
type CompiledContext struct {
EnabledTools []shared.ToolName `json:"enabledTools" api:"required,nullable"`
SystemPrompt string `json:"systemPrompt" api:"required,nullable"`
@@ -588,6 +710,30 @@ func (r MessageRole) IsKnown() bool {
return false
}
+type MessagePage struct {
+ Cursor string `json:"cursor" api:"required,nullable"`
+ HasMore bool `json:"hasMore" api:"required"`
+ Messages []Message `json:"messages" api:"required"`
+ JSON messagePageJSON `json:"-"`
+}
+
+// messagePageJSON contains the JSON metadata for the struct [MessagePage]
+type messagePageJSON struct {
+ Cursor apijson.Field
+ HasMore apijson.Field
+ Messages apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *MessagePage) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r messagePageJSON) RawJSON() string {
+ return r.raw
+}
+
// `idle` threads can accept a new turn or be closed. `running` threads have an
// active turn. `awaiting` threads are paused on external input such as approvals.
// `closed` threads are terminal.
@@ -723,9 +869,11 @@ type Thread struct {
CreatedAt string `json:"createdAt" api:"required"`
Depth float64 `json:"depth" api:"required"`
Error string `json:"error" api:"required,nullable"`
+ HasMoreMessages bool `json:"hasMoreMessages" api:"required"`
Instructions string `json:"instructions" api:"required,nullable"`
LastTurnStatus ThreadLastTurnStatus `json:"lastTurnStatus" api:"required,nullable"`
Message string `json:"message" api:"required"`
+ MessageCursor float64 `json:"messageCursor" api:"required,nullable"`
Messages []Message `json:"messages" api:"required"`
Model string `json:"model" api:"required"`
ParentThreadID string `json:"parentThreadId" api:"required,nullable"`
@@ -753,9 +901,11 @@ type threadJSON struct {
CreatedAt apijson.Field
Depth apijson.Field
Error apijson.Field
+ HasMoreMessages apijson.Field
Instructions apijson.Field
LastTurnStatus apijson.Field
Message apijson.Field
+ MessageCursor apijson.Field
Messages apijson.Field
Model apijson.Field
ParentThreadID apijson.Field
@@ -879,6 +1029,52 @@ func (r threadSummaryJSON) RawJSON() string {
return r.raw
}
+type ThreadTurnSummary struct {
+ Activity string `json:"activity" api:"required,nullable"`
+ CompletedAt string `json:"completedAt" api:"required"`
+ MessageCount float64 `json:"messageCount" api:"required"`
+ NextStep string `json:"nextStep" api:"required,nullable"`
+ Status ThreadTurnSummaryStatus `json:"status" api:"required"`
+ TurnSeq float64 `json:"turnSeq" api:"required"`
+ JSON threadTurnSummaryJSON `json:"-"`
+}
+
+// threadTurnSummaryJSON contains the JSON metadata for the struct
+// [ThreadTurnSummary]
+type threadTurnSummaryJSON struct {
+ Activity apijson.Field
+ CompletedAt apijson.Field
+ MessageCount apijson.Field
+ NextStep apijson.Field
+ Status apijson.Field
+ TurnSeq apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *ThreadTurnSummary) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r threadTurnSummaryJSON) RawJSON() string {
+ return r.raw
+}
+
+type ThreadTurnSummaryStatus string
+
+const (
+ ThreadTurnSummaryStatusCompleted ThreadTurnSummaryStatus = "completed"
+ ThreadTurnSummaryStatusFailed ThreadTurnSummaryStatus = "failed"
+)
+
+func (r ThreadTurnSummaryStatus) IsKnown() bool {
+ switch r {
+ case ThreadTurnSummaryStatusCompleted, ThreadTurnSummaryStatusFailed:
+ return true
+ }
+ return false
+}
+
type TokenUsage struct {
InputTokens float64 `json:"inputTokens" api:"required"`
OutputTokens float64 `json:"outputTokens" api:"required"`
@@ -978,7 +1174,9 @@ func (r ThreadNewParams) MarshalJSON() (data []byte, err error) {
type ThreadGetParams struct {
// When true, includes debug-only compiled context fields.
Debug param.Field[ThreadGetParamsDebug] `query:"debug"`
- // When true, includes message content in the thread detail.
+ // Deprecated compatibility flag. Thread detail includes a bounded recent message
+ // page by default; pass `false` only to opt out when no message pagination params
+ // are present.
IncludeMessages param.Field[ThreadGetParamsIncludeMessages] `query:"includeMessages"`
}
@@ -1006,7 +1204,9 @@ func (r ThreadGetParamsDebug) IsKnown() bool {
return false
}
-// When true, includes message content in the thread detail.
+// Deprecated compatibility flag. Thread detail includes a bounded recent message
+// page by default; pass `false` only to opt out when no message pagination params
+// are present.
type ThreadGetParamsIncludeMessages string
const (
@@ -1042,6 +1242,37 @@ func (r ThreadListParams) URLQuery() (v url.Values) {
})
}
+type ThreadActivityParams struct {
+ // Optional fleet id for index-backed authorization.
+ FleetID param.Field[string] `query:"fleetId"`
+}
+
+// URLQuery serializes [ThreadActivityParams]'s query parameters as `url.Values`.
+func (r ThreadActivityParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type ThreadListMessagesParams struct {
+ // Cursor returned by a previous thread messages response.
+ Cursor param.Field[string] `query:"cursor"`
+ // Optional fleet id for index-backed authorization.
+ FleetID param.Field[string] `query:"fleetId"`
+ // Maximum number of messages to include, capped at 500.
+ Limit param.Field[string] `query:"limit"`
+}
+
+// URLQuery serializes [ThreadListMessagesParams]'s query parameters as
+// `url.Values`.
+func (r ThreadListMessagesParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
type ThreadStartTurnParams struct {
Message param.Field[string] `json:"message" api:"required"`
Model param.Field[string] `json:"model"`
diff --git a/thread_test.go b/thread_test.go
index 3317281..a985a58 100644
--- a/thread_test.go
+++ b/thread_test.go
@@ -107,6 +107,35 @@ func TestThreadListWithOptionalParams(t *testing.T) {
}
}
+func TestThreadActivityWithOptionalParams(t *testing.T) {
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := cercago.NewClient(
+ option.WithBaseURL(baseURL),
+ option.WithAPIKey("My API Key"),
+ )
+ _, err := client.Threads.Activity(
+ context.TODO(),
+ "agent_abc123",
+ "thread_abc123",
+ cercago.ThreadActivityParams{
+ FleetID: cercago.F("fleetId"),
+ },
+ )
+ if err != nil {
+ var apierr *cercago.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
+
func TestThreadCancel(t *testing.T) {
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
@@ -185,6 +214,37 @@ func TestThreadCompact(t *testing.T) {
}
}
+func TestThreadListMessagesWithOptionalParams(t *testing.T) {
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := cercago.NewClient(
+ option.WithBaseURL(baseURL),
+ option.WithAPIKey("My API Key"),
+ )
+ _, err := client.Threads.ListMessages(
+ context.TODO(),
+ "agent_abc123",
+ "thread_abc123",
+ cercago.ThreadListMessagesParams{
+ Cursor: cercago.F("42"),
+ FleetID: cercago.F("fleetId"),
+ Limit: cercago.F("100"),
+ },
+ )
+ if err != nil {
+ var apierr *cercago.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
+
func TestThreadStartTurnWithOptionalParams(t *testing.T) {
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {