llama.cpp's /v1/responses emits function_call `output_item.added` with
only `item.call_id` (no `item.id`, no `output_index`); the matching
`function_call_arguments.delta` carries `item_id: "fc_<call_id>"`.
`processResponsesStream` keyed its lookup map by `item.id` only, so
the registry stayed empty and every delta fell back to `lastOpenItem`
— the most recently added block. With N parallel calls, N-1 of them
finalized with arguments `{}` and the agent rejected them with
`path: Invalid input: expected string, received undefined`, while the
trailing call hoarded every delta.
Register function-call and custom-tool-call items under `item.call_id`
as a secondary key alongside `item.id`/`output_index`, and look up by
the same fallback in `output_item.done`. Real OpenAI is unaffected
(distinct keys, both populated). Regression test pins the exact
llama.cpp emission shape.
Fixes#2015
- ToolExecutionComponent was updated to skip append-only treatment for finalized blocks and pass result state into `isStreamingPreviewAppendOnly`.
- Eval rendering was changed to render full code continuously and to report append-only status only once a result exists, avoiding commitment of stale pending previews.
- Live-region tests were added for expanded eval output overflow and for append-only transitions from pending to finalized streaming states.
- Lazy-loaded OTEL SDK, HTML export, TTSR, and autoresearch modules.
- Made resolveMemoryBackend async to import backends on demand.
- Replaced backend resolution with direct settings reads for rekey checks.
- Migrated Effort and THINKING_EFFORTS imports to @oh-my-pi/pi-ai/effort in CLI args and launch command files.
- Split model-registry dependencies across focused @oh-my-pi/pi-ai submodules instead of the root barrel export.
- Added dependency-free `@oh-my-pi/pi-ai/effort` module and re-exported `Effort`/`THINKING_EFFORTS`.
- Moved `Effort` and `THINKING_EFFORTS` from `model-thinking.ts` to `effort.ts`, then migrated runtime/tests imports.
- Deferred setup wizard import until setup was forced or version stale.
- Dynamically loaded ACP, RPC, and print mode runners only when used.
- Added a marketplace auto-update scheduler with off-mode early exit and non-blocking errors.
- Added setup-version assertions to keep CURRENT_SETUP_VERSION aligned with scenes.
- Added a WeakMap cache in compileEquivalenceConfig to reuse compiled configs.
- Raised QUALIFIED_NAMESPACE_SUFFIX_CACHE_CAP and HEURISTIC_CANDIDATES_CACHE_CAP from 256 to 4096.
- Reworked resolveCanonicalIdForModel to build officialMatches during candidate iteration.
- Added `isStreamingPreviewAppendOnly` to `ToolRenderer` for per-tool streaming mode selection.
- Updated `ToolExecutionComponent` to query append-only predicates only while a call preview is streaming.
- Marked expanded write previews as append-only so over-tall streaming output can commit head rows.
- Threaded resolved status-line `segmentOptions` into `#buildSegmentContext` construction.
- Added regression tests for scrollback retention and append-only state transitions.
- Reworked message parsing to process complete frames from a pending chunk queue.
- Added chunk-aware header scanning and range copy to avoid repeated buffer concatenation.
- Persisted partially read bytes in client.messageBuffer during reader teardown.
- Added a per-frame cache for #fitLineToWidth results and cleared it at each #doRender.
- Reused memoized line-fit outputs to skip repeated visibleWidth/truncate work.
- Specified replace N..M ranges as inclusive to prevent accidental boundary truncation.
- Clarified that edits must target only lines actually read, not merely covered by a #TAG.
- Added guidance to keep pure insertions as insert hunks and avoid widening replace ranges.
- Added `repairOrphanResponsesToolCalls` to append placeholder outputs for orphan calls.
- Updated `openai-codex/request-transformer.ts` to repair unmatched tool-call/input pairs.
- Wrapped `openai-responses.ts` and `azure-openai-responses.ts` with orphan call repair before request conversion.
- Added regression coverage for orphan call repair in Codex and Responses tests.
- Added Bun preload that records inclusive per-module windows and resolved static import edges via plugin hooks.
- Shared events through a dependency-free buffer so the preload and logger avoid importing each other.
- Rendered module spans as a body/TLA-ranked dependency tree, separating graph wait from top-level work.
- Back-folded captured load phase into the root window to shrink the opaque pre-instrumentation figure.
- Replaced WeakMap model cache with provider/id string keys for stable reuse.
- Returned official model ids directly when matched, before heuristics.
- Collapsed non-message token path to system prompt and tool schema totals.
- Shared immutable model registries and auth storage via beforeAll/afterAll.
- Swapped fixed-delay settle sleeps for predicate polling and signals.
- Stubbed network/timers to drop wall-clock waits in registry and history tests.
- Added resetDisplay invalidation tests and startup-timing breakdown lines.
- Added a tolerance-aware `compareUsageRankingMetric` helper with finite-value handling.
- Replaced usage provider sorting comparisons with the new comparator for secondary and primary usage metrics.
- Kept existing tie-breaker fields while stabilizing ordering for nearly equal metric values.
- Used `||` so empty stderr falls through to abortReason in agent bridge.
- Preferred assistant errorMessage over "Cancelled by caller" on internal aborts.
- Forced `maxRuntimeMs: 0` for eval subagents via ExecutorOptions override.
- Parsed /actions/runs URLs into run and job render handlers.
- Rendered run metadata with per-job breakdown, showing steps for failed jobs.
- Fetched job logs via API token, stripping ISO timestamp prefixes.
The runtime cutover to Kagi's V1 search API landed in #1272 but
docs/tools/web_search.md still described the sunset V0 endpoint
(GET /api/v0/search with 'Authorization: Bot ...'). Realigned the
Querying and Output bullets with the actual implementation in
packages/coding-agent/src/web/kagi.ts:
- POST https://kagi.com/api/v1/search with Bearer auth and JSON body.
- recency maps to filters.after as a UTC YYYY-MM-DD string.
- Output now includes the categorized bucket merge (search/video/news/
infobox with title tags), adjacent_question + related_search related
questions, direct_answer-derived answer, and meta.trace requestId.
Fixes#2009
Address review feedback:
- Replace `as string` assertion with typed `chosenType` local
- Strip sibling keys from nextSchema that were copied via
copySchemaWithout but belong to a type other than the chosen one
(e.g. sibling `items` on a now-string-typed schema)
- Export ALL_CCA_TYPE_SPECIFIC_KEYS from fields.ts for sibling filtering
- Add regression test for the sibling-key edge case
When collapseMixedTypeCombinerVariants collapses an anyOf with mixed
types (e.g. string | array), it previously picked the first non-null
type but indiscriminately copied ALL mergedVariantFields — including
type-specific keys like "items" that only belong to array. This
produced schemas like {type: "string", items: {...}} which Google
Cloud Code Assist API rejects with 400.
Fix: filter mergedVariantFields against the chosen types allowed keys
(CLOUD_CODE_ASSIST_TYPE_SPECIFIC_KEYS) before copying, so array-only
keys are dropped when the winner is string (and vice versa).
Fixes 400 error on github tools "pr" parameter (anyOf string/array).
- #getUsageReportIdentifiers now only pushes project: when no email was
found, preventing two users with different emails on the same GCP
project from being merged at the usage-report level.
- Antigravity dedup key now uses quotaInfo.windowId directly before
falling back to parseWindow's id. When resetTime is absent but
windowId is set, separate windows no longer collapse to 'default'.
- BLOCKING: reorder resolveProviderCredentialIdentityKey so email
identity takes priority over project — two users with different
emails on the same GCP project no longer get merged/hard-deleted.
- Added #getUsageReportScopeProjectId helper so Gemini CLI reports
(which set projectId on limit.scope but not metadata) still get
dedup coverage. Both metadata and scope projectId paths checked.
- formatAggregateAmount now falls back to limits.length when no
scope.accountId values are present, preserving pre-existing
behaviour for providers that don't set accountId on limits.
- Added 9 contract tests for the antigravity usage merge logic:
tier dedup, worst-fraction-wins, mixed-case collapsing,
reset-time-from-other-entry, windowId separation, metadata,
sort order, and null-on-no-project.
- Nits: label='Usage' (so formatLimitTitle renders 'Usage (Default)'
not bare 'Default'), id uses params.provider instead of hardcoded
string, tier field drops redundant ?? undefined.
Gemini CLI provider stores projectId on limit.scope but not in report
metadata, so the metadata-only projectId fallback added earlier missed
that case. Now all three lookup sites (dedup identifiers, TUI account
label, ACP account label) also check limit.scope.projectId.
When models within the same (tier, windowId) group have complementary
data — some carry remainingFraction but no resetTime, others carry
resetTime but no remainingFraction — merge them so the displayed entry
has both a real bar and the 'resets in…' line.
Also lowercases tier names for dedup keys so 'Default' and 'default'
are recognized as the same tier.
Adds projectId to OAuth credential identity extraction and to the
usage-report dedup identifiers so duplicate credential rows (same
Google Cloud project, separate login sessions) are pruned and merged
at both the store and usage-report levels.
- Antigravity usage provider now deduplicates model quota entries by tier
instead of emitting one bar per model (15+ redundant bars for one account).
The upstream API groups quota by tier — models within the same tier share
the same quota bucket, so per-model bars were misleading noise.
- Reports now carry credential email and accountId in metadata so the
/usage display and deduplicator can show meaningful account identities
instead of 'account 1'.
- formatAggregateAmount no longer uses limits.length as account count.
Instead counts unique accountId values from limit scopes — a single
account's N incomplete limits no longer display as 'N accts'.
- Usage report dedup now considers metadata.projectId for Google Cloud
providers so duplicate credential rows with the same project merge.
- account labels in both TUI and ACP markdown paths now fall back to
metadata.projectId before the generic 'account N' placeholder.
resolveAnthropicBaseUrl defaults to https://api.anthropic.com when
model.baseUrl is absent (e.g. same-id custom overrides that only tweak
metadata), and the existing isAnthropicApiBaseUrl helper already treats
an empty/undefined baseUrl as official. The new isOfficialAnthropicEndpoint
helper classified the same model as non-official, so
shouldReplayUnsignedThinking would replay unsigned thinking as
type: thinking against the first-party API — which rejects it.
Drop the redundant helper and use isAnthropicApiBaseUrl as the single
source of truth. Add a regression test that pins the missing-baseUrl
case to the text fallback.
Anthropic-compatible reasoning providers commonly emit thinking blocks
without first-party Anthropic signatures while still expecting those
blocks back as native thinking on continuation. The previous follow-up
for #2005 fixed Xiaomi by provider/host allowlist, but the protocol
contract is broader and matches the behavior described in #1996.
Replace the Xiaomi-specific branch with a protocol-level rule:
- official api.anthropic.com keeps demoting unsigned thinking to text
- non-official anthropic-messages reasoning models replay unsigned
thinking as type: thinking with an empty signature
- existing known non-signing DeepSeek/Z.AI compatibility remains
The regression test now uses a generic Anthropic-compatible reasoning
endpoint, includes the Xiaomi MiMo reporter configuration only as a
fixture, and guards non-reasoning unknown endpoints plus official
Anthropic behavior.
Fixes#2005
The Anthropic-compat endpoints hosted under *.xiaomimimo.com (every
Xiaomi MiMo Token Plan region plus api.xiaomimimo.com) emit thinking
blocks without a signature. convertAnthropicMessages defaulted to
"signing capable" for any endpoint not explicitly allowlisted as
non-signing, so MiMo's unsigned thinking blocks were demoted to text on
every continuation request. Without its prior reasoning replayed, MiMo
destabilized tool-call argument serialization — the root cause behind
the args?.ops?.map crash already mitigated at the renderer in #2005.
Extend isNonSigningAnthropicEndpoint to cover the xiaomi catalog
provider, every xiaomi-token-plan-* provider id, and any baseUrl on
xiaomimimo.com so the existing non-signing replay branch fires for MiMo
the same way it does for DeepSeek and Z.AI.
Fixes#2005
Python eval agent() collapsed every subagent runtime-limit abort into a
generic 'RuntimeError: bridge call __agent__ failed' instead of the real
reason. runEvalAgent built its failure message with:
result.error ?? result.stderr ?? result.abortReason ?? <default>
? is nullish-coalescing, so result.stderr = "" (the executor's value for
a runtime-limit abort) short-circuited the chain and never reached
abortReason. The host bridge then shipped {ok: false, error: ""}, and
prelude.py's '<msg> or <fallback>' picked the named-bridge fallback.
Extracted buildSubagentFailureMessage(): aborted subagents prefer the
trimmed abortReason; otherwise fall through error, stderr (trimmed),
abortReason, and the named-bridge default. Empty/whitespace strings no
longer mask anything. The failure-detection condition also accepts
result.aborted so an abort with exitCode 0 (theoretically) still flows
the abort reason out.
Added a regression test asserting that runtime-limit aborts, whitespace
stderr/error, and totally blank aborts all produce non-empty messages
matching the executor's abortReason text.
Fixes#2006
- Forced authenticated ask requests to `experimental`, matching the anonymous fallback since the cookie session ignores pro upgrades.
- Kept TUI collapsed search answers full; capping now only applies in compact mode via `maxAnswerLines`.
- Preserved full multiline task pending preview instead of bounding it.
The todo tool's renderCall ran args?.ops?.map(...) directly, which throws
TypeError on any non-array ops value. parseStreamingJson surfaces such
shapes mid-stream: a partial Anthropic input_json_delta buffer like
'{"ops":"[{' becomes { ops: '[{' }, and intermediate states can hand back
null entries before object fields arrive. Each crash spammed Tool
renderer failed warnings and starved the TUI render loop.
Guard against:
- ops being any non-array (string, object, primitive)
- entries being null / non-object
- entry.items being a non-array
The fix is in the TUI renderer only — schema validation in the agent
loop is unchanged, so any genuinely malformed model output still
surfaces an invalid-args tool error to the model.
Fixes#2005