chore: version packages#57
Conversation
8fef5b9 to
f030a4d
Compare
There was a problem hiding this comment.
⚠️ APPROVE unavailable on this installation — the maintainer GitHub App is not
configured with write access on this org, so the verdict below is posted as COMMENT.
Event-level approval (for branch-protection / review requirements) must be added out-of-band.
Perry's Review
Automated Changesets "version packages" PR — deletes 3 consumed changeset files and bumps @openrouter/agent (0.7.2→0.8.0) and @openrouter/mcp (0.1.0→0.2.0) with matching CHANGELOG entries.
Verdict: ✅ LGTM
Details
Risk: 🟢 Low — mechanical version/changelog bump only, no source code touched
CI: all passing ✅ (e2e-tests, lint, structural-gate, typecheck, unit-tests)
Findings: none
Research: n/a — small tier, no deep research pass
Security: no concerns — diff touches only changeset markdown files, CHANGELOG.md entries, and package.json version fields; no secrets, no code paths
Test coverage: n/a — no code changed
Unresolved threads: none
Verification: all 3 deleted changesets correspond exactly to entries appended to both CHANGELOGs; both version bumps are minor bumps matching their "### Minor Changes" sections; @openrouter/mcp's workspace dependency on @openrouter/agent resolves automatically, no other file needed updating.
Scope: first review (full)
Review: tier=small · model=claude-sonnet-latest · score=0.8
5cae45f to
c7c8835
Compare
c7c8835 to
c14950a
Compare
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@openrouter/[email protected]
Minor Changes
#66
c83ccebThanks @LukasParke! - Add a versionedConversationStateserialization contract.versionfield onConversationState(absence means v1);createInitialStatenow stampsversion: 1.serializeConversationState/deserializeConversationState(package root +@openrouter/agent/conversation-state).UnsupportedStateVersionError({found, supported}) andInvalidStateErrorfor malformed payloads.deserializeConversationStateon version bump. StateAccessor load/save is unchanged — helpers are opt-in wrappers over what consumers already do withJSON.stringify/parse.#64
e4d06e3Thanks @LukasParke! - Persist unresolved manual tool calls (execute: false/ no execute fn) toConversationState.pendingToolCallswhen the loop stops, and set status to the new value'awaiting_client_tools'.Previously, HITL pauses (
onToolCalled → null) correctly populatedpendingToolCallswith status'awaiting_hitl', but bare manual tools onlybreak'd the loop —getPendingToolCalls()returned[]and status was leftin_progress/complete. Cold-start consumers could not recover the unresolved calls from serialized state.ConversationStatusvalue:'awaiting_client_tools'(additive; does not replace'awaiting_hitl').pendingToolCalls.'awaiting_client_tools'clears the stale pendings and continues as a normal turn. Failed resume requests leave the paused state intact. Manual tools are not approved/rejected via call IDs (unlike HITL/awaiting_approval).#7
80ff8a7Thanks @mattapperson! - Add a typed lifecycle hook system tocallModel, inspired by the Claude Agent SDK hooks pattern.Two usage modes: an inline config object (built-in hooks only) or a
HooksManagerinstance (custom hooks, dynamic registration viaon()/off()/removeAll(), programmaticemit()).Eight built-in hooks:
PreToolUse(block or mutate tool input before every client-tool execution),PostToolUse/PostToolUseFailure(observe results and errors with timing),UserPromptSubmit(mutate or reject the prompt before the initial request),PermissionRequest(programmatically allow/deny/ask for tools requiring approval),Stop(force-resume a halted loop or inject a follow-up prompt, capped against runaway handlers), andSessionStart/SessionEnd(paired once per run on every exit path, including approval pauses, interruptions, errors, and no-tools streaming paths).Features: tool matchers (string / RegExp / predicate), payload filter predicates, sequential mutation piping, short-circuit on block/reject, async fire-and-forget handlers with
drain()/abortInflight()/ per-handler timeouts and cooperative cancellation viactx.signal, configurable error handling (throwOnHandlerError), and custom hook definitions via Zod schema pairs with full TypeScript inference (transforms/defaults are honored — handlers receive parsed output values).The API is additive: existing
onTurnStart,onTurnEnd, andrequireApprovalare unchanged. Public exports:HooksManager,HookName,isAsyncOutput, and the payload/result/config types; also available via the@openrouter/agent/hooks-managersubpath.#56
209499aThanks @mattapperson! - Add asourcediscriminant to tool results so untyped MCP tools no longer collapse the type safety of typed tools.Previously, mixing an MCP tool (whose output schema is
unknown) with fully-typed tools in onecallModel({ tools })array collapsed the entire result union tounknown— one untyped tool poisoned every other tool's result type.ToolExecutionResult(andToolExecutionResultUnion) now carrysource: 'client' | 'mcp'. Narrowing onsource === 'client'recovers the precise, schema-derived results for your own tools; MCP results stay isolated asunknownundersource === 'mcp'.ToolResultEvent(streaming:getFullResponsesStream,getToolStream) gains the samesourcefield. Breaking: thetool.resultevent payload now includessource; consumers that constructed or exhaustively matched these events may need to account for it.@openrouter/agentexports amarkMcp()helper, anisMcpTool()guard, and theMcpBrandedtype.@openrouter/mcpbrands every wrapped tool (including syntheticlist_resources/read_resource) so the discrimination is automatic — callers just spreadmcp.toolsas before.type: 'function'; the brand is purely informational and does not change runtime behavior.#67
cb83f45Thanks @LukasParke! - Add aPostModelCalllifecycle hook and aggregate usage totals onSessionEnd— the telemetry primitives for tracing and benchmark consumers.PostModelCallfires once per completed model response, on every request the agent loop makes: the initial request, each tool-round follow-up, the empty-final retry, theallowFinalResponsefinal turn, and approval-resume requests. The payload carriesresponseId(the OpenRouter generation id, deep-linkable),model,durationMs(dispatch to fully materialized response, including stream consumption),turnType('initial' | 'resume' | 'tool_round' | 'final' | 'retry'),turnNumber, and a normalizedusageblock (inputTokens,outputTokens,totalTokens,cachedTokens,reasoningTokens,cost?) when the server reported usage accounting. Purely observational: handlers cannot mutate or block.SessionEndnow carries an optionaltotalUsageaggregate (modelCallsplus the summed usage fields, withcostpresent when any call reported one) whenever at least one model call completed during the run.New exported types:
PostModelCallPayload,ModelCallUsage,SessionUsageTotals.Patch Changes
#62
1362232Thanks @LukasParke! - Docs: fix three README/API drifts found while building production agents — tool context isctx.local(notctx.context); thestateoption takes aStateAccessor({load, save}) and state is read viaresult.getState()(not(await getResponse()).state);getToolStream()emits argument deltas + generator preliminary results, while execution results are ongetFullResponsesStream(). Adds a streams cheat-sheet table.#65
09a041eThanks @LukasParke! - Infer tool context types fromcontextSchemaend-to-end:tool()now preserves the concrete Zod schema through its overloads, soexecute'sctx.localis typed from the schema andcallModel'scontextmap slots accept/reject the real per-tool shape — no morectx.local as Xorcontext: map as any. Tools without acontextSchemastill resolve their map slot toRecord<string, never>. Types-only; runtime behavior unchanged.#61
c020bc7Thanks @LukasParke! - Fix: bare stringinputis now normalized into a message item when resuming a conversation with loaded history. Previously the raw string was appended to the request input array un-normalized, causing an OpenResponses 400 validation error on the advertised string-input style.#63
d96cd9fThanks @LukasParke! - Tolerate empty finaloutputafter completed tool rounds: retry the follow-up request once, then resolve successfully with empty text instead of throwingInvalid final response: empty or invalid output. Mini-class models intermittently treat a successful tool call as the terminal answer. Opt into the old throw withstrictFinalResponse: true. Runs with no completed tool work still throw on empty output.#59
8edae63Thanks @ayush-or! - Stop the tool-execution loop when a round contains unresolved manual (client-executed) tool calls, instead of sending a follow-up request whose input carries afunction_callwith no matchingfunction_call_output— a history providers reject with a 400 "No tool output found for function call ...". The response is surfaced so the caller can execute the manual calls and continue, mirroring the existing all-manual behavior.@openrouter/[email protected]
Minor Changes
#56
209499aThanks @mattapperson! - Add asourcediscriminant to tool results so untyped MCP tools no longer collapse the type safety of typed tools.Previously, mixing an MCP tool (whose output schema is
unknown) with fully-typed tools in onecallModel({ tools })array collapsed the entire result union tounknown— one untyped tool poisoned every other tool's result type.ToolExecutionResult(andToolExecutionResultUnion) now carrysource: 'client' | 'mcp'. Narrowing onsource === 'client'recovers the precise, schema-derived results for your own tools; MCP results stay isolated asunknownundersource === 'mcp'.ToolResultEvent(streaming:getFullResponsesStream,getToolStream) gains the samesourcefield. Breaking: thetool.resultevent payload now includessource; consumers that constructed or exhaustively matched these events may need to account for it.@openrouter/agentexports amarkMcp()helper, anisMcpTool()guard, and theMcpBrandedtype.@openrouter/mcpbrands every wrapped tool (including syntheticlist_resources/read_resource) so the discrimination is automatic — callers just spreadmcp.toolsas before.type: 'function'; the brand is purely informational and does not change runtime behavior.#56
26336b5Thanks @mattapperson! - Add@openrouter/mcp: expose remote MCP server tools (Streamable HTTP / SSE) ascallModeltools.createMCPTools()connects to a non-stdio MCP server, authenticates once (bearer token, custom headers, or a pluggableOAuthClientProvider), and returns a handle whose.toolsdrop straight intocallModel({ tools }). The same auth is reused for tool discovery and every tool call.convertMcpInputSchema) so the model sees real parameters; tool output schemas are mapped too.serialize()/rehydrateMCPTools()/ pluggableMCPCacheStore+InMemoryMCPCacheStore) that skips re-listing and, opt-in, re-authentication. Credential caching is off by default.tools/list_changedauto-refresh, cancellation via an abort signal, resources exposed as syntheticlist_resources/read_resourcetools, and elicitation with an optional handler (auto-declines when none is provided).Patch Changes
1362232,c83cceb,09a041e,e4d06e3,c020bc7,d96cd9f,80ff8a7,209499a,8edae63,cb83f45]: