fix: address 8-domain Oracle review findings (C1, C2, M1-M4)

- C1: thinking-prepend unique part IDs per message (global PK collision)
- C2: recover-thinking-disabled-violation try/catch guard on SDK call
- M1: remove non-schema truncated/originalSize fields from SDK interfaces
- M2: messageHasContentFromSDK treats thinking-only messages as non-empty
- M3: syncAllTasksToTodos persists finalTodos + no-id rename dedup guard
- M4: AbortSignal.timeout(30s) on HTTP fetch calls in opencode-http-api

All 2739 tests pass, typecheck clean.
This commit is contained in:
YeonGyu-Kim
2026-02-16 15:23:45 +09:00
parent 106cd5c8b1
commit c2012c6027
10 changed files with 148 additions and 46 deletions
@@ -20,10 +20,15 @@ function messageHasContentFromSDK(message: SDKMessage): boolean {
const parts = message.parts
if (!parts || parts.length === 0) return false
let hasIgnoredParts = false
for (const part of parts) {
const type = part.type
if (!type) continue
if (IGNORE_TYPES.has(type)) continue
if (IGNORE_TYPES.has(type)) {
hasIgnoredParts = true
continue
}
if (type === "text") {
if (part.text?.trim()) return true
@@ -31,9 +36,12 @@ function messageHasContentFromSDK(message: SDKMessage): boolean {
}
if (TOOL_TYPES.has(type)) return true
return true
}
return false
// Messages with only thinking/meta parts are NOT empty — they have content
return hasIgnoredParts
}
function getSdkMessages(response: unknown): SDKMessage[] {
@@ -31,10 +31,15 @@ function messageHasContentFromSDK(message: SDKMessage): boolean {
const parts = message.parts
if (!parts || parts.length === 0) return false
let hasIgnoredParts = false
for (const part of parts) {
const type = part.type
if (!type) continue
if (IGNORE_TYPES.has(type)) continue
if (IGNORE_TYPES.has(type)) {
hasIgnoredParts = true
continue
}
if (type === "text") {
if (part.text?.trim()) return true
@@ -42,9 +47,12 @@ function messageHasContentFromSDK(message: SDKMessage): boolean {
}
if (TOOL_TYPES.has(type)) return true
return true
}
return false
// Messages with only thinking/meta parts are NOT empty — they have content
return hasIgnoredParts
}
async function findEmptyMessageIdsFromSDK(
@@ -24,9 +24,7 @@ interface SDKToolPart {
type: string
callID?: string
tool?: string
state?: { output?: string }
truncated?: boolean
originalSize?: number
state?: { output?: string; time?: { compacted?: number } }
}
interface SDKMessage {
@@ -120,7 +118,7 @@ async function truncateToolOutputsByCallIdFromSDK(
for (const part of msg.parts) {
if (part.type !== "tool" || !part.callID) continue
if (!callIds.has(part.callID)) continue
if (!part.state?.output || part.truncated) continue
if (!part.state?.output || part.state?.time?.compacted) continue
const result = await truncateToolResultAsync(client, sessionID, messageID, part.id, part)
if (result.success) {
@@ -19,8 +19,6 @@ interface SDKToolPart {
error?: string
time?: { start?: number; end?: number; compacted?: number }
}
truncated?: boolean
originalSize?: number
}
interface SDKMessage {
@@ -42,7 +40,7 @@ export async function findToolResultsBySizeFromSDK(
if (!messageID || !msg.parts) continue
for (const part of msg.parts) {
if (part.type === "tool" && part.state?.output && !part.truncated && part.tool) {
if (part.type === "tool" && part.state?.output && !part.state?.time?.compacted && part.tool) {
results.push({
partPath: "",
partId: part.id,
@@ -74,8 +72,6 @@ export async function truncateToolResultAsync(
const updatedPart: Record<string, unknown> = {
...part,
truncated: true,
originalSize,
state: {
...part.state,
output: TRUNCATION_MESSAGE,
@@ -108,7 +104,7 @@ export async function countTruncatedResultsFromSDK(
for (const msg of messages) {
if (!msg.parts) continue
for (const part of msg.parts) {
if (part.truncated === true) count++
if (part.state?.time?.compacted) count++
}
}