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: 2 additions & 0 deletions contracts/agents-api/core.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,9 @@ definitions:
type:
type: string
required:
- code
- message
- param
- type
type: object
api.CoreErrorResponse:
Expand Down
2 changes: 1 addition & 1 deletion contracts/agents-api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Core targets the complete OpenAI Agents API as pinned below ([public API rule](h
| [openapi.yaml](./openapi.yaml) | The official public contract with Core's `x_agents_core` extension on Agent and Session request/response objects |
| [go-bindings.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/go-bindings.json) | Go names, field representations, encoding order and stored projections; it does not define official field membership, enums or constraints |

Run `make openapi` to regenerate the public Go types, the Agent request shapes, the route inventory and all three OpenAPI documents. `scripts/generate-public-api.py` reads the checked-in, checksum-verified official source without network access. It selects Agents, Vaults, Files and Skills and follows their schema references, preserving union types, nullability, required fields and constraints. The request shapes in `services/core/internal/api/official_shapes.gen.go` project `CreateAgentParams`, `UpdateAgentParams` and `SessionAgentConfigParam`; Core checks request bodies against them before it reads an Agent configuration. Core's extension types in `v1/` remain authored in Go and are added to the public schema during generation. The TypeScript client's types, enum values and field names in `packages/agents-client/src/generated/public-api.ts` are generated from the resulting public schema. The internal `/core/v1` and `/api/v1` documents come from handler annotations; the same generator projects the `/core/v1` document into `packages/agents-client/src/generated/core-api.ts`, which imports the public types it references from `public-api.ts`. `make check-openapi` checks freshness and the generator; it also runs through `make check-go`.
Run `make openapi` to regenerate the public Go types, the Agent request shapes, the route inventory and all three OpenAPI documents. `scripts/generate-public-api.py` reads the checked-in, checksum-verified official source without network access. It selects Agents, Vaults, Files and Skills and follows their schema references, preserving union types, nullability, required fields and constraints. The request shapes in `services/core/internal/api/official_shapes.gen.go` project `CreateAgentParams`, `UpdateAgentParams` and `SessionAgentConfigParam`; Core checks request bodies against them before it reads an Agent configuration. Core's extension types in `v1/` remain authored in Go and are added to the public schema during generation. The TypeScript client's types, enum values and field names in `packages/agents-client/src/generated/public-api.ts` are generated from the resulting public schema. The internal `/core/v1` and `/api/v1` documents come from handler annotations; the same generator projects the `/core/v1` document into `packages/agents-client/src/generated/core-api.ts`, which imports the public types it references from `public-api.ts`. In those annotations a response field Core always sends carries `binding:"required"`, a field that can be null carries `extensions:"x-nullable"`, and a closed set carries `enums:`; the tags only shape the documents, and the client rejects a response that breaks them. `make check-openapi` checks freshness and the generator; it also runs through `make check-go`.

The public contract is the official API plus Core extensions. Standard fields are generated into `v1/official.gen.go`; `go-bindings.json` lists types consumed by Core and overrides only the Go representation or field order that existing storage or custom JSON encoding requires. Unspecified fields follow the official schema; shared shapes use one Go type. Selected discriminated unions also generate JSON serializers to retain required nullable fields for each variant. Other union serializers, Core's local limits, execution admission and state transitions remain implementation code. Contract tests verify that the public schema preserves the official definitions, extensions remain in `x_agents_core`, and all documents match registered routes. Official-client and raw HTTP tests verify behavior. Schema generation does not qualify an unimplemented feature; the gaps below still apply. Upstream upgrades update the OpenAPI and SDK pins together after comparison and compatibility tests.

Expand Down
2 changes: 2 additions & 0 deletions contracts/agents-api/runtime.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ definitions:
type:
type: string
required:
- code
- message
- param
- type
type: object
api.CoreErrorResponse:
Expand Down
5 changes: 4 additions & 1 deletion contracts/agents-api/v1/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ func itemEvent(eventType string) bool {

// MarshalJSON keeps the nullable top-level usage on terminal Turn events only,
// a nullable output_index on every Item event (EVT-09), a nullable error param
// on error events and a nullable turn_id on Environment events.
// on error events and a nullable turn_id on Environment events. Subagent events
// carry no session_id; their Subagent names the Session.
func (e SessionEvent) MarshalJSON() ([]byte, error) {
type wire SessionEvent
switch {
Expand Down Expand Up @@ -52,6 +53,8 @@ func (e SessionEvent) MarshalJSON() ([]byte, error) {
wire
TurnID *string `json:"turn_id"`
}{wire(e), turn})
case strings.HasPrefix(e.Type, "agent.session.subagent."):
e.SessionID = ""
}
e.Usage = nil
return json.Marshal(wire(e))
Expand Down
6 changes: 4 additions & 2 deletions contracts/agents-api/v1/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ func TestSessionEventUsageOnlyOnTerminalTurnEvents(t *testing.T) {

// Error events carry the pinned SessionError, whose param is present and null
// when unset; Environment state errors keep their observed three fields (HI-01/02)
// and Environment events carry a null turn_id.
func TestSessionErrorEventCarriesNullableParam(t *testing.T) {
// and Environment events carry a null turn_id. Subagent events carry no session_id.
func TestSessionEventWireFields(t *testing.T) {
failure := &StreamError{Type: "environment_error", Code: "sandbox_error", Message: "Failed to provision environment"}
param := "input"
for _, test := range []struct {
Expand All @@ -60,6 +60,8 @@ func TestSessionErrorEventCarriesNullableParam(t *testing.T) {
{SessionEvent{Type: "agent.session.environment.failed", EventID: "event", Environment: &SessionEnvironmentState{ID: "environment", Type: "openai_hosted", Status: "failed",
Error: &StreamError{Type: "environment_error", Code: "environment_connection_failed", Message: "The environment failed to connect."}}},
`{"type":"agent.session.environment.failed","event_id":"event","environment":{"id":"environment","type":"openai_hosted","status":"failed","error":{"code":"environment_connection_failed","type":"environment_error","message":"The environment failed to connect."}},"turn_id":null}`},
{SessionEvent{Type: "agent.session.subagent.closed", EventID: "event", SessionID: "session", Subagent: &Subagent{ID: "subagent", Object: "agent.session.subagent", SessionID: "session", ParentAgentID: "root", Status: "closed"}},
`{"subagent":{"id":"subagent","object":"agent.session.subagent","session_id":"session","parent_agent_id":"root","opened_at":0,"closed_at":null,"name":null,"instructions":null,"status":"closed"},"type":"agent.session.subagent.closed","event_id":"event"}`},
} {
raw, err := json.Marshal(test.event)
if err != nil || string(raw) != test.want {
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: "Agents API 覆盖台账"
source: contracts/agents-api/index.md
source_hash: b2281fe0f7ab7c4f810b029aca847a11a3a292d367dcacd1ac3f716f511e2814
source_hash: dc1388f616997b89e298113b7626fac569a9650f9a52710500f96a86c2b1af80
---

Core 旨在以下方固定版本为准支持完整的 OpenAI Agents API([public API rule](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/AGENTS.md#public-api))。本台账记录 Core 对各项资源实现了哪些内容、哪些契约保存其详细信息,并列出相对于 OpenAI 服务的所有已知差异和所有未解决缺口。[API namespaces and credentials](../../../docs/zh/api/index.md) 说明谁调用哪些 API;[Agents API guide](../../../docs/zh/api/public-agent-api.md) 介绍使用方法。
Expand All @@ -16,7 +16,7 @@ Core 旨在以下方固定版本为准支持完整的 OpenAI Agents API([publi
| [openapi.yaml](../openapi.yaml) | 官方公共契约,并在 Agent 和 Session 请求及响应对象上加入 Core 的 `x_agents_core` 扩展 |
| [go-bindings.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/go-bindings.json) | Go 名称、字段表示、编码顺序和存储投影;不定义官方字段集合、枚举或约束 |

运行 `make openapi` 重新生成公共 Go 类型、Agent 请求结构、路由清单和三个 OpenAPI 文档。`scripts/generate-public-api.py` 读取仓库内经过校验和验证的官方源文件,无需网络。它选择 Agents、Vaults、Files 和 Skills,并跟随 schema 引用,保留联合类型、可空性、必填字段和约束。`services/core/internal/api/official_shapes.gen.go` 中的请求结构投影 `CreateAgentParams`、`UpdateAgentParams` 和 `SessionAgentConfigParam`;Core 在读取 Agent 配置前先按它们检查请求体。Core 在 `v1/` 中的扩展类型继续由 Go 定义,在生成时加入公共 schema。TypeScript 客户端在 `packages/agents-client/src/generated/public-api.ts` 中的类型、枚举值和字段名由生成后的公共 schema 生成。内部 `/core/v1` 和 `/api/v1` 文档由处理函数注解生成;同一生成器把 `/core/v1` 文档投影为 `packages/agents-client/src/generated/core-api.ts`,其中引用的公共类型从 `public-api.ts` 导入。`make check-openapi` 检查生成结果是否最新并测试生成器;`make check-go` 也会运行此检查。
运行 `make openapi` 重新生成公共 Go 类型、Agent 请求结构、路由清单和三个 OpenAPI 文档。`scripts/generate-public-api.py` 读取仓库内经过校验和验证的官方源文件,无需网络。它选择 Agents、Vaults、Files 和 Skills,并跟随 schema 引用,保留联合类型、可空性、必填字段和约束。`services/core/internal/api/official_shapes.gen.go` 中的请求结构投影 `CreateAgentParams`、`UpdateAgentParams` 和 `SessionAgentConfigParam`;Core 在读取 Agent 配置前先按它们检查请求体。Core 在 `v1/` 中的扩展类型继续由 Go 定义,在生成时加入公共 schema。TypeScript 客户端在 `packages/agents-client/src/generated/public-api.ts` 中的类型、枚举值和字段名由生成后的公共 schema 生成。内部 `/core/v1` 和 `/api/v1` 文档由处理函数注解生成;同一生成器把 `/core/v1` 文档投影为 `packages/agents-client/src/generated/core-api.ts`,其中引用的公共类型从 `public-api.ts` 导入。在这些注解里,Core 总会发送的响应字段带 `binding:"required"`,可以为 null 的字段带 `extensions:"x-nullable"`,封闭集合带 `enums:`;这些标签只影响文档,客户端会拒绝不符合它们的响应。`make check-openapi` 检查生成结果是否最新并测试生成器;`make check-go` 也会运行此检查。

公共契约是官方 API 加上 Core 扩展。标准字段生成到 `v1/official.gen.go`;`go-bindings.json` 只列出 Core 使用的类型,仅在已有存储或自定义 JSON 编码需要时覆盖 Go 表示或字段顺序。未覆盖的字段遵循官方 schema,相同结构复用同一个 Go 类型。部分带判别字段的联合类型也从 schema 生成 JSON 序列化代码,保留每个分支必需的可空字段。其他联合类型序列化、Core 的本地限制、执行准入和状态转换仍由实现代码负责。契约测试验证公共 schema 保留官方定义、扩展位于 `x_agents_core` 中,且所有文档与注册路由一致。官方客户端和原始 HTTP 测试验证行为。生成 schema 不代表某个尚未实现的功能已经得到验证;下方缺口仍然适用。升级上游时,在比对和兼容性测试后一起更新 OpenAPI 和 SDK 固定版本。

Expand Down
6 changes: 3 additions & 3 deletions packages/agents-client/src/generated/core-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,14 @@ export interface ConfigurationDiscoveryInput {
export const configurationDiscoveryInputFields = ["configuration", "credential", "query"] as const;
export const configurationDiscoveryInputRequired = [] as const;
export interface CoreAPIError {
code?: string | null;
code: string | null;
details?: Record<string, unknown>;
message: string;
param?: string | null;
param: string | null;
type: string;
}
export const coreAPIErrorFields = ["code", "details", "message", "param", "type"] as const;
export const coreAPIErrorRequired = ["message", "type"] as const;
export const coreAPIErrorRequired = ["code", "message", "param", "type"] as const;
export interface CoreErrorResponse {
error: CoreAPIError;
}
Expand Down
7 changes: 6 additions & 1 deletion scripts/generate-public-api.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,15 +406,20 @@ def core_module(document, public, bindings):
taken = [ts_name(name) for name in local] + list(imported)
for name in local:
names[name] = ts_name(name) if taken.count(ts_name(name)) == 1 else name.split('.')[0].capitalize() + ts_name(name)
schemas = {}
schemas, hoisted = {}, []
for name in local:
schema = schemas[names[name]] = json.loads(refs.sub(lambda ref: f'"#/definitions/{names[ref[1]]}"', json.dumps(definitions[name])))
for field, member in schema.get('properties', {}).items():
target = member.get('items', member)
if len(target.get('enum', [])) > 1:
enum = names[name] + ''.join(part.capitalize() for part in field.split('_'))
hoisted.append(enum)
schemas[enum] = {'type': target.pop('type'), 'enum': target.pop('enum')}
target['$ref'] = '#/definitions/' + enum
# A prefixed or hoisted name must not replace another type.
emitted = [*imported, *(names[name] for name in local), *hoisted]
if len(emitted) != len(set(emitted)):
raise ValueError('TypeScript names collide')
used = sorted(imported.intersection(refs.findall(json.dumps(schemas))))
header = ['import type { ' + ', '.join(used) + ' } from "./public-api";'] if used else []
return ts_module(schemas, 'contracts/agents-api/core.openapi.yaml', header)
Expand Down
11 changes: 11 additions & 0 deletions scripts/generate-public-api.test.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,17 @@ def test_core_types_import_public_names_and_hoist_inline_enums(self):
self.assertIn(line, generated)
self.assertNotIn('Unused', generated)

def test_core_names_never_replace_another_type(self):
ref = '#/definitions/'
for definitions in (
{'api.Reset': {'type': 'object', 'properties': {'clear': {'type': 'string', 'enum': ['auto', 'force']}}}, 'api.ResetClear': {'type': 'object'}},
{'one.Node': {'type': 'object'}, 'two.Node': {'type': 'object'}, 'api.OneNode': {'type': 'object'}},
):
document = {'paths': {'/x': {'get': {'responses': {'200': {'schema': {'type': 'object', 'properties': {
name: {'$ref': ref + name} for name in definitions}}}}}}}, 'definitions': definitions}
with self.subTest(sorted(definitions)), self.assertRaisesRegex(ValueError, 'collide'):
generator.core_module(document, {}, {})

def test_every_public_reference_resolves_and_no_other_apis_leak(self):
self.assertEqual({p.split('/')[1] for p in self.public['paths']}, {'agents', 'vaults', 'files', 'skills'})
generator.prune_components(copy.deepcopy(self.public))
Expand Down
4 changes: 2 additions & 2 deletions services/core/internal/api/core_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ type CoreErrorResponse struct {
type CoreAPIError struct {
Message string `json:"message" binding:"required"`
Type string `json:"type" binding:"required"`
Code *string `json:"code" extensions:"x-nullable"`
Param *string `json:"param" extensions:"x-nullable"`
Code *string `json:"code" binding:"required" extensions:"x-nullable"`
Param *string `json:"param" binding:"required" extensions:"x-nullable"`
// Details contains only documented, Core-owned facts: string, finite number,
// boolean, null or string array values. Never include request echoes, secrets
// or native/provider error text. Empty or invalid details are omitted.
Expand Down
Loading