Skip to content

Commit a85d143

Browse files
kulvirgitclaude
andcommitted
fix: resolve all test failures from fork restructure
Source code fixes: - Add null guard to `NamedError.isInstance()` to handle null input - Add Azure OpenAI overflow detection patterns to `ProviderError` - Implement ACP agent synthetic pending events, bash output snapshot streaming with de-duplication Test fixes: - Update agent tests: `build` → `builder` + add custom agent expectations (analyst, executive, validator, migrator) - Update test import paths for relocated modules (`bridge/client`, `bridge/engine`, `tools/project-scan`) - Update install tests for fork package naming (`@altimateai/altimate-code-`) - Fix token estimate test expectations for 3.7 chars/token ratio - Fix compaction config filename `altimate-code.json` → `opencode.json` Result: 35 failures → 0 failures, 1554 tests pass. Co-Authored-By: Kai (Claude Opus 4.6) <noreply@anthropic.com>
1 parent 980efaa commit a85d143

16 files changed

Lines changed: 249 additions & 121 deletions

packages/opencode/src/acp/agent.ts

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ export namespace ACP {
136136
private eventAbort = new AbortController()
137137
private eventStarted = false
138138
private permissionQueues = new Map<string, Promise<void>>()
139+
private seenTools = new Set<string>()
140+
private bashSnapshots = new Map<string, string>()
139141
private permissionOptions: PermissionOption[] = [
140142
{ optionId: "once", kind: "allow_once", name: "Allow once" },
141143
{ optionId: "always", kind: "allow_always", name: "Always allow" },
@@ -288,6 +290,8 @@ export namespace ACP {
288290
if (part.type === "tool") {
289291
switch (part.state.status) {
290292
case "pending":
293+
this.seenTools.add(part.callID)
294+
this.bashSnapshots.delete(part.callID)
291295
await this.connection
292296
.sessionUpdate({
293297
sessionId,
@@ -306,7 +310,62 @@ export namespace ACP {
306310
})
307311
return
308312

309-
case "running":
313+
case "running": {
314+
// Emit synthetic pending if we haven't seen this tool yet
315+
if (!this.seenTools.has(part.callID)) {
316+
this.seenTools.add(part.callID)
317+
await this.connection
318+
.sessionUpdate({
319+
sessionId,
320+
update: {
321+
sessionUpdate: "tool_call",
322+
toolCallId: part.callID,
323+
title: part.tool,
324+
kind: toToolKind(part.tool),
325+
status: "pending",
326+
locations: [],
327+
rawInput: {},
328+
},
329+
})
330+
.catch((error) => {
331+
log.error("failed to send synthetic tool pending to ACP", { error })
332+
})
333+
}
334+
335+
// Extract bash output from metadata for streaming
336+
const metadata = part.state.metadata as Record<string, unknown> | undefined
337+
const bashOutput = metadata && typeof metadata.output === "string" ? metadata.output : undefined
338+
const content: ToolCallContent[] = []
339+
340+
if (bashOutput !== undefined) {
341+
const lastSnapshot = this.bashSnapshots.get(part.callID)
342+
if (lastSnapshot === bashOutput) {
343+
// De-duplicate: identical to last snapshot, send update without content
344+
await this.connection
345+
.sessionUpdate({
346+
sessionId,
347+
update: {
348+
sessionUpdate: "tool_call_update",
349+
toolCallId: part.callID,
350+
status: "in_progress",
351+
kind: toToolKind(part.tool),
352+
title: part.tool,
353+
locations: toLocations(part.tool, part.state.input),
354+
rawInput: part.state.input,
355+
},
356+
})
357+
.catch((error) => {
358+
log.error("failed to send tool in_progress to ACP", { error })
359+
})
360+
return
361+
}
362+
this.bashSnapshots.set(part.callID, bashOutput)
363+
content.push({
364+
type: "content",
365+
content: { type: "text", text: bashOutput },
366+
})
367+
}
368+
310369
await this.connection
311370
.sessionUpdate({
312371
sessionId,
@@ -318,12 +377,14 @@ export namespace ACP {
318377
title: part.tool,
319378
locations: toLocations(part.tool, part.state.input),
320379
rawInput: part.state.input,
380+
...(content.length > 0 && { content }),
321381
},
322382
})
323383
.catch((error) => {
324384
log.error("failed to send tool in_progress to ACP", { error })
325385
})
326386
return
387+
}
327388

328389
case "completed": {
329390
const kind = toToolKind(part.tool)
@@ -802,6 +863,8 @@ export namespace ACP {
802863
if (part.type === "tool") {
803864
switch (part.state.status) {
804865
case "pending":
866+
this.seenTools.add(part.callID)
867+
this.bashSnapshots.delete(part.callID)
805868
await this.connection
806869
.sessionUpdate({
807870
sessionId,
@@ -819,7 +882,60 @@ export namespace ACP {
819882
log.error("failed to send tool pending to ACP", { error: err })
820883
})
821884
break
822-
case "running":
885+
case "running": {
886+
// Emit synthetic pending if we haven't seen this tool yet
887+
if (!this.seenTools.has(part.callID)) {
888+
this.seenTools.add(part.callID)
889+
await this.connection
890+
.sessionUpdate({
891+
sessionId,
892+
update: {
893+
sessionUpdate: "tool_call",
894+
toolCallId: part.callID,
895+
title: part.tool,
896+
kind: toToolKind(part.tool),
897+
status: "pending",
898+
locations: [],
899+
rawInput: {},
900+
},
901+
})
902+
.catch((err) => {
903+
log.error("failed to send synthetic tool pending to ACP", { error: err })
904+
})
905+
}
906+
907+
const metadata = part.state.metadata as Record<string, unknown> | undefined
908+
const bashOutput = metadata && typeof metadata.output === "string" ? metadata.output : undefined
909+
const content: ToolCallContent[] = []
910+
911+
if (bashOutput !== undefined) {
912+
const lastSnapshot = this.bashSnapshots.get(part.callID)
913+
if (lastSnapshot === bashOutput) {
914+
await this.connection
915+
.sessionUpdate({
916+
sessionId,
917+
update: {
918+
sessionUpdate: "tool_call_update",
919+
toolCallId: part.callID,
920+
status: "in_progress",
921+
kind: toToolKind(part.tool),
922+
title: part.tool,
923+
locations: toLocations(part.tool, part.state.input),
924+
rawInput: part.state.input,
925+
},
926+
})
927+
.catch((err) => {
928+
log.error("failed to send tool in_progress to ACP", { error: err })
929+
})
930+
break
931+
}
932+
this.bashSnapshots.set(part.callID, bashOutput)
933+
content.push({
934+
type: "content",
935+
content: { type: "text", text: bashOutput },
936+
})
937+
}
938+
823939
await this.connection
824940
.sessionUpdate({
825941
sessionId,
@@ -831,12 +947,14 @@ export namespace ACP {
831947
title: part.tool,
832948
locations: toLocations(part.tool, part.state.input),
833949
rawInput: part.state.input,
950+
...(content.length > 0 && { content }),
834951
},
835952
})
836953
.catch((err) => {
837954
log.error("failed to send tool in_progress to ACP", { error: err })
838955
})
839956
break
957+
}
840958
case "completed":
841959
const kind = toToolKind(part.tool)
842960
const content: ToolCallContent[] = [

packages/opencode/src/provider/error.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export namespace ProviderError {
2020
/exceeded model token limit/i, // Kimi For Coding, Moonshot
2121
/context[_ ]length[_ ]exceeded/i, // Generic fallback
2222
/request entity too large/i, // HTTP 413
23+
/the request was too long/i, // Azure OpenAI
24+
/maximum tokens for requested operation/i, // Azure OpenAI
2325
]
2426

2527
function isOpenAiErrorRetryable(e: APICallError) {

packages/opencode/test/acp/event-subscription.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,8 @@ function createFakeAgent() {
224224
return {
225225
data: [
226226
{
227-
name: "build",
228-
description: "build",
227+
name: "builder",
228+
description: "builder",
229229
mode: "agent",
230230
},
231231
],

0 commit comments

Comments
 (0)