Skip to content

refactor: provider-name dispatch + fix agent finalization and rescue crash - #1

Merged
Iris-Ares merged 3 commits into
mainfrom
claude/focused-wozniak
Apr 15, 2026
Merged

refactor: provider-name dispatch + fix agent finalization and rescue crash#1
Iris-Ares merged 3 commits into
mainfrom
claude/focused-wozniak

Conversation

@Iris-Ares

Copy link
Copy Markdown
Owner

Summary

  • Pivot to first-party provider dispatch. The action now takes llm-provider (one of openai | anthropic | deepseek | google | groq | mistral | xai | openrouter | openai-compatible) and uses the matching @ai-sdk/* SDK. Each provider's own package handles structured outputs, tool calling, and auth correctly — no capability shims.
  • Fix the step-budget miss. Added a prepareStep callback that restricts the final step to submit_report only and sets toolChoice: { type: 'tool', toolName: 'submit_report' }. The agent now finalizes reliably within budget.
  • Fix the rescue crash. Dropped the @qraftr/json-repair first pass, the Lenient schema aliases, and normaliseAliases. Rescue is now one Output.object({ schema: VerifyReportSchema }) call — the provider's native structured-output path guarantees the exact Zod shape.
  • Kept @ai-sdk/openai-compatible as the explicit escape hatch for self-hosted vLLM / Moonshot / Qwen, now with supportsStructuredOutputs: true set so it actually works.
  • Fixed .gitignore so dist/index.js ships with the commit (the old dist/ ignore line contradicted the README and the comment right next to it).

Motivation

A prod run of Iris-Ares/pr-lens-github-actions@v0 failed with:

Warning: agent exhausted step budget without calling submit_report; attempting rescue from raw text
AI SDK Warning (byo.chat / ***): { type: "unsupported-setting", setting: "responseFormat",
  details: "JSON response format schema is only supported with structuredOutputs" }
Warning: PR-Lens failed: AI_NoObjectGeneratedError

Two compounding defects: the ToolLoopAgent never called submit_report within the 12-step budget, and the rescue crashed because the generic BYO provider was built without supportsStructuredOutputs — so Output.object({ schema }) silently sent a schema-less request.

Breaking change

Existing workflows must update:

 - uses: Iris-Ares/pr-lens-github-actions@v0
   with:
-    llm-base-url: https://api.openai.com/v1
+    llm-provider: openai
     llm-api-key:  ${{ secrets.LLM_API_KEY }}
-    llm-model:    gpt-4o
+    llm-model:    gpt-4.1

For self-hosted or non-first-party endpoints (Moonshot, local vLLM, Ollama):

with:
  llm-provider: openai-compatible
  llm-base-url: https://api.moonshot.cn/v1
  llm-api-key:  ${{ secrets.LLM_API_KEY }}
  llm-model:    kimi-k2.5

README has per-provider examples and a model-id table.

Test plan

  • bun run typecheck — clean
  • bun test — 47/47 pass
  • bun run build — 376 modules, dist/index.js = 1.68 MB
  • Bundle import smoke — loadConfig reaches the expected required-input error
  • End-to-end live run against sindresorhus/ky#853 via Moonshot kimi-k2.5 (openai-compatible branch):
    • No responseFormat warning
    • 7 steps, submittedViaTool: true (well under the 12-step budget)
    • 15,388 tokens, valid 5-item checklist rendered
  • CI self-test runs on this PR with an updated LLM_PROVIDER var (needs repo secret/var update before merging)
  • Verify the new prepareStep force-finalization by running with STEP_BUDGET=2 (too small to finish) and confirming the single-call rescue completes cleanly

Files touched

  • action.yml, src/config.ts — new llm-provider input + conditional base-URL validation
  • src/ai/provider.ts — provider dispatcher (9 branches)
  • src/ai/agent.tsprepareStep + simplified rescue + V2→V3 type bump
  • src/run.ts — pass llmProvider into buildModel
  • package.json, bun.lock — add 8 new provider packages, drop @qraftr/json-repair
  • README.md, .env.example, scripts/local-run.ts, .github/workflows/self-test.yml — docs + local tooling for new input shape
  • .gitignore — un-ignore dist/
  • dist/index.js — rebuilt bundle

🤖 Generated with Claude Code

Iris-Ares and others added 3 commits April 14, 2026 19:06
…tion and rescue crash

A production run of PR-Lens failed with two compounding defects:

1. The ToolLoopAgent exhausted its 12-step budget without calling submit_report.
2. The rescue path then crashed with AI_NoObjectGeneratedError because the
   generic @ai-sdk/openai-compatible provider was created without
   supportsStructuredOutputs, so `Output.object({ schema })` silently sent the
   schema-less response_format and the model returned text that didn't parse.

Rather than paper over (2) with a capability flag and keep the generic BYO
wrapper for every provider, this change pivots to provider-name dispatch:
users pass llm-provider = openai | anthropic | deepseek | google | groq |
mistral | xai | openrouter | openai-compatible, and each branch uses the
respective first-party @ai-sdk/* package which already knows its own
capability shape.

Other fixes in the same pass:
- Add prepareStep to force `submit_report` on the final allowed step, so
  stopWhen: hasToolCall('submit_report') fires by construction and the rescue
  becomes a true safety net rather than a normal fallback.
- Simplify rescue to a single generateText + Output.object call against
  VerifyReportSchema directly. Delete the repairJson / Lenient-schema / alias-
  normalisation layers and the @qraftr/json-repair dependency — structured
  outputs guarantee the exact Zod shape.
- Keep @ai-sdk/openai-compatible as the explicit escape hatch
  (llm-provider: openai-compatible) with supportsStructuredOutputs + includeUsage.
- Bump LanguageModelV2 → LanguageModelV3 everywhere; all provider SDKs at v3.x
  implement V3.
- Fix .gitignore so dist/ actually ships (previous ignore line contradicted the
  comment right below it and the README's stated intent).

Breaking change: existing workflows must add `llm-provider:` and drop
`llm-base-url:` unless they're using an OpenAI-compatible self-hosted endpoint.
README and self-test workflow updated accordingly.

Smoke-tested end-to-end against sindresorhus/ky#853 via Moonshot kimi-k2.5
(openai-compatible branch): 7 steps, submittedViaTool=true, 15k tokens, no
warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previous commit mistakenly committed dist/index.js and un-ignored dist/.
release.yml's header comment clearly states dist/ is gitignored on main and
built+committed onto the tag commit at release time — that's the actual
design. CI's `git diff --exit-code dist/` then kept failing on PRs because
`bun run build --minify` output is not byte-stable across Bun versions (my
local canary produced one bundle, CI's bun@latest produced another).

Fixes:
- Restore `dist/` to .gitignore; clarify the comment so nobody repeats my
  mistake. `git rm --cached dist/index.js` to untrack the accidental commit.
- Pin CI and Release workflows to `bun-version: 1.3.12` so the bundle that
  ships at a tag matches what future CI would validate on PRs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Self-test was referencing `uses: ./` without dist/index.js present on the PR
branch (dist is gitignored on main — only built at release-tag time). Add a
bun install + bun run build step so the action bundle exists when the action
step runs. Uses the same pinned Bun version as ci.yml and release.yml.

This bug was latent in the initial commit but hadn't been hit before because
there were no PRs against the repo yet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Iris-Ares
Iris-Ares merged commit 5dcf049 into main Apr 15, 2026
2 checks passed
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.

1 participant