refactor: create normalizeSDKResponse helper and replace scattered patterns across 37 files
This commit is contained in:
@@ -6,7 +6,7 @@ import type {
|
||||
ResumeInput,
|
||||
} from "./types"
|
||||
import { TaskHistory } from "./task-history"
|
||||
import { log, getAgentToolRestrictions, promptWithModelSuggestionRetry } from "../../shared"
|
||||
import { log, getAgentToolRestrictions, normalizeSDKResponse, promptWithModelSuggestionRetry } from "../../shared"
|
||||
import { setSessionTools } from "../../shared/session-tools-store"
|
||||
import { ConcurrencyManager } from "./concurrency"
|
||||
import type { BackgroundTaskConfig, TmuxConfig } from "../../config/schema"
|
||||
@@ -651,7 +651,7 @@ export class BackgroundManager {
|
||||
const response = await this.client.session.todo({
|
||||
path: { id: sessionID },
|
||||
})
|
||||
const todos = (response.data ?? response) as Todo[]
|
||||
const todos = normalizeSDKResponse(response, [] as Todo[], { preferResponseOnMissingData: true })
|
||||
if (!todos || todos.length === 0) return false
|
||||
|
||||
const incomplete = todos.filter(
|
||||
@@ -875,7 +875,7 @@ export class BackgroundManager {
|
||||
path: { id: sessionID },
|
||||
})
|
||||
|
||||
const messages = ((response.data ?? response) as unknown as Array<{ info?: { role?: string } }>) ?? []
|
||||
const messages = normalizeSDKResponse(response, [] as Array<{ info?: { role?: string } }>, { preferResponseOnMissingData: true })
|
||||
|
||||
// Check for at least one assistant or tool message
|
||||
const hasAssistantOrToolMessage = messages.some(
|
||||
@@ -1244,9 +1244,9 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
||||
|
||||
try {
|
||||
const messagesResp = await this.client.session.messages({ path: { id: task.parentSessionID } })
|
||||
const messages = (messagesResp.data ?? []) as Array<{
|
||||
const messages = normalizeSDKResponse(messagesResp, [] as Array<{
|
||||
info?: { agent?: string; model?: { providerID: string; modelID: string }; modelID?: string; providerID?: string }
|
||||
}>
|
||||
}>)
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const info = messages[i].info
|
||||
if (info?.agent || info?.model || (info?.modelID && info?.providerID)) {
|
||||
@@ -1535,7 +1535,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
||||
this.pruneStaleTasksAndNotifications()
|
||||
|
||||
const statusResult = await this.client.session.status()
|
||||
const allStatuses = (statusResult.data ?? {}) as Record<string, { type: string }>
|
||||
const allStatuses = normalizeSDKResponse(statusResult, {} as Record<string, { type: string }>)
|
||||
|
||||
await this.checkAndInterruptStaleTasks(allStatuses)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { log } from "../../shared"
|
||||
import { log, normalizeSDKResponse } from "../../shared"
|
||||
|
||||
import { findNearestMessageWithFields } from "../hook-message-injector"
|
||||
import { getTaskToastManager } from "../task-toast-manager"
|
||||
@@ -106,7 +106,7 @@ export async function notifyParentSession(args: {
|
||||
const messagesResp = await client.session.messages({
|
||||
path: { id: task.parentSessionID },
|
||||
})
|
||||
const raw = (messagesResp as { data?: unknown }).data ?? []
|
||||
const raw = normalizeSDKResponse(messagesResp, [] as unknown[])
|
||||
const messages = Array.isArray(raw) ? raw : []
|
||||
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { log } from "../../shared"
|
||||
import { log, normalizeSDKResponse } from "../../shared"
|
||||
|
||||
import {
|
||||
MIN_STABILITY_TIME_MS,
|
||||
@@ -56,7 +56,7 @@ export async function pollRunningTasks(args: {
|
||||
pruneStaleTasksAndNotifications()
|
||||
|
||||
const statusResult = await client.session.status()
|
||||
const allStatuses = ((statusResult as { data?: unknown }).data ?? {}) as SessionStatusMap
|
||||
const allStatuses = normalizeSDKResponse(statusResult, {} as SessionStatusMap)
|
||||
|
||||
await checkAndInterruptStaleTasks(allStatuses)
|
||||
|
||||
@@ -95,10 +95,9 @@ export async function pollRunningTasks(args: {
|
||||
continue
|
||||
}
|
||||
|
||||
const messagesPayload = Array.isArray(messagesResult)
|
||||
? messagesResult
|
||||
: (messagesResult as { data?: unknown }).data
|
||||
const messages = asSessionMessages(messagesPayload)
|
||||
const messages = asSessionMessages(normalizeSDKResponse(messagesResult, [] as SessionMessage[], {
|
||||
preferResponseOnMissingData: true,
|
||||
}))
|
||||
const assistantMsgs = messages.filter((m) => m.info?.role === "assistant")
|
||||
|
||||
let toolCalls = 0
|
||||
@@ -139,7 +138,7 @@ export async function pollRunningTasks(args: {
|
||||
task.stablePolls = (task.stablePolls ?? 0) + 1
|
||||
if (task.stablePolls >= 3) {
|
||||
const recheckStatus = await client.session.status()
|
||||
const recheckData = ((recheckStatus as { data?: unknown }).data ?? {}) as SessionStatusMap
|
||||
const recheckData = normalizeSDKResponse(recheckStatus, {} as SessionStatusMap)
|
||||
const currentStatus = recheckData[sessionID]
|
||||
|
||||
if (currentStatus?.type !== "idle") {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { log } from "../../shared"
|
||||
import { log, normalizeSDKResponse } from "../../shared"
|
||||
|
||||
import type { OpencodeClient } from "./opencode-client"
|
||||
|
||||
@@ -51,7 +51,9 @@ export async function validateSessionHasOutput(
|
||||
path: { id: sessionID },
|
||||
})
|
||||
|
||||
const messages = asSessionMessages((response as { data?: unknown }).data ?? response)
|
||||
const messages = asSessionMessages(normalizeSDKResponse(response, [] as SessionMessage[], {
|
||||
preferResponseOnMissingData: true,
|
||||
}))
|
||||
|
||||
const hasAssistantOrToolMessage = messages.some(
|
||||
(m) => m.info?.role === "assistant" || m.info?.role === "tool"
|
||||
@@ -97,8 +99,9 @@ export async function checkSessionTodos(
|
||||
path: { id: sessionID },
|
||||
})
|
||||
|
||||
const raw = (response as { data?: unknown }).data ?? response
|
||||
const todos = Array.isArray(raw) ? (raw as Todo[]) : []
|
||||
const todos = normalizeSDKResponse(response, [] as Todo[], {
|
||||
preferResponseOnMissingData: true,
|
||||
})
|
||||
if (todos.length === 0) return false
|
||||
|
||||
const incomplete = todos.filter(
|
||||
|
||||
@@ -5,6 +5,8 @@ import { MESSAGE_STORAGE, PART_STORAGE } from "./constants"
|
||||
import type { MessageMeta, OriginalMessageContext, TextPart, ToolPermission } from "./types"
|
||||
import { log } from "../../shared/logger"
|
||||
import { isSqliteBackend } from "../../shared/opencode-storage-detection"
|
||||
import { getMessageDir } from "../../shared/opencode-message-dir"
|
||||
import { normalizeSDKResponse } from "../../shared"
|
||||
|
||||
export interface StoredMessage {
|
||||
agent?: string
|
||||
@@ -64,7 +66,7 @@ export async function findNearestMessageWithFieldsFromSDK(
|
||||
): Promise<StoredMessage | null> {
|
||||
try {
|
||||
const response = await client.session.messages({ path: { id: sessionID } })
|
||||
const messages = ((response.data ?? response) as unknown as SDKMessage[]) ?? []
|
||||
const messages = normalizeSDKResponse(response, [] as SDKMessage[], { preferResponseOnMissingData: true })
|
||||
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const stored = convertSDKMessageToStoredMessage(messages[i])
|
||||
@@ -97,7 +99,7 @@ export async function findFirstMessageWithAgentFromSDK(
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const response = await client.session.messages({ path: { id: sessionID } })
|
||||
const messages = ((response.data ?? response) as unknown as SDKMessage[]) ?? []
|
||||
const messages = normalizeSDKResponse(response, [] as SDKMessage[], { preferResponseOnMissingData: true })
|
||||
|
||||
for (const msg of messages) {
|
||||
const stored = convertSDKMessageToStoredMessage(msg)
|
||||
@@ -354,3 +356,21 @@ export function injectHookMessage(
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveMessageContext(
|
||||
sessionID: string,
|
||||
client: OpencodeClient,
|
||||
messageDir: string | null
|
||||
): Promise<{ prevMessage: StoredMessage | null; firstMessageAgent: string | null }> {
|
||||
const [prevMessage, firstMessageAgent] = isSqliteBackend()
|
||||
? await Promise.all([
|
||||
findNearestMessageWithFieldsFromSDK(client, sessionID),
|
||||
findFirstMessageWithAgentFromSDK(client, sessionID),
|
||||
])
|
||||
: [
|
||||
messageDir ? findNearestMessageWithFields(messageDir) : null,
|
||||
messageDir ? findFirstMessageWithAgent(messageDir) : null,
|
||||
]
|
||||
|
||||
return { prevMessage, firstMessageAgent }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import type { TmuxConfig } from "../../config/schema"
|
||||
import type { TrackedSession, CapacityConfig } from "./types"
|
||||
import { log, normalizeSDKResponse } from "../../shared"
|
||||
import {
|
||||
isInsideTmux as defaultIsInsideTmux,
|
||||
getCurrentPaneId as defaultGetCurrentPaneId,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
SESSION_READY_POLL_INTERVAL_MS,
|
||||
SESSION_READY_TIMEOUT_MS,
|
||||
} from "../../shared/tmux"
|
||||
import { log } from "../../shared"
|
||||
import { queryWindowState } from "./pane-state-querier"
|
||||
import { decideSpawnActions, decideCloseAction, type SessionMapping } from "./decision-engine"
|
||||
import { executeActions, executeAction } from "./action-executor"
|
||||
@@ -103,7 +103,7 @@ export class TmuxSessionManager {
|
||||
while (Date.now() - startTime < SESSION_READY_TIMEOUT_MS) {
|
||||
try {
|
||||
const statusResult = await this.client.session.status({ path: undefined })
|
||||
const allStatuses = (statusResult.data ?? {}) as Record<string, { type: string }>
|
||||
const allStatuses = normalizeSDKResponse(statusResult, {} as Record<string, { type: string }>)
|
||||
|
||||
if (allStatuses[sessionId]) {
|
||||
log("[tmux-session-manager] session ready", {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { POLL_INTERVAL_BACKGROUND_MS } from "../../shared/tmux"
|
||||
import type { TrackedSession } from "./types"
|
||||
import { SESSION_MISSING_GRACE_MS } from "../../shared/tmux"
|
||||
import { log } from "../../shared"
|
||||
import { normalizeSDKResponse } from "../../shared"
|
||||
|
||||
const SESSION_TIMEOUT_MS = 10 * 60 * 1000
|
||||
const MIN_STABILITY_TIME_MS = 10 * 1000
|
||||
@@ -43,7 +44,7 @@ export class TmuxPollingManager {
|
||||
|
||||
try {
|
||||
const statusResult = await this.client.session.status({ path: undefined })
|
||||
const allStatuses = (statusResult.data ?? {}) as Record<string, { type: string }>
|
||||
const allStatuses = normalizeSDKResponse(statusResult, {} as Record<string, { type: string }>)
|
||||
|
||||
log("[tmux-session-manager] pollSessions", {
|
||||
trackedSessions: Array.from(this.sessions.keys()),
|
||||
@@ -82,7 +83,7 @@ export class TmuxPollingManager {
|
||||
|
||||
if (tracked.stableIdlePolls >= STABLE_POLLS_REQUIRED) {
|
||||
const recheckResult = await this.client.session.status({ path: undefined })
|
||||
const recheckStatuses = (recheckResult.data ?? {}) as Record<string, { type: string }>
|
||||
const recheckStatuses = normalizeSDKResponse(recheckResult, {} as Record<string, { type: string }>)
|
||||
const recheckStatus = recheckStatuses[sessionId]
|
||||
|
||||
if (recheckStatus?.type === "idle") {
|
||||
|
||||
Reference in New Issue
Block a user