Skip to content

Default a missing input item type to message - #117

Merged
shibayan merged 4 commits into
masterfrom
issue-100
Aug 31, 2026
Merged

Default a missing input item type to message#117
shibayan merged 4 commits into
masterfrom
issue-100

Conversation

@shibayan

@shibayan shibayan commented Aug 30, 2026

Copy link
Copy Markdown
Member

What this changes

Fixes #100. POST /responses required every input item to name a string type, so a plain OpenAI
Responses EasyInputMessage{ "role": "user", "content": "hello" } — was answered with a 400
that neither reference server gives. An item whose type property is absent is now judged by the
message rules and normalized to type: "message" in parseCreateRequest, which is what puts the
resolved discriminator in front of every later reader at once: the handler receives the parsed
request, and the same input array feeds the id minting and the stored transcript, where an
untyped item had no known id prefix and dropped silently out of persistence.

The default applies only to an absent property — type: null and any other non-string type stay
a 400 — and neither the caller's request object nor its items are written to.

An item that names type: "message" is held to the same message rules. Defaulting the absent
discriminator would otherwise have given one shape two validators, letting a caller skip the
role / content checks by writing out the discriminator those checks belong to.

Parity

  • Reference checked: .NET ItemValidatorCustom.ResolveDefaultDiscriminator, which supplies
    "message", and Python _request_validators.py, whose _validate_OpenAI_Item reads
    value.get("type", "message") and hands both the defaulted and the written-out message to the
    single _validate_OpenAI_ItemMessage, which requires role and content. Both refuse an id-only
    object, which needs the explicit item_reference discriminator; that stays refused here.
    Deliberate divergence: .NET also defaults a non-string type, and this follows Python in treating
    one as a broken discriminator instead.
  • Wire format affected: yes — an input item may now omit its type. A malformed message item is
    reported at $.input[i].role and $.input[i].content rather than only at $.input[i]; the HTTP
    status and error envelope are unchanged.
  • Public API affected: no
  • Breaking change: yes

What breaks: { "type": "message", "id": "x" } was accepted and is now a 400. Point at a stored item with { "type": "item_reference", "id": "x" }, or give the message a role and content. Items of every other type are unaffected.

Checklist

  • pnpm check passes (lint, typecheck, build, test)
  • Behaviour changes are covered by a test that fails without the change

`POST /responses` required every input item to name a string `type`, so a
plain OpenAI Responses `EasyInputMessage` — `{ "role": "user", "content":
"hello" }` — was answered with a 400 that neither reference server gives.
Both resolve an absent discriminator to `message` before dispatching: .NET's
item validator supplies the default from its custom discriminator resolver,
Python's `_request_validators.py` reads `value.get("type", "message")`.

The schema pass now dispatches the same way. An item whose `type` property is
absent is judged by the message rules — `role` and `content` are required, and
each is reported at its own path (`$.input[i].role`, `$.input[i].content`) —
and `parseCreateRequest` writes the resolved `message` onto a copy of the item
before returning. Normalizing in the parser rather than in the server is what
puts the resolved type in front of every later reader at once: the handler
receives the parsed request, and the same `input` array feeds the id minting
and the stored transcript, where an untyped item has no known id prefix and
would silently drop out of persistence.

The default applies only to an absent property. `type: null` and any other
non-string `type` stay a 400 at `$.input[i]`, an explicit discriminator is
preserved untouched, and `{ "id": "x" }` is still refused — referencing a
stored item needs the explicit `item_reference` discriminator, which continues
to be accepted. Neither the caller's request object nor its items are written
to: only the items that need the default are rebuilt, and a request where none
does is handed back as-is. The error envelope and HTTP status of every
malformed input are unchanged.

Fixes #100

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 30, 2026 08:14
@shibayan shibayan added the bug Usage: [PRs], Target: bug fixes and regressions; issues use the Bug issue type label Aug 30, 2026
@github-actions github-actions Bot added documentation Usage: [Issues, PRs], Target: documentation changes agentserver Usage: [Issues, PRs], Target: packages/agentserver hosting Usage: [Issues, PRs], Target: agent hosting protocols and runtime labels Aug 30, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

Pull request overview

This pull request updates the agentserver’s POST /responses request validation to match reference server behavior by accepting OpenAI Responses-style input items that omit the type discriminator (e.g. { role, content }). It normalizes such items to type: "message" in parseCreateRequest so downstream paths (handler dispatch, id minting, and transcript persistence) consistently observe the resolved discriminator.

Changes:

  • Extend schema validation to treat an input item with an absent type as a message shape and report field-level errors at $.input[i].role / $.input[i].content.
  • Normalize type-less input items to { type: "message", ...item } without mutating caller-owned objects.
  • Add targeted unit/integration tests and document the behavior change in the changelog.
File summaries
File Description
packages/agentserver/src/validation.ts Defaults absent input item type to "message" via normalization while preserving error envelope and avoiding caller-object mutation.
packages/agentserver/src/validation.test.ts Adds schema/normalization tests covering defaulting, non-mutation, invalid discriminators, and field-level error paths.
packages/agentserver/src/server.test.ts Adds an end-to-end route test ensuring the handler and persistence see type: "message" and that ids are minted/persisted.
CHANGELOG.md Records the wire-format/validation change for the agentserver package in Unreleased.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI review requested due to automatic review settings August 31, 2026 02:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread packages/agentserver/src/validation.ts
Comment thread packages/agentserver/src/validation.test.ts
Copilot AI review requested due to automatic review settings August 31, 2026 02:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Defaulting an absent `type` to `message` gave the two spellings of one shape
two different validators: a type-less item answered for `role` and `content`,
while an item that named `message` returned early and answered for nothing. A
caller could skip the checks by writing out the discriminator they belong to,
and `{ "type": "message", "id": "x" }` was accepted while the equivalent
type-less object was refused.

Both reference servers dispatch the two spellings to one validator. Python's
`_validate_OpenAI_Item` reads `value.get("type", "message")` and hands every
`message` — defaulted or written — to `_validate_OpenAI_ItemMessage`, which
requires `role` and `content`. The explicit spelling now takes the same path
here, reported at the same `$.input[i].role` / `$.input[i].content` paths.
Items of every other type keep returning to their own shape.

The terse `{ type: 'message', id }` fixtures in the server tests stood for
input a caller sends, so they carry a role and content now; the stored output
item in the handler fixture is untouched.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 31, 2026 03:06
@shibayan shibayan added the breaking change Usage: [PRs], Target: changes that are not backward compatible label Aug 31, 2026
@github-actions github-actions Bot removed the breaking change Usage: [PRs], Target: changes that are not backward compatible label Aug 31, 2026
@shibayan
shibayan merged commit 4c17798 into master Aug 31, 2026
11 checks passed
@shibayan
shibayan deleted the issue-100 branch August 31, 2026 03:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

CHANGELOG.md:51

  • The PR description states "Breaking change: no", but this changelog entry is marked [BREAKING] and describes a behavior change ({ "type": "message", "id": "x" } was accepted before and is now a 400). Please reconcile these two so release notes and PR metadata agree on whether this is considered a breaking change.
- **[BREAKING] `@polymind-inc/agent-framework-agentserver`** — an input item that names
  `type: "message"` is held to the message rules, the same ones an item that omits its `type`
  answers for. Both reference servers dispatch the two spellings to one validator — Python's
  `_validate_OpenAI_ItemMessage` requires `role` and `content` whether the discriminator was
  written out or defaulted — so writing `"type": "message"` no longer buys a laxer check than
  leaving it off. `{ "type": "message", "id": "x" }` was accepted before and is now a 400 naming
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +140 to +143
if (!isPlainObject(item)) {
wrong(path, "an object with a string 'type'");
return;
}
@github-actions github-actions Bot added the breaking change Usage: [PRs], Target: changes that are not backward compatible label Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentserver Usage: [Issues, PRs], Target: packages/agentserver breaking change Usage: [PRs], Target: changes that are not backward compatible bug Usage: [PRs], Target: bug fixes and regressions; issues use the Bug issue type documentation Usage: [Issues, PRs], Target: documentation changes hosting Usage: [Issues, PRs], Target: agent hosting protocols and runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

agentserver rejects input items without an explicit type that both reference SDKs accept

2 participants