Skip to content

fix: allow user-defined interfaces as agent Props#1906

Closed
mattzcarey wants to merge 2 commits into
mainfrom
fix/1886-agent-props-interface-types
Closed

fix: allow user-defined interfaces as agent Props#1906
mattzcarey wants to merge 2 commits into
mainfrom
fix/1886-agent-props-interface-types

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

The Props generic on Agent (and every API that carries props) was bounded by Record<string, unknown>. Interfaces do not get implicit index signatures in TypeScript, so a well-typed interface fails that bound and users hit:

Type 'MyProps' is not assignable to type 'Record<string, unknown>'.
  Index signature for type 'string' is missing in type 'MyProps'.

exactly as reported on a getAgentByName(ns, name, { props: config }) call.

Fix — object bounds + derived props generics (no any)

  • Props extends object = Record<string, unknown> on Agent, McpAgent, getAgentByName, and AgentGetOptions. Interfaces satisfy object natively; defaults stay Record<string, unknown> so untyped usage still reads props values as unknown.
  • partyserver seam: partyserver still bounds its Props at Record<string, unknown> (Server, getServerByName), which we can't change from this repo. Agent bridges it with extends Server<Env, Props & Record<string, unknown>> — the intersection is erased at runtime (props is a JSON bag) — and getAgentByName widens the props value with one commented cast when handing off to getServerByName.
  • addMcpServer RPC props are now derived, not loosened: the overload takes AddRpcMcpServerOptions<McpAgentProps<T>> where McpAgentProps<T> = NonNullable<T["props"]> reads the Props type off the target McpAgent. Props passed over RPC are type-checked against the agent actually being connected to — a wrong-shape props object is now a compile error (previously any record was accepted). The old bare-McpAgent constraint also rejected typed subclasses through Durable Object stub variance; the constraint is now T extends McpAgent<Cloudflare.Env, unknown, object>.

Repro / regression tests

New compile-time test src/tests-d/agent-props.test-d.ts, following the existing tests-d/ pattern (runs under the package typecheck, which CI enforces). Written first and confirmed red on main with 6 errors matching the issue:

  • getAgentByName with inferred and explicit interface Props
  • Agent / McpAgent class declarations with interface Props, and this.props keeping the interface type
  • addMcpServer(name, namespace, { props }) with an interface-Props McpAgent namespace, plus McpAgentProps<T> extraction
  • negative pins: primitives are still rejected as Props, and wrong-shape RPC props are rejected against the target agent's declared Props

Verification

  • npm run check clean (sherif, exports, oxfmt, oxlint, all 116 typecheck projects)
  • Full packages/agents runtime suite passing (types-only change)

Out of scope / follow-up

routeAgentRequest's AgentOptions pins props at the partyserver default and would need an upstream partyserver bound change (object instead of Record<string, unknown> on Server/getServerByName/routePartykitRequest) — that would also remove the intersection bridge and cast here.

Fixes #1886

Interfaces do not get implicit index signatures in TypeScript, so the
Record<string, unknown> bound on Props rejected them with 'Index
signature for type string is missing'.

Introduce a single shared AgentProps bound (Record<string, any> — the
one record shape every interface satisfies) in types.ts and use it for
every props surface: the Agent, McpAgent, getAgentByName, and
AgentGetOptions generics, the addMcpServer RPC overload constraint and
its props option, and RPCClientTransportOptions. Generic defaults stay
Record<string, unknown> so untyped usage still reads props values as
unknown.

Fixes #1886
@changeset-bot

changeset-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 836acfb

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
agents Patch
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot 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.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@pkg-pr-new

pkg-pr-new Bot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@1906

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@1906

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@1906

create-think

npm i https://pkg.pr.new/create-think@1906

hono-agents

npm i https://pkg.pr.new/hono-agents@1906

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@1906

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@1906

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@1906

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@1906

commit: 836acfb

…-records

Per review: no any. Props bounds become 'extends object' (interfaces
satisfy object natively) on Agent, McpAgent, getAgentByName, and
AgentGetOptions; Agent bridges partyserver's stricter
Record<string, unknown> bound with an intersection that is erased at
runtime, plus one commented value-cast at the getServerByName seam.

The addMcpServer RPC overload now derives its props type from the
target McpAgent via McpAgentProps<T> (NonNullable<T['props']>), so RPC
props are type-checked against the agent being connected to instead of
accepting any record.
@mattzcarey

Copy link
Copy Markdown
Contributor Author

Closing in favor of fixing the Props bound upstream in partyserver (cloudflare/partykit), where Server/getServerByName own the Record<string, unknown> constraint — that removes the need for the intersection bridge and cast here. Issue #1886 stays open until the upstream fix lands and agents bumps the dependency.

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.

Allow proper types for agent props

1 participant