Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .changeset/docs-readme-api-drift.md

This file was deleted.

10 changes: 0 additions & 10 deletions .changeset/feat-versioned-state-contract.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/fix-context-schema-inference.md

This file was deleted.

11 changes: 0 additions & 11 deletions .changeset/fix-manual-tool-pending-state.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/fix-resume-string-input.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/fix-tool-terminal-empty-final.md

This file was deleted.

13 changes: 0 additions & 13 deletions .changeset/hooks-lifecycle-system.md

This file was deleted.

13 changes: 0 additions & 13 deletions .changeset/mcp-result-discrimination.md

This file was deleted.

10 changes: 0 additions & 10 deletions .changeset/openrouter-mcp-initial.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/orange-drinks-smile.md

This file was deleted.

11 changes: 0 additions & 11 deletions .changeset/post-model-call-telemetry.md

This file was deleted.

58 changes: 58 additions & 0 deletions packages/agent/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,63 @@
# @openrouter/agent

## 0.8.0

### Minor Changes

- [#66](https://github.com/OpenRouterTeam/typescript-agent/pull/66) [`c83cceb`](https://github.com/OpenRouterTeam/typescript-agent/commit/c83cceb17ec1d66b9a1fd2d46ac8ac9b6e60fa4c) Thanks [@LukasParke](https://github.com/LukasParke)! - Add a versioned `ConversationState` serialization contract.

- Optional `version` field on `ConversationState` (absence means v1); `createInitialState` now stamps `version: 1`.
- New helpers: `serializeConversationState` / `deserializeConversationState` (package root + `@openrouter/agent/conversation-state`).
- Typed errors: `UnsupportedStateVersionError` (`{found, supported}`) and `InvalidStateError` for malformed payloads.
- Compat policy: treat JSON as opaque; additive changes within a major; migrations run in `deserializeConversationState` on version bump. StateAccessor load/save is unchanged — helpers are opt-in wrappers over what consumers already do with `JSON.stringify`/`parse`.

- [#64](https://github.com/OpenRouterTeam/typescript-agent/pull/64) [`e4d06e3`](https://github.com/OpenRouterTeam/typescript-agent/commit/e4d06e38215d6eafbd5c198e3485f476e65d26f0) Thanks [@LukasParke](https://github.com/LukasParke)! - Persist unresolved manual tool calls (`execute: false` / no execute fn) to `ConversationState.pendingToolCalls` when the loop stops, and set status to the new value `'awaiting_client_tools'`.

Previously, HITL pauses (`onToolCalled → null`) correctly populated `pendingToolCalls` with status `'awaiting_hitl'`, but bare manual tools only `break`'d the loop — `getPendingToolCalls()` returned `[]` and status was left `in_progress`/`complete`. Cold-start consumers could not recover the unresolved calls from serialized state.

- New `ConversationStatus` value: `'awaiting_client_tools'` (additive; does not replace `'awaiting_hitl'`).
- Mixed auto+manual rounds still execute/persist regular tool outputs, then pause with only the unresolved manual calls in `pendingToolCalls`.
- A successful resume with new input from `'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](https://github.com/OpenRouterTeam/typescript-agent/pull/7) [`80ff8a7`](https://github.com/OpenRouterTeam/typescript-agent/commit/80ff8a730292aa00a3acfcce6ab1e9f5a6a7f0de) Thanks [@mattapperson](https://github.com/mattapperson)! - Add a typed lifecycle hook system to `callModel`, inspired by the Claude Agent SDK hooks pattern.

Two usage modes: an inline config object (built-in hooks only) or a `HooksManager` instance (custom hooks, dynamic registration via `on()`/`off()`/`removeAll()`, programmatic `emit()`).

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), and `SessionStart` / `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 via `ctx.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`, and `requireApproval` are unchanged. Public exports: `HooksManager`, `HookName`, `isAsyncOutput`, and the payload/result/config types; also available via the `@openrouter/agent/hooks-manager` subpath.

- [#56](https://github.com/OpenRouterTeam/typescript-agent/pull/56) [`209499a`](https://github.com/OpenRouterTeam/typescript-agent/commit/209499abacd6783ee5c98155bb2a676e3932c3f4) Thanks [@mattapperson](https://github.com/mattapperson)! - Add a `source` discriminant 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 one `callModel({ tools })` array collapsed the entire result union to `unknown` — one untyped tool poisoned every other tool's result type.

- `ToolExecutionResult` (and `ToolExecutionResultUnion`) now carry `source: 'client' | 'mcp'`. Narrowing on `source === 'client'` recovers the precise, schema-derived results for your own tools; MCP results stay isolated as `unknown` under `source === 'mcp'`.
- `ToolResultEvent` (streaming: `getFullResponsesStream`, `getToolStream`) gains the same `source` field. **Breaking:** the `tool.result` event payload now includes `source`; consumers that constructed or exhaustively matched these events may need to account for it.
- `@openrouter/agent` exports a `markMcp()` helper, an `isMcpTool()` guard, and the `McpBranded` type. `@openrouter/mcp` brands every wrapped tool (including synthetic `list_resources`/`read_resource`) so the discrimination is automatic — callers just spread `mcp.tools` as before.
- MCP tools continue to execute locally and serialize to the wire as `type: 'function'`; the brand is purely informational and does not change runtime behavior.

- [#67](https://github.com/OpenRouterTeam/typescript-agent/pull/67) [`cb83f45`](https://github.com/OpenRouterTeam/typescript-agent/commit/cb83f45209ff66f8c58077f4e0a85d35f884afdb) Thanks [@LukasParke](https://github.com/LukasParke)! - Add a `PostModelCall` lifecycle hook and aggregate usage totals on `SessionEnd` — the telemetry primitives for tracing and benchmark consumers.

`PostModelCall` fires once per completed model response, on **every** request the agent loop makes: the initial request, each tool-round follow-up, the empty-final retry, the `allowFinalResponse` final turn, and approval-resume requests. The payload carries `responseId` (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 normalized `usage` block (`inputTokens`, `outputTokens`, `totalTokens`, `cachedTokens`, `reasoningTokens`, `cost?`) when the server reported usage accounting. Purely observational: handlers cannot mutate or block.

`SessionEnd` now carries an optional `totalUsage` aggregate (`modelCalls` plus the summed usage fields, with `cost` present when any call reported one) whenever at least one model call completed during the run.

New exported types: `PostModelCallPayload`, `ModelCallUsage`, `SessionUsageTotals`.

### Patch Changes

- [#62](https://github.com/OpenRouterTeam/typescript-agent/pull/62) [`1362232`](https://github.com/OpenRouterTeam/typescript-agent/commit/1362232975f0254343f9842f30ec1b35d391f4fe) Thanks [@LukasParke](https://github.com/LukasParke)! - Docs: fix three README/API drifts found while building production agents — tool context is `ctx.local` (not `ctx.context`); the `state` option takes a `StateAccessor` (`{load, save}`) and state is read via `result.getState()` (not `(await getResponse()).state`); `getToolStream()` emits argument deltas + generator preliminary results, while execution results are on `getFullResponsesStream()`. Adds a streams cheat-sheet table.

- [#65](https://github.com/OpenRouterTeam/typescript-agent/pull/65) [`09a041e`](https://github.com/OpenRouterTeam/typescript-agent/commit/09a041ea717b384c6c85d7c81ef391b170b0dd8f) Thanks [@LukasParke](https://github.com/LukasParke)! - Infer tool context types from `contextSchema` end-to-end: `tool()` now preserves the concrete Zod schema through its overloads, so `execute`'s `ctx.local` is typed from the schema and `callModel`'s `context` map slots accept/reject the real per-tool shape — no more `ctx.local as X` or `context: map as any`. Tools without a `contextSchema` still resolve their map slot to `Record<string, never>`. Types-only; runtime behavior unchanged.

- [#61](https://github.com/OpenRouterTeam/typescript-agent/pull/61) [`c020bc7`](https://github.com/OpenRouterTeam/typescript-agent/commit/c020bc7c86d2f743ecf9158ca3c9ff7b315e43b3) Thanks [@LukasParke](https://github.com/LukasParke)! - Fix: bare string `input` is 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](https://github.com/OpenRouterTeam/typescript-agent/pull/63) [`d96cd9f`](https://github.com/OpenRouterTeam/typescript-agent/commit/d96cd9fc589c27978bcdc2fd1921f754be88e3f0) Thanks [@LukasParke](https://github.com/LukasParke)! - Tolerate empty final `output` after completed tool rounds: retry the follow-up request once, then resolve successfully with empty text instead of throwing `Invalid final response: empty or invalid output`. Mini-class models intermittently treat a successful tool call as the terminal answer. Opt into the old throw with `strictFinalResponse: true`. Runs with no completed tool work still throw on empty output.

- [#59](https://github.com/OpenRouterTeam/typescript-agent/pull/59) [`8edae63`](https://github.com/OpenRouterTeam/typescript-agent/commit/8edae63f4f6fe89e146f3abbf6d24dab7a164681) Thanks [@ayush-or](https://github.com/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 a `function_call` with no matching `function_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.

## 0.7.2

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/agent/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openrouter/agent",
"version": "0.7.2",
"version": "0.8.0",
"author": "OpenRouter",
"description": "Agent toolkit for building AI applications with OpenRouter — tool orchestration, streaming, multi-turn conversations, and format compatibility.",
"keywords": [
Expand Down
Loading