Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/agents-api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Each item is Core's deliberate or native behavior where the official service beh

**Lists** ([lists](./wire-semantics.md#lists))

- A deleted Agent or Session used as a cursor returns 404; the official service still pages from it.
- A deleted Agent used as a cursor returns 404; the official service still pages from it.
- A Turn cursor from another Session, a Credential cursor equal to its Vault ID, and a Skills cursor that is not a Skill ID return 404.
- Vault and Credential lists clamp a negative `limit`, as the pinned SDK describes; the official service returns 400.

Expand Down
2 changes: 1 addition & 1 deletion contracts/agents-api/wire-semantics.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ A `limit` that is not a decimal integer, including an empty value, returns 400 `

### Cursors

`after` names a resource of the same list, inside its already resolved parent and tenant. The parent is resolved first: a missing or foreign parent returns its 404 before the cursor is read. A cursor that does not resolve, whether random, malformed, of another type, of another parent, deleted or of another tenant, returns:
`after` names a resource of the same list, inside its already resolved parent and tenant. The parent is resolved first: a missing or foreign parent returns its 404 before the cursor is read. A cursor that does not resolve, whether random, malformed, of another type, of another parent, deleted or of another tenant, returns the response below. A deleted Session still resolves as a Sessions cursor and the page continues from its position, as on the official service; a deleted Agent does not resolve:

| Lists | Response |
| --- | --- |
Expand Down
4 changes: 2 additions & 2 deletions contracts/agents-api/zh/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "OpenAI 兼容性"
source: contracts/agents-api/index.md
source_hash: 685e26e496e369c1eb2b1b093ec2e26e6fb6fbdf082ec1b877d9c638b0ac17e6
source_hash: d3d542a34c7cf38c798a2ec85373f9d554f83892f7c47b6773783b7dcf230c62
---

Core 实现下方固定版本的 OpenAI Agents API。本台账列出各项资源的覆盖情况、相对于官方服务的所有差异以及已知缺口。
Expand Down Expand Up @@ -64,7 +64,7 @@ Core 自身字段位于 `x_agents_core` 中([Core extensions](../../../docs/zh

**列表**([lists](wire-semantics.md#lists))

- 将已删除的 Agent 或 Session 用作游标时会返回 404;官方服务仍可从该游标继续分页。
- 将已删除的 Agent 用作游标时会返回 404;官方服务仍可从该游标继续分页。
- 使用来自其他 Session 的 Turn 游标、与 Vault ID 相等的 Credential 游标,或不是 Skill ID 的 Skills 游标时,都会返回 404。
- Vault 和 Credential 列表会按照固定版本 SDK 的描述钳制负数 `limit`;官方服务返回 400。

Expand Down
4 changes: 2 additions & 2 deletions contracts/agents-api/zh/wire-semantics.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "API 行为"
source: contracts/agents-api/wire-semantics.md
source_hash: ba9c6565567c1b4dcffb0c8f18731372b9e3c95e25f84dc0c3c2782c94161eb2
source_hash: 1d087e0f29e20f091416cc7bd3293e754c610f2335e2d66456f5205706d9983a
---

已锁定版本的 OpenAI Python SDK([upstream.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream.json))定义了 `/v1` 路由、字段和类型。本页面说明这些类型未作规定之处 Core 的行为,例如状态码、错误字段、默认值和列表边界,以及 Core 与官方服务存在差异的地方。[OpenAI 兼容性](index.md) 列出了这些差异和尚存缺口;[Sessions, events and history](sessions-events.md)、[message content](message-content.md)、[Vaults](vaults.md)、[source Files and Skills](source-files.md) 和 [Environment files and Artifacts](environment-files.md) 分别负责各自资源的规则。
Expand Down Expand Up @@ -128,7 +128,7 @@ Vault 和 Credential 列表接受标量 `status`、`status[]` 条目或两者,

### 游标 {#cursors}

`after` 指定同一列表中的某个资源,并且该资源必须位于已经解析出的父资源和租户内。首先解析父资源:父资源缺失或属于外部租户时,会先返回其 404,然后才读取游标。无法解析的游标,无论是随机值、格式错误、类型不同、父资源不同、已删除还是属于其他租户,均返回:
`after` 指定同一列表中的某个资源,并且该资源必须位于已经解析出的父资源和租户内。首先解析父资源:父资源缺失或属于外部租户时,会先返回其 404,然后才读取游标。无法解析的游标,无论是随机值、格式错误、类型不同、父资源不同、已删除还是属于其他租户,均返回下表中的响应。已删除的 Session 仍可作为 Sessions 游标解析,并从其位置继续分页,与官方服务一致;已删除的 Agent 无法解析:

| 列表 | 响应 |
| --- | --- |
Expand Down
3 changes: 3 additions & 0 deletions services/core/internal/db/queries/sessions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ RETURNING *;
-- name: GetSession :one
SELECT * FROM sessions WHERE tenant_id = $1 AND id = $2 AND deleted_at IS NULL;

-- name: GetSessionCursor :one
SELECT created_at, id FROM sessions WHERE tenant_id = $1 AND id = $2;

-- name: ListSessions :many
SELECT * FROM sessions
WHERE tenant_id = sqlc.arg(tenant_id) AND deleted_at IS NULL
Expand Down
21 changes: 21 additions & 0 deletions services/core/internal/db/sqlc/sessions.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ func (s *Store) ListSessions(ctx context.Context, tenantID, cursor string, limit
err = s.units.Snapshot(ctx, func(ctx context.Context, tx pgx.Tx) error {
q := sqlc.New(tx)
if cursor != "" {
after, err := q.GetSession(ctx, sqlc.GetSessionParams{TenantID: tenant, ID: pgunit.PathID(cursor)})
// A deleted Session still anchors the page position.
after, err := q.GetSessionCursor(ctx, sqlc.GetSessionCursorParams{TenantID: tenant, ID: pgunit.PathID(cursor)})
if err != nil {
return err
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package sessionpg

import (
"errors"
"testing"

"github.com/google/uuid"

"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/persistence/postgres/pgtest"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/persistence/postgres/pgunit"
"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/sessions"
)

func TestListSessionsPagesFromDeletedCursor(t *testing.T) {
pool := pgtest.Open(t)
store := New(pgunit.NewPool(pool), pgtest.CredentialKey(t))
tenant := uuid.New()
var ids []string
for i := range 3 {
id := uuid.New()
exec(t, pool, `INSERT INTO sessions(id, tenant_id, engine, idempotency_key, request_hash, configuration, created_at)
VALUES ($1, $2, 'codex', $3, 'hash', '{}', now() + $4 * interval '1 second')`, id, tenant, id.String(), i)
ids = append(ids, id.String())
}
exec(t, pool, `UPDATE sessions SET deleted_at = now() WHERE id = $1`, ids[1])

page, err := store.ListSessions(t.Context(), tenant.String(), ids[1], 10, false, nil)
if err != nil || len(page.Sessions) != 1 || page.Sessions[0].ID != ids[0] {
t.Fatalf("deleted cursor: %+v %v", page, err)
}
if _, err := store.ListSessions(t.Context(), uuid.NewString(), ids[1], 10, false, nil); !errors.Is(err, sessions.ErrNotFound) {
t.Fatalf("foreign tenant cursor: %v", err)
}
}
Loading