Viewer REST API
后端地址 http://localhost:8090。除静态文件端点外,响应统一为 {code, message, data},code=0 表示成功。模型 id 格式是 m_ 加 16 位小写 hex。
完整的机器可读 schema 见 go-server.openapi.json,由脚本生成并做路由覆盖检测,详见文末机器可消费 OpenAPI一节。
模型
POST /api/v1/models
上传 IFC 文件并触发异步转换。请求是 multipart/form-data,字段 file,仅接受 .ifc,上限 200MB。
用户界面的上传入口已于 2026-08-21 隐藏,模型由 agent 在项目会话内生成。本端点保留为 agent 建模型的内部链路,用户不需要直接调它。
响应:
{"code":0,"message":"ok","data":{"id":"m_01J...","name":"Building-Architecture.ifc","status":"converting"}}错误:40001 文件类型非法,40002 超出大小上限。
GET /api/v1/models
模型列表,前端每 2 秒轮询,直到所有模型脱离 converting:
{"code":0,"message":"ok","data":[
{"id":"m_01J...","name":"a.ifc","size":1832140,"status":"ready","createdAt":"2026-07-27T10:00:00Z","error":""}
]}status 取值 converting、ready、failed。
GET /api/v1/models/{id}
单模型详情,结构同上。
POST /api/v1/models/{id}/retry
对 failed 模型重新入队转换,返回更新后的模型对象。
DELETE /api/v1/models/{id}
删除该模型的 IFC、XKT、metadata、状态文件及 issues、changes、overrides。
模型归属于项目时,删除会自动把它从项目里摘掉,project.json 同步更新,不会留下孤立的 modelId。
GET /api/v1/models/{id}/download
下载原始 IFC,响应带 Content-Disposition: attachment。
Issues
issue id 格式是 i_ 加 12 位小写 hex。status 取值 open、checking、resolved。作者默认 local-user,来源默认 UI,创建时可覆盖。
GET /api/v1/models/{id}/issues
返回 data: Issue[],按创建时间降序。
POST /api/v1/models/{id}/issues
multipart/form-data 两个部分:
issue(必填):JSON 字符串,含entityId、entityName、entityType、title(必填)、comment、可选的author、provenance和相机参数camera。screenshot(可选):PNG,上限 5MB。
返回创建后的 data: Issue,含生成的 id、初始状态和截图相对路径。
PATCH /api/v1/models/{id}/issues/{issueId}
JSON body 传 title、comment、status 中要更新的字段。
DELETE /api/v1/models/{id}/issues/{issueId}
删除 Issue 及其截图。
GET /v1/models/{id}/issues/
Issue 截图静态服务,file 必须匹配 i_[0-9a-f]{12}\.png。
属性 Override 与修改记录
属性修改走 metadata override,不改 IFC 本体。白名单字段只有 Name、Description、Classification、FireRating、Comments,每次修改逐字段写一条 change log。
GET /api/v1/models/{id}/overrides
返回 data: { [entityId]: { [field]: value } },无数据时是 {}。
PUT /api/v1/models/{id}/entities/{entityId}/properties
JSON body:
{"entityName":"Wall","fields":{"FireRating":"F60","Comments":"备注"}}fields 必填且非空,字段名不在白名单返回 40001。空字符串表示清除该字段的 override。每个字段写一条 change log,返回该实体当前生效的 override 集合。
GET /api/v1/models/{id}/changes
返回 data: ChangeEntry[],按创建时间降序:
{"code":0,"message":"ok","data":[
{"id":"c_1a2b3c4d5e6f","entityId":"3a82-xxxx","entityName":"Wall","field":"FireRating","oldValue":"","newValue":"F60","author":"local-user","provenance":{"source":"UI"},"operation":"update","createdAt":"2026-07-29T10:00:00Z"}
]}Chat:项目、会话与 AI 对话
进程内 chat agent 的 REST 与 SSE 接口。项目是入口,一个项目绑定唯一会话;前端的历史项目列表就是会话列表。
项目
POST /api/v1/chat/projects
创建项目。body 是 {"title", "kind"},kind 必填,取值 ifc、cad、cad->ifc,它决定 agent 的派发方向。
模型初始化按 kind 分化:ifc 建项目时就生成骨架模型,跑一遍最小脚本产出 v1;cad 和 cad->ifc 先留空,agent 会话内用 init_model 按需建。
项目创建和删除在
/api/v1/chat/projects,方案读写在/api/v1/projects/{id}/...。同一个项目 id 出现在两个前缀下是历史原因。
{"code":0,"message":"ok","data":{"projectId":"p_xxxx","title":"我的项目","kind":"cad","createdAt":"2026-08-21T06:00:00Z","models":[]}}错误:40001 kind 缺失或非法。
会话
GET /api/v1/chat/sessions
会话列表:
{"code":0,"message":"ok","data":[
{"chatSessionId":"c_xxxx","opencodeSessionId":"s_xxxx","modelId":"","projectId":"p_xxxx","title":"我的项目","createdAt":"2026-08-21T06:00:00Z"}
]}opencodeSessionId 是历史遗留字段名,契约如此。
DELETE /api/v1/chat/projects/{id}
删除项目并级联清理:会话、事件日志、方案文件、项目下所有模型及其附属数据。注意和单模型删除的区别——删项目就是删全套。
POST /api/v1/chat/sessions
创建或复用会话。传 {"title", "projectId"} 是项目级语义,projectId 幂等,一个项目只有一个会话;传 {"title", "modelId"} 是旧的单模型语义。错误:40001 项目不存在。
POST /api/v1/chat/sessions/{cid}/messages
发消息,异步处理。body 是 {"text"},响应 {"accepted":true},事件经 SSE 推送。
GET /api/v1/chat/sessions/{cid}/messages
会话历史,前端按消息 id 去重合并。
GET /api/v1/chat/sessions/{cid}/events
SSE 事件流。帧类型:
| event | data | 说明 |
|---|---|---|
session.status | busy 或 idle | 一轮对话的边界 |
message.updated | 消息骨架 | |
message.part.updated | part 定型 | 文本、推理、工具卡片 |
message.part.delta | 流式增量 | |
subagent.status | subagentId、persona、status、task | 子 agent 边界 |
question.ask | interruptId、question | agent 向用户提问 |
viewer.staged | modelId、kind | 试运行成功的中途预览 |
viewer.committed | 保存成功 | 驱动前端刷新 |
model.created | modelId、kind、title、projectId | agent 创建了新模型 |
session.error | error | 错误 |
session.idle | 空 | 本轮结束 |
支持 Last-Event-ID 断线重同步,缓冲最近 64 条。
POST /api/v1/chat/sessions/{cid}/answer
回答 agent 的提问,body 是 {"interruptId", "answer"},回答后 agent 继续执行。
POST /api/v1/chat/sessions/{cid}/abort
中止当前执行。
项目级方案产物
GET / PUT /api/v1/projects/{projectID}/{name}
方案文件读写。name 取值 plan、bim_supplement、building。PUT 全量替换并写入版本历史。
三个方案文件走 PlanStore 版本化。skill 的中间产物如 design.json 不版本化,落在 skill 工作区,随项目删除清理。
GET /api/v1/projects/{projectID}/plan_history
方案版本历史。
GET /api/v1/projects/{projectID}/plan_history/{base}/{target}/diff
两个方案版本的 diff,base 和 target 可以是 v{n} 或 current。
POST /api/v1/projects/{projectID}/deliver
方案交付,body 是 {"plan", "bimSupplement"}。
编辑代理端点
Go server 把编辑服务的脚本端点暴露在 /api/v1/models/{id}/script/...,run、save、rollback 成功后自动排队重转 XKT。只读与对比端点在 /api/v1/models/{id}/edit/... 前缀下。完整契约见 IFC 编辑 API。
静态资源
以下路径直接返回文件,不走 JSON envelope:
| 路径 | 说明 |
|---|---|
GET /v1/models/{id}/model.xkt | XKT 几何数据,支持 Range |
GET /v1/models/{id}/metadata.json | xeokit 元模型,schema 见下 |
GET /v1/models/{id}/render.json | CAD 渲染数据,仅 dxf 模型,schema 见下 |
GET /v1/models/{id}/issues/{file} | Issue 截图 |
metadata.json Schema
由 converter 从 IFC 提取,含空间结构树和属性集,可直接作为 XKTLoaderPlugin.load 的输入:
{
"projectId": "3xFoo",
"metaObjects": [
{"id": "1AbC...", "type": "IfcBuildingStorey", "name": "Level 1", "parent": "0Root"},
{"id": "2XdE...", "type": "IfcWall", "name": "Wall-001", "parent": "1AbC...", "propertySetIds": ["pset_2XdE_0"]}
],
"propertySets": [
{
"id": "pset_2XdE_0",
"name": "Pset_WallCommon",
"type": "Pset",
"properties": [
{"name": "FireRating", "value": "120min", "type": "IfcLabel"},
{"name": "LoadBearing", "value": true, "type": "IfcBoolean"}
]
}
]
}metaObjects[].id 就是 IFC GlobalId,与 XKT 实体 id 一致。层级为 Site、Building、Storey 到构件。没有 pset 的构件省略 propertySetIds。
render.json Schema
由 services/cad 在 run 或 save 成功后原子发布,供前端 Canvas 二维预览。坐标保留原始 DXF 坐标系:
{
"schemaVersion": 2,
"bounds": {"min": [0, 0], "max": [100, 80]},
"layers": [{"name": "WALL", "color": 7, "linetype": "CONTINUOUS"}],
"entities": [
{"key": "e_1a2b3c", "type": "LINE", "layer": "WALL", "start": [0, 0], "end": [10, 0]}
],
"unsupported": [{"type": "HATCH", "handle": "1F", "coords": [5, 5]}]
}约定:bounds 在没有可用坐标时为 null;entities[].key 是 XDATA 稳定 key,前端选中实体即得到 key;LWPOLYLINE 炸开成 LINE 和 ARC 条目;INSERT 只展开一层,子实体 key 为 null;白名单外的实体明面列入 unsupported,不静默丢弃。
通用错误码
40001 参数校验错误,40002 超限,40400 不存在,50000 内部错误。
机器可消费 OpenAPI
edit-service
完整 schema 见 ai-tools.openapi.json,由 FastAPI 直接导出,与运行中服务的 GET /openapi.json 一致;渲染成文档的版本见编辑 API 参考。编辑 API 变更后重新生成:
cd services/ifc
uv run python scripts/export_openapi.py # 输出到 docs/site/public/ai-tools.openapi.jsonGo server
完整 schema 见 go-server.openapi.json,可以直接喂给 LLM、工具或代码生成器。说明一点:Go 的标准库 mux 没有反射,schema 无法从代码自动导出,因此采用三层机制——路由清单由脚本从 mux 注册自动提取,请求响应 schema 手工维护,生成器对两者做双向覆盖断言。新增路由没配 schema,或 schema 里有已删除的路由,生成都会失败,CI 会红。
cd docs
npm run gen:api # 生成三件产物:edit-api-reference.md、go-rest-api.routes.json、go-server.openapi.json
npm run check:api # 自证测试 + 生成 + git 漂移检测,无 diff 才绿