v2.2-paseo-providers: Paseo provider stack + v2.2.1 pane-scoped chat fixes
Ship Paseo-equivalent provider snapshot, AgentComposerBar, ACP dispatch rewrite with streaming/persist, permission prompts, and agent commands. Follow-up: pane-scoped chat resolution, CoderMessageList tool timeline, WS user-delta replace, and inference orphan tool_call stripping. Archive openspec v2-2; update CHANGELOG and CURRENT. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -37,6 +37,34 @@ export interface OpenAiMessage {
|
||||
// omit it and exercise the byte-stability surface directly through
|
||||
// buildSystemPromptWithFingerprint. The observer Map in system-prompt.ts
|
||||
// updates regardless of whether log is passed.
|
||||
function toolResultIdsFollowing(history: Message[], assistantIdx: number): Set<string> {
|
||||
const ids = new Set<string>();
|
||||
for (let j = assistantIdx + 1; j < history.length; j++) {
|
||||
const row = history[j]!;
|
||||
if (row.role === 'user' || row.role === 'assistant') break;
|
||||
if (row.role === 'tool' && row.tool_results?.tool_call_id) {
|
||||
ids.add(row.tool_results.tool_call_id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
function findAssistantOwnerForToolCall(history: Message[], toolIdx: number, callId: string): number | null {
|
||||
for (let k = toolIdx - 1; k >= 0; k--) {
|
||||
const row = history[k]!;
|
||||
if (row.role === 'user') break;
|
||||
if (row.role === 'assistant' && row.tool_calls?.some((tc) => tc.id === callId)) return k;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function assistantToolCallsArePayloadComplete(history: Message[], assistantIdx: number): boolean {
|
||||
const assistant = history[assistantIdx]!;
|
||||
if (!assistant.tool_calls?.length) return false;
|
||||
const fulfilled = toolResultIdsFollowing(history, assistantIdx);
|
||||
return assistant.tool_calls.every((tc) => fulfilled.has(tc.id));
|
||||
}
|
||||
|
||||
export async function buildMessagesPayload(
|
||||
session: Session,
|
||||
project: Project,
|
||||
@@ -97,6 +125,10 @@ export async function buildMessagesPayload(
|
||||
if (m.role === 'tool') {
|
||||
const tr = m.tool_results;
|
||||
if (!tr) continue;
|
||||
const ownerIdx = findAssistantOwnerForToolCall(history, i, tr.tool_call_id);
|
||||
if (ownerIdx == null || !assistantToolCallsArePayloadComplete(history, ownerIdx)) {
|
||||
continue;
|
||||
}
|
||||
const outputText = tr.error
|
||||
? `error: ${tr.error}`
|
||||
: typeof tr.output === 'string'
|
||||
@@ -115,11 +147,15 @@ export async function buildMessagesPayload(
|
||||
content: m.content && m.content.length > 0 ? m.content : null,
|
||||
};
|
||||
if (m.tool_calls && m.tool_calls.length > 0) {
|
||||
msg.tool_calls = m.tool_calls.map((tc) => ({
|
||||
id: tc.id,
|
||||
type: 'function' as const,
|
||||
function: { name: tc.name, arguments: JSON.stringify(tc.args) },
|
||||
}));
|
||||
if (assistantToolCallsArePayloadComplete(history, i)) {
|
||||
msg.tool_calls = m.tool_calls.map((tc) => ({
|
||||
id: tc.id,
|
||||
type: 'function' as const,
|
||||
function: { name: tc.name, arguments: JSON.stringify(tc.args) },
|
||||
}));
|
||||
}
|
||||
// Orphaned tool_calls (no matching tool rows) are stripped so the
|
||||
// upstream API does not reject the payload on the next user turn.
|
||||
}
|
||||
// v1.13.1-C: collapse reasoning_parts into a single string. The view
|
||||
// returns them ordered by sequence; multiple reasoning parts on one
|
||||
@@ -127,6 +163,11 @@ export async function buildMessagesPayload(
|
||||
if (m.reasoning_parts && m.reasoning_parts.length > 0) {
|
||||
msg.reasoning = m.reasoning_parts.map((p) => p.text ?? '').join('');
|
||||
}
|
||||
const hasPayload =
|
||||
(msg.content != null && msg.content.trim().length > 0) ||
|
||||
(msg.tool_calls != null && msg.tool_calls.length > 0) ||
|
||||
(msg.reasoning != null && msg.reasoning.length > 0);
|
||||
if (!hasPayload) continue;
|
||||
out.push(msg);
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user