Skip to content

fix(cron): accept nested GET-response shape in POST /api/cron/jobs (fixes iOfficeAI/AionUi#4042) - #858

Open
cdxiaodong wants to merge 1 commit into
iOfficeAI:mainfrom
cdxiaodong:fix/cron-create-nested-body
Open

fix(cron): accept nested GET-response shape in POST /api/cron/jobs (fixes iOfficeAI/AionUi#4042)#858
cdxiaodong wants to merge 1 commit into
iOfficeAI:mainfrom
cdxiaodong:fix/cron-create-nested-body

Conversation

@cdxiaodong

Copy link
Copy Markdown

问题

POST /api/cron/jobs(创建定时任务)对任何 JSON 请求体都无差别返回 400 Invalid JSON request body,包括语法合法的最简 body {}GET /api/cron/jobsPUT /api/cron/jobs/{id} 均正常,仅 create 失败。这导致所有 agent-facing CLI 创建 cron 任务的通道(aioncore config cron current create / aioncore config cron jobs create)全部不可用。

上游 issue:iOfficeAI/AionUi#4042(已在该 issue 评论中附完整复现证据与截图)。

根因

CreateCronJobRequest 使用 #[serde(deny_unknown_fields)],且仍是旧的扁平 schema(顶层 message / 必填 conversation_id / 必填 created_by)。

而 agent / CLI 是按 GET /api/cron/jobs 返回的嵌套结构反向构造 create body 的:

{ "name": ..., "enabled": ..., "schedule": {...},
  "target": { "payload": {"kind":"message","text":...}, "execution_mode": "new_conversation" } }

其中的 enabledtarget 等字段在 CreateCronJobRequest 里不存在 → 触发 deny_unknown_fields → serde 在反序列化第一步即拒绝(后端日志 latency_ms=0 可证),根本走不到字段校验。PUT /{id} 正常,正是因为 UpdateCronJobRequest 没有 deny_unknown_fields 且字段全为 Option

修复

让 create DTO 同时兼容两种结构(向后兼容,旧扁平调用方不受影响):

  • CreateCronJobRequest 新增可选 target(复用现有 CronJobTargetDto)与 enabled 字段
  • conversation_id / created_by 改为可选:created_by 在 service 层默认 "agent";conversation_idexisting 执行模式下仍由 service 层强制校验(行为不变)
  • 移除 deny_unknown_fields:未知字段忽略而非 400,使对 PUT 合法的 body 也能用于 create
  • service 层归一化:target.payload.textmessagetarget.execution_modeexecution_mode(扁平字段优先);enabled 默认值 true 保持原有行为,不再硬编码

测试

  • 新增单元测试:create_request_nested_response_shape(嵌套结构可反序列化)、create_request_ignores_unknown_fields(未知字段不拒绝)
  • 更新 3 个反映旧契约的测试(missing_conversation_id / missing_created_by / legacy_agent_type)以匹配新行为
  • cargo test -p aionui-api-types --lib cron:22 passed
  • cargo test -p aionui-cron:280 passed, 0 failed
  • cargo clippy / cargo fmt --check:无警告

兼容性

完全向后兼容:原有扁平 body(含 conversation_id / created_by / 顶层 message)照常工作;新增嵌套 body 现在也能正确创建任务,与 GET 响应及 PUT 行为对齐。

The create endpoint rejected every JSON body with 400 "Invalid JSON
request body." because CreateCronJobRequest used #[serde(deny_unknown_fields)]
over a legacy flat schema (top-level message/conversation_id/created_by),
while agents and the CLI build create bodies by mirroring the nested shape
returned by GET /api/cron/jobs (target.payload.text, target.execution_mode,
enabled). The unknown fields tripped deny_unknown_fields and serde rejected
the body before any field validation, so even {} failed. PUT /{id} was
unaffected because UpdateCronJobRequest has no deny_unknown_fields and
all-optional fields.

Make the create DTO accept both shapes:
- add optional target (reusing CronJobTargetDto) and enabled fields
- make conversation_id/created_by optional (created_by defaults to "agent"
  in the service layer; conversation_id is still required at the service
  layer for existing execution mode)
- drop deny_unknown_fields so a body valid for PUT also deserializes for
  create

The service layer normalizes target.payload.text -> message and
target.execution_mode -> execution_mode, with flat fields taking precedence,
and honors the enabled flag instead of hardcoding true.

Fixes iOfficeAI/AionUi#4042
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant