MoonshotAI / MoonshotAI/kimi-code

fix(acp-server): surface provider errors on session/prompt

Open
#3,107 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
7.5k
Forks
1.2k
Avg merge
11h 53m
Merged PRs (30d)
350

Description

Problem

When the LLM provider returns a 4xx/5xx/rate-limit/overload/connection-error, the ACP session/prompt call resolves with { stopReason: 'end_turn' } and no JSON-RPC error. The custom ACP client (currently the JetBrains plugin) sees a normal-looking "turn ended, no content" outcome and cannot tell that the model service was actually unavailable.

Repro
  1. Point the provider config at a custom OpenAI-compatible gateway that returns 500 on a chat-completion request.
  2. Run any turn that needs an LLM call.
  3. Observe the ACP PromptResponse: { stopReason: 'end_turn', usage: {...} }. No error. No error code. No retry guidance.
Where the silent resolution happens

packages/acp-server/src/session.ts:907 onTurnEnded:

private onTurnEnded(event): void {
  const driver = this.driverFor(event.turnId);
  if (driver === undefined) return;
  const error = event.error as { code: string; message?: string } | undefined;
  this.settleDriver(driver, () => {
    if (event.reason === 'failed' && isAuthError(error)) {
      driver.reject(RequestError.authRequired(undefined, error?.message));
      return;
    }
    driver.resolve({ stopReason: turnEndReasonToStopReason(event.reason, error) });
  });
  ...
}

The isAuthError(error) check only matches the auth code set (provider.auth_error + auth.*). Anything else with reason: 'failed' falls through to turnEndReasonToStopReason, which only maps provider.filtered to refusal; everything else → end_turn.

Engine side (unchanged)

packages/agent-core-v2/src/kosong/contract/errors.ts already classifies errors as provider.api_error / provider.overloaded / provider.connection_error / provider.rate_limit / context.overflow etc. (born-coded). The loop layer surfaces these as turn.ended { reason: 'failed', error: Error2{ code } }. The classification is correct; the ACP layer just discards it.

Proposed fix

Add a sibling helper to isAuthError in packages/acp-server/src/events-map.ts:

const PROVIDER_ERROR_CODES: ReadonlySet<string> = new Set([
  'provider.api_error',
  'provider.filtered',
  'provider.rate_limit',
  'provider.connection_error',
  'provider.overloaded',
  'provider.not_found',
  'context.overflow',
  'loop.max_steps_exceeded',
]);

export function isProviderError(error?: { readonly code: string }): boolean {
  return error !== undefined && PROVIDER_ERROR_CODES.has(error.code);
}

Extend onTurnEnded in session.ts:

if (event.reason === 'failed' && isAuthError(error)) {
  driver.reject(RequestError.authRequired(undefined, error?.message));
  return;
}
if (event.reason === 'failed' && isProviderError(error)) {
  driver.reject(RequestError.internalError(
    { code: error?.code, message: error?.message, name: error?.name },
    error?.message ?? 'model provider reported an error',
  ));
  return;
}

Apply the same logic in mapPromptLaunchError (session.ts:118) so a launch-time failure that already has a provider code surfaces as internalError rather than the fixed 'session prompt failed' text.

Scope note

This change only affects the ACP server. VS Code plugin, TUI, and kap-server all consume the engine directly and are unaffected.

Test plan
  • packages/acp-server/test/convert.test.ts (or a small new events-map.test.ts if cleaner): unit-test isProviderError against every code in the set and against codes outside it.
  • packages/acp-server/test/approval.test.ts / interaction-bridge.test.ts: verify mapPromptLaunchError returns RequestError.internalError when the launch error has a provider.* / context.* code, and continues to return 'session prompt failed' otherwise.
  • packages/acp-server/test/e2e-turn.test.ts: scripted provider throws a provider.api_error mid-turn; verify the client receives a JSON-RPC error (not a successful end_turn); auth path still produces auth_required; unrelated failures still produce end_turn.
Risk

Behaviour change for any external ACP client that today treats a provider outage as "agent produced nothing". Such clients will now see a JSON-RPC error — strictly more informative, no semantic ambiguity, no protocol breakage. Mitigate by announcing the change in apps/kimi-code/CHANGELOG.md.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with isAuthError and the proposed provider-code handling in packages/acp-server/src/events-map.ts and packages/acp-server/src/session.ts, including onTurnEnded and mapPromptLaunchError. Run the named ACP unit and end-to-end tests, then verify provider failures become JSON-RPC errors while auth and unrelated failures retain their existing behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.