Merge branch 'codex/20260831-fix-history-quota-errors-6c2a91e4-fix-history-quota-errors-6c2a91e4'
This commit is contained in:
@@ -6,8 +6,8 @@ import type {
|
||||
ConversationToolNode,
|
||||
PublicUsage,
|
||||
} from '../contracts';
|
||||
import { isAIGatewayUserContextMissing } from '../../../shared/ai-gateway-error-details';
|
||||
import type { PiRpcEvent } from './rpc-client';
|
||||
import { projectPiProviderFailure } from './provider-failure';
|
||||
import { subagentDetailsOfResult } from '../subagent-protocol';
|
||||
import {
|
||||
isProductToolName,
|
||||
@@ -45,17 +45,6 @@ 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>
|
||||
@@ -294,7 +283,7 @@ export class PiEventProjector {
|
||||
...run,
|
||||
status: 'error',
|
||||
terminalReason: 'failed',
|
||||
error: providerFailure(event.finalError),
|
||||
error: projectPiProviderFailure(event.finalError),
|
||||
},
|
||||
}];
|
||||
}
|
||||
@@ -485,7 +474,7 @@ export class PiEventProjector {
|
||||
...snapshot.run,
|
||||
status: 'error',
|
||||
terminalReason: 'failed',
|
||||
error: providerFailure(assistant.errorMessage),
|
||||
error: projectPiProviderFailure(assistant.errorMessage),
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
25
electron/coding-runtime/pi/provider-failure.ts
Normal file
25
electron/coding-runtime/pi/provider-failure.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { CodingRuntimePublicError } from '../contracts';
|
||||
import { isAIGatewayUserContextMissing } from '../../../shared/ai-gateway-error-details';
|
||||
import { getAIGatewayErrorKind } from '../../../shared/ai-gateway-error-kind';
|
||||
|
||||
export function projectPiProviderFailure(message: unknown): CodingRuntimePublicError {
|
||||
if (typeof message === 'string' && isAIGatewayUserContextMissing(message)) {
|
||||
return {
|
||||
code: 'CODING_PROVIDER_AUTH_REQUIRED',
|
||||
message: '模型服务身份上下文无效,请重试;若仍失败请重新登录。',
|
||||
recoverable: true,
|
||||
};
|
||||
}
|
||||
if (typeof message === 'string' && getAIGatewayErrorKind(message) === 'quota_exhausted') {
|
||||
return {
|
||||
code: 'CODING_PROVIDER_QUOTA_EXHAUSTED',
|
||||
message: '词元点数余额不足,请充值后重试。',
|
||||
recoverable: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
code: 'CODING_RUNTIME_START_FAILED',
|
||||
message: '模型服务请求失败,请稍后重试。',
|
||||
recoverable: true,
|
||||
};
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
} from './event-projector';
|
||||
import { isProductToolName, productToolDetailsOfResult } from '../product-tool-protocol';
|
||||
import { subagentDetailsOfResult } from '../subagent-protocol';
|
||||
import { projectPiProviderFailure } from './provider-failure';
|
||||
|
||||
export interface PiSessionSnapshotInput {
|
||||
snapshot: ConversationSnapshot;
|
||||
@@ -106,23 +107,6 @@ function activePath(entriesValue: unknown, leafId: unknown): SessionEntryRecord[
|
||||
return path.reverse();
|
||||
}
|
||||
|
||||
function retainedTail(path: SessionEntryRecord[]): SessionEntryRecord[] {
|
||||
const compactionIndex = path.findLastIndex((entry) => entry.type === 'compaction');
|
||||
if (compactionIndex < 0) return path;
|
||||
const compaction = path[compactionIndex];
|
||||
const firstKeptEntryId = compaction.firstKeptEntryId;
|
||||
if (typeof firstKeptEntryId !== 'string') unreadable();
|
||||
const firstKeptIndex = path.findIndex(
|
||||
(entry, index) => index < compactionIndex && entry.id === firstKeptEntryId,
|
||||
);
|
||||
if (firstKeptIndex < 0) unreadable();
|
||||
return [
|
||||
compaction,
|
||||
...path.slice(firstKeptIndex, compactionIndex),
|
||||
...path.slice(compactionIndex + 1),
|
||||
];
|
||||
}
|
||||
|
||||
async function contentBlocks(
|
||||
messageId: string,
|
||||
content: unknown,
|
||||
@@ -265,6 +249,19 @@ async function projectEntries(
|
||||
...(stopReason ? { stopReason } : {}),
|
||||
};
|
||||
nodes.push(node);
|
||||
if (message.stopReason === 'error') {
|
||||
const failure = projectPiProviderFailure(message.errorMessage);
|
||||
if (failure.code === 'CODING_PROVIDER_AUTH_REQUIRED'
|
||||
|| failure.code === 'CODING_PROVIDER_QUOTA_EXHAUSTED') {
|
||||
nodes.push({
|
||||
kind: 'notice',
|
||||
id: `${messageId}:provider-error`,
|
||||
code: failure.code,
|
||||
level: 'error',
|
||||
message: failure.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(message.content)) continue;
|
||||
for (const value of message.content) {
|
||||
const block = asRecord(value);
|
||||
@@ -310,6 +307,16 @@ function reconcileLiveIds(
|
||||
liveNodes: ConversationNode[],
|
||||
): ConversationNode[] {
|
||||
const used = new Set<string>();
|
||||
const durableCompactions = durableNodes.filter((node) => node.kind === 'compaction');
|
||||
const liveCompactions = liveNodes.filter((node) => node.kind === 'compaction');
|
||||
const compactionMatches = new Map<string, Extract<ConversationNode, { kind: 'compaction' }>>();
|
||||
for (
|
||||
let durableIndex = durableCompactions.length - 1, liveIndex = liveCompactions.length - 1;
|
||||
durableIndex >= 0 && liveIndex >= 0;
|
||||
durableIndex -= 1, liveIndex -= 1
|
||||
) {
|
||||
compactionMatches.set(durableCompactions[durableIndex].id, liveCompactions[liveIndex]);
|
||||
}
|
||||
return durableNodes.map((node) => {
|
||||
if (node.kind === 'message') {
|
||||
const live = liveNodes.find((candidate) => candidate.kind === 'message'
|
||||
@@ -338,10 +345,8 @@ function reconcileLiveIds(
|
||||
};
|
||||
}
|
||||
if (node.kind === 'compaction') {
|
||||
const live = liveNodes.findLast(
|
||||
(candidate) => candidate.kind === 'compaction' && !used.has(candidate.id),
|
||||
);
|
||||
if (!live || live.kind !== 'compaction') return node;
|
||||
const live = compactionMatches.get(node.id);
|
||||
if (!live) return node;
|
||||
used.add(live.id);
|
||||
return { ...node, id: live.id, runId: live.runId };
|
||||
}
|
||||
@@ -375,7 +380,7 @@ export async function projectPiSessionSnapshot(
|
||||
const response = asRecord(input.entries);
|
||||
const state = asRecord(input.state);
|
||||
if (!response || !state) unreadable();
|
||||
const path = retainedTail(activePath(response.entries, response.leafId));
|
||||
const path = activePath(response.entries, response.leafId);
|
||||
const durableNodes = await projectEntries(path, input);
|
||||
const nodes = reconcileLiveIds(durableNodes, input.snapshot.nodes);
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user