🎉 feat: 建立阶段 0 工程骨架与 OpenAPI 契约机器化(WP-01 起步)
- Makefile:统一命令入口(backend/engine/docker/openapi/generate,含 make help) - backend:FastAPI + uv 最小工程,统一错误体(契约第 7 节)、ADR-015 平台 ID 生成与校验(uuid-utils);pytest 7 项、ruff、mypy strict、启动冒烟全部通过 - engine:灵台引擎 C++23/CMake 骨架,按 Lingtai C ABI 契约实现 lingtai.h(13 个 HjStatus、版本协商、句柄表防二次释放、线程局部错误上下文);doctest 5 用例通过,nm 验证仅导出 hj_* 符号 - openapi:OpenAPI 3.1 单一真源初始版(错误体/分页/Money/首批平台 ID/ContentBlock 全 21 类型/流事件 envelope),Redocly lint 0 错误、bundle 通过 - frontend:Next.js 最小骨架,openapi-typescript 生成 API 类型并通过 strict tsc - docker:hunjing-postgres(pgvector:17)compose;.env.example 与 config 示例 - ADR-025:登记测试与契约工具链(doctest/libFuzzer/Redocly/openapi-typescript/uuid-utils) - desktop:Tauri v2 占位与初始化说明(WP-10 启动)
This commit is contained in:
1 parent
5da1efe921
commit
3fbcd0437c
32 files changed
+11459
No files matched your search
@@ -0,0 +1,401 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: Hunjing API
|
||||
version: 0.1.0
|
||||
summary: Hunjing 平台公共契约(OpenAPI 3.1 单一真源)
|
||||
license:
|
||||
name: Apache-2.0
|
||||
identifier: Apache-2.0
|
||||
description: |
|
||||
本文件由 docs/contracts/ 机器化而来,是公共 HTTP 契约的单一真源;
|
||||
TypeScript/Rust 客户端由本文件生成,不手写重复 DTO。
|
||||
当前版本覆盖阶段 0 所需基础:错误体、分页、Money、平台 ID、
|
||||
统一 ContentBlock(21 类型)与流事件 envelope;资源端点随工作包增量登记。
|
||||
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
|
||||
servers:
|
||||
- url: https://api.example.dev/v1
|
||||
description: Native API(开发占位,正式域名见 docs/contracts/api-conventions.md)
|
||||
paths:
|
||||
/healthz:
|
||||
get:
|
||||
operationId: getHealth
|
||||
tags: [ops]
|
||||
summary: 服务健康探针
|
||||
security: []
|
||||
responses:
|
||||
"200":
|
||||
description: 服务正常
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [status]
|
||||
properties:
|
||||
status: { type: string, enum: [ok] }
|
||||
env: { type: string }
|
||||
default:
|
||||
description: 错误
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/ErrorEnvelope" }
|
||||
components:
|
||||
schemas:
|
||||
# ===== 通用:错误体(api-conventions 第 7 节) =====
|
||||
Error:
|
||||
type: object
|
||||
required: [code, message]
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
description: 稳定机器错误码
|
||||
examples: [validation_failed]
|
||||
message: { type: string, description: 可本地化消息 }
|
||||
trace_id: { type: string, format: uuid }
|
||||
request_id: { type: string }
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
description: 不稳定细节,必须脱敏
|
||||
ErrorEnvelope:
|
||||
type: object
|
||||
required: [error]
|
||||
properties:
|
||||
error: { $ref: "#/components/schemas/Error" }
|
||||
|
||||
# ===== 通用:分页(opaque cursor) =====
|
||||
CursorPage:
|
||||
type: object
|
||||
required: [data, next_cursor, has_more]
|
||||
properties:
|
||||
data: { type: array, items: {} }
|
||||
next_cursor:
|
||||
oneOf: [{ type: string }, { type: "null" }]
|
||||
description: opaque cursor;客户端不得解析
|
||||
has_more: { type: boolean }
|
||||
|
||||
# ===== 通用:金额(最小货币单位整数,禁止浮点) =====
|
||||
Money:
|
||||
type: object
|
||||
required: [amount_minor, currency]
|
||||
properties:
|
||||
amount_minor: { type: integer, minimum: 0 }
|
||||
currency: { type: string, pattern: "^[A-Z]{3}$" }
|
||||
|
||||
# ===== 平台 ID(ADR-015:<prefix><lowercase-canonical-uuid>;首批按垂直切片登记) =====
|
||||
UserId: { type: string, pattern: "^user_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
|
||||
PersonalDomainId: { type: string, pattern: "^domain_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
|
||||
ConversationId: { type: string, pattern: "^conversation_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
|
||||
BranchId: { type: string, pattern: "^branch_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
|
||||
MessageId: { type: string, pattern: "^message_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
|
||||
ContentBlockId: { type: string, pattern: "^block_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
|
||||
RunId: { type: string, pattern: "^run_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
|
||||
TurnId: { type: string, pattern: "^turn_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
|
||||
RequestId: { type: string, pattern: "^request_[0-9a-f]{12}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", description: 服务端生成,UUIDv4 }
|
||||
|
||||
# ===== 统一 ContentBlock(content-blocks-and-streaming 第 2-3 节) =====
|
||||
BlockStatus:
|
||||
type: string
|
||||
enum: [pending, streaming, completed, failed, cancelled]
|
||||
BlockAnnotation:
|
||||
type: object
|
||||
description: 类型化标注(citation/来源/敏感度/语言)
|
||||
additionalProperties: true
|
||||
BlockBase:
|
||||
type: object
|
||||
required: [id]
|
||||
properties:
|
||||
id: { $ref: "#/components/schemas/ContentBlockId" }
|
||||
status: { $ref: "#/components/schemas/BlockStatus" }
|
||||
annotations:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/BlockAnnotation" }
|
||||
metadata:
|
||||
type: object
|
||||
description: 只允许 Schema 登记字段,不存任意秘密
|
||||
additionalProperties: true
|
||||
|
||||
TextBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [text]
|
||||
properties:
|
||||
type: { type: string, enum: [text] }
|
||||
text: { type: string }
|
||||
language: { type: string }
|
||||
MarkdownBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [text]
|
||||
properties:
|
||||
type: { type: string, enum: [markdown] }
|
||||
text: { type: string }
|
||||
flavor: { type: string, enum: [hunjing-gfm-v1] }
|
||||
CodeBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [source]
|
||||
properties:
|
||||
type: { type: string, enum: [code] }
|
||||
source: { type: string }
|
||||
language: { type: string }
|
||||
filename: { type: string }
|
||||
QuoteBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [content]
|
||||
properties:
|
||||
type: { type: string, enum: [quote] }
|
||||
content:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/ContentBlock" }
|
||||
source_ref: { type: string }
|
||||
TableBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [columns, rows]
|
||||
properties:
|
||||
type: { type: string, enum: [table] }
|
||||
columns: { type: array, items: { type: string } }
|
||||
rows: { type: array, items: { type: array, items: {} } }
|
||||
caption: { type: string }
|
||||
CalloutBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [kind, content]
|
||||
properties:
|
||||
type: { type: string, enum: [callout] }
|
||||
kind: { type: string, enum: [info, warning, danger, success] }
|
||||
title: { type: string }
|
||||
content:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/ContentBlock" }
|
||||
MathBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [notation, display, source]
|
||||
properties:
|
||||
type: { type: string, enum: [math] }
|
||||
notation: { type: string, enum: [latex, asciimath] }
|
||||
display: { type: string, enum: [inline, block] }
|
||||
source: { type: string }
|
||||
fallback_text: { type: string, description: 可访问性与解析失败降级描述 }
|
||||
label: { type: string }
|
||||
MermaidBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [source]
|
||||
properties:
|
||||
type: { type: string, enum: [mermaid] }
|
||||
source: { type: string }
|
||||
title: { type: string }
|
||||
caption: { type: string }
|
||||
fallback_code_language: { type: string, default: mermaid }
|
||||
ImageBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [alt]
|
||||
properties:
|
||||
type: { type: string, enum: [image] }
|
||||
file_id: { type: string }
|
||||
url_ref: { type: string, description: "受控 URL;禁止 file://、javascript: 等协议" }
|
||||
alt: { type: string }
|
||||
width: { type: integer, minimum: 1 }
|
||||
height: { type: integer, minimum: 1 }
|
||||
caption: { type: string }
|
||||
VideoBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [file_id]
|
||||
properties:
|
||||
type: { type: string, enum: [video] }
|
||||
file_id: { type: string }
|
||||
poster_file_id: { type: string }
|
||||
caption: { type: string }
|
||||
transcript_file_id: { type: string }
|
||||
duration_ms: { type: integer, minimum: 0 }
|
||||
AudioBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [file_id]
|
||||
properties:
|
||||
type: { type: string, enum: [audio] }
|
||||
file_id: { type: string }
|
||||
transcript: { type: string }
|
||||
duration_ms: { type: integer, minimum: 0 }
|
||||
waveform: { type: string }
|
||||
FileBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [file_id, filename, media_type, size_bytes]
|
||||
properties:
|
||||
type: { type: string, enum: [file] }
|
||||
file_id: { type: string }
|
||||
filename: { type: string }
|
||||
media_type: { type: string }
|
||||
size_bytes: { type: integer, minimum: 0 }
|
||||
description: { type: string }
|
||||
GalleryBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [items]
|
||||
properties:
|
||||
type: { type: string, enum: [gallery] }
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/ImageBlock"
|
||||
- $ref: "#/components/schemas/VideoBlock"
|
||||
layout: { type: string }
|
||||
AvatarBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [asset_id, format]
|
||||
properties:
|
||||
type: { type: string, enum: [avatar] }
|
||||
asset_id: { type: string }
|
||||
format: { type: string, enum: [live2d, vrm, gltf] }
|
||||
preview_file_id: { type: string }
|
||||
ToolCallBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [tool_name, call_id]
|
||||
properties:
|
||||
type: { type: string, enum: [tool_call] }
|
||||
tool_name: { type: string }
|
||||
call_id: { type: string }
|
||||
arguments_redacted: { type: object, description: 参数脱敏视图 }
|
||||
approval_id: { type: string }
|
||||
ToolResultBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [call_id]
|
||||
properties:
|
||||
type: { type: string, enum: [tool_result] }
|
||||
call_id: { type: string }
|
||||
summary: { type: object, additionalProperties: true }
|
||||
file_ids: { type: array, items: { type: string } }
|
||||
is_error: { type: boolean }
|
||||
AgentStatusBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [stage]
|
||||
properties:
|
||||
type: { type: string, enum: [agent_status] }
|
||||
agent_id: { type: string }
|
||||
child_run_id: { type: string }
|
||||
stage: { type: string }
|
||||
progress: { type: object, additionalProperties: true }
|
||||
waiting_reason: { type: string }
|
||||
ApprovalRequestBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [approval_id, action]
|
||||
properties:
|
||||
type: { type: string, enum: [approval_request] }
|
||||
approval_id: { type: string }
|
||||
action: { type: string }
|
||||
impact: { type: string }
|
||||
arguments_summary: { type: object, additionalProperties: true }
|
||||
expires_at: { type: string, format: date-time }
|
||||
ArtifactBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [artifact_id, artifact_type]
|
||||
properties:
|
||||
type: { type: string, enum: [artifact] }
|
||||
artifact_id: { type: string }
|
||||
artifact_type: { type: string }
|
||||
version: { type: integer }
|
||||
CitationBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [source_id, title, quoted_text]
|
||||
properties:
|
||||
type: { type: string, enum: [citation] }
|
||||
source_id: { type: string }
|
||||
title: { type: string }
|
||||
quoted_text: { type: string }
|
||||
locator:
|
||||
type: object
|
||||
description: 定位(文本区间、图像区域、PDF 页区、音视频时间码)与原文快照
|
||||
additionalProperties: true
|
||||
ErrorBlock:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BlockBase"
|
||||
- type: object
|
||||
required: [message]
|
||||
properties:
|
||||
type: { type: string, enum: [error] }
|
||||
message: { type: string }
|
||||
retryable: { type: boolean }
|
||||
fallback: { type: string }
|
||||
|
||||
ContentBlock:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/TextBlock"
|
||||
- $ref: "#/components/schemas/MarkdownBlock"
|
||||
- $ref: "#/components/schemas/CodeBlock"
|
||||
- $ref: "#/components/schemas/QuoteBlock"
|
||||
- $ref: "#/components/schemas/TableBlock"
|
||||
- $ref: "#/components/schemas/CalloutBlock"
|
||||
- $ref: "#/components/schemas/MathBlock"
|
||||
- $ref: "#/components/schemas/MermaidBlock"
|
||||
- $ref: "#/components/schemas/ImageBlock"
|
||||
- $ref: "#/components/schemas/VideoBlock"
|
||||
- $ref: "#/components/schemas/AudioBlock"
|
||||
- $ref: "#/components/schemas/FileBlock"
|
||||
- $ref: "#/components/schemas/GalleryBlock"
|
||||
- $ref: "#/components/schemas/AvatarBlock"
|
||||
- $ref: "#/components/schemas/ToolCallBlock"
|
||||
- $ref: "#/components/schemas/ToolResultBlock"
|
||||
- $ref: "#/components/schemas/AgentStatusBlock"
|
||||
- $ref: "#/components/schemas/ApprovalRequestBlock"
|
||||
- $ref: "#/components/schemas/ArtifactBlock"
|
||||
- $ref: "#/components/schemas/CitationBlock"
|
||||
- $ref: "#/components/schemas/ErrorBlock"
|
||||
discriminator:
|
||||
propertyName: type
|
||||
|
||||
# ===== 流事件 envelope(content-blocks-and-streaming 第 5 节) =====
|
||||
StreamEvent:
|
||||
type: object
|
||||
required: [type, data]
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
[
|
||||
content.block.started,
|
||||
content.block.delta,
|
||||
content.block.progress,
|
||||
content.block.completed,
|
||||
content.block.failed,
|
||||
content.block.cancelled,
|
||||
message.completed,
|
||||
]
|
||||
data: { type: object, additionalProperties: true }
|
||||
sequence:
|
||||
type: integer
|
||||
minimum: 0
|
||||
description: Run 内单调递增
|
||||
Reference in new issue
Block a user