fix(coding): recover provider context failures
This commit is contained in:
@@ -9,7 +9,10 @@ import {
|
||||
import { proxyAwareFetch } from '../../utils/proxy-fetch';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { getAIGatewayErrorKind } from '../../../shared/ai-gateway-error-kind';
|
||||
import { isAIGatewayUpstreamSaturated } from '../../../shared/ai-gateway-error-details';
|
||||
import {
|
||||
isAIGatewayUpstreamSaturated,
|
||||
isAIGatewayUserContextMissing,
|
||||
} from '../../../shared/ai-gateway-error-details';
|
||||
|
||||
const AI_PROXY_PREFIX = '/api/ai-proxy/v1';
|
||||
const MAX_ERROR_LOG_MESSAGE_LENGTH = 600;
|
||||
@@ -80,6 +83,9 @@ function isExpiredGatewayTokenResponse(status: number, bodyText: string): boolea
|
||||
}
|
||||
|
||||
function getForwardedOneApiStatus(status: number, bodyText: string): number {
|
||||
if (status === 502 && isAIGatewayUserContextMissing(bodyText)) {
|
||||
return 401;
|
||||
}
|
||||
if (status === 429 && getAIGatewayErrorKind(bodyText) === 'quota_exhausted') {
|
||||
return 402;
|
||||
}
|
||||
@@ -282,6 +288,9 @@ export async function handleAiProxyRoutes(
|
||||
upstream = upstreamResult.response;
|
||||
targetUrl = upstreamResult.targetUrl;
|
||||
} else {
|
||||
if (isAIGatewayUserContextMissing(bodyText)) {
|
||||
markWorksSquareAIGatewayCredentialExpired();
|
||||
}
|
||||
logNonSuccessOneApiResponseFromBody(upstream, targetUrl, bodyText);
|
||||
await writeBufferedResponse(res, upstream, bodyText, getForwardedOneApiStatus(upstream.status, bodyText));
|
||||
return true;
|
||||
@@ -290,6 +299,9 @@ export async function handleAiProxyRoutes(
|
||||
|
||||
if (!upstream.ok) {
|
||||
const bodyText = await upstream.text();
|
||||
if (isAIGatewayUserContextMissing(bodyText)) {
|
||||
markWorksSquareAIGatewayCredentialExpired();
|
||||
}
|
||||
logNonSuccessOneApiResponseFromBody(upstream, targetUrl, bodyText);
|
||||
await writeBufferedResponse(res, upstream, bodyText, getForwardedOneApiStatus(upstream.status, bodyText));
|
||||
return true;
|
||||
|
||||
@@ -443,13 +443,13 @@ export class CodingConversationService {
|
||||
try {
|
||||
selected = await this.runtime.validateModel(model);
|
||||
} catch (error) { runtimeError(error); }
|
||||
await this.ensurePrepared(conversationId);
|
||||
let snapshot: ConversationSnapshot;
|
||||
try {
|
||||
snapshot = await this.runtime.getSnapshot(conversationId);
|
||||
} catch (error) { runtimeError(error); }
|
||||
this.assertSnapshotAllowsMutation(snapshot);
|
||||
if (conversation.modelResolution === 'resolved' && conversation.model) {
|
||||
await this.ensurePrepared(conversationId);
|
||||
let snapshot: ConversationSnapshot;
|
||||
try {
|
||||
snapshot = await this.runtime.getSnapshot(conversationId);
|
||||
} catch (error) { runtimeError(error); }
|
||||
this.assertSnapshotAllowsMutation(snapshot);
|
||||
let state: ConversationModelState;
|
||||
try {
|
||||
state = await this.runtime.setModel({
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
ConversationToolNode,
|
||||
PublicUsage,
|
||||
} from '../contracts';
|
||||
import { isAIGatewayUserContextMissing } from '../../../shared/ai-gateway-error-details';
|
||||
import type { PiRpcEvent } from './rpc-client';
|
||||
import { subagentDetailsOfResult } from '../subagent-protocol';
|
||||
import {
|
||||
@@ -44,6 +45,17 @@ function retryFailure(message: string) {
|
||||
};
|
||||
}
|
||||
|
||||
function providerFailure(message: unknown) {
|
||||
if (typeof message === 'string' && isAIGatewayUserContextMissing(message)) {
|
||||
return {
|
||||
code: 'CODING_PROVIDER_AUTH_REQUIRED' as const,
|
||||
message: '模型服务身份上下文无效,请重试;若仍失败请重新登录。',
|
||||
recoverable: true,
|
||||
};
|
||||
}
|
||||
return retryFailure('模型服务请求失败,请稍后重试。');
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
||||
? value as Record<string, unknown>
|
||||
@@ -282,7 +294,7 @@ export class PiEventProjector {
|
||||
...run,
|
||||
status: 'error',
|
||||
terminalReason: 'failed',
|
||||
error: retryFailure('The local Agent retry failed'),
|
||||
error: providerFailure(event.finalError),
|
||||
},
|
||||
}];
|
||||
}
|
||||
@@ -459,6 +471,25 @@ export class PiEventProjector {
|
||||
];
|
||||
}
|
||||
|
||||
if (event.type === 'agent_end' && event.willRetry === false && Array.isArray(event.messages)) {
|
||||
const assistant = [...event.messages]
|
||||
.reverse()
|
||||
.map(asRecord)
|
||||
.find((message) => message?.role === 'assistant');
|
||||
if (assistant?.stopReason === 'error') {
|
||||
return [{
|
||||
op: 'run.state',
|
||||
run: {
|
||||
...snapshot.run,
|
||||
status: 'error',
|
||||
terminalReason: 'failed',
|
||||
error: providerFailure(assistant.errorMessage),
|
||||
},
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
if (event.type === 'extension_ui_request'
|
||||
&& typeof event.id === 'string'
|
||||
&& typeof event.method === 'string'
|
||||
|
||||
Reference in New Issue
Block a user