Skip to content
1 change: 1 addition & 0 deletions src/features/background-agent/session-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export async function validateSessionHasOutput(
if (type === "tool") return true
if (type === "text" && hasNonEmptyText(part.text)) return true
if (type === "reasoning" && hasNonEmptyText(part.text)) return true
if (type === "reasoning.encrypted" && hasNonEmptyText(part.text)) return true
if (type === "tool_result" && isToolResultContentNonEmpty(part.content)) return true
return false
})
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/session-recovery/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export const OPENCODE_STORAGE = getOpenCodeStorageDir()
export const MESSAGE_STORAGE = join(OPENCODE_STORAGE, "message")
export const PART_STORAGE = join(OPENCODE_STORAGE, "part")

export const THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"])
export const THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning", "reasoning.encrypted"])
export const META_TYPES = new Set(["step-start", "step-finish"])
export const CONTENT_TYPES = new Set(["text", "tool", "tool_use", "tool_result"])
23 changes: 23 additions & 0 deletions src/hooks/session-recovery/reasoning-encrypted.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, it } from "bun:test"
import { THINKING_TYPES } from "./constants"

describe("reasoning.encrypted support", () => {
//#given reasoning.encrypted is a valid thinking-family type from Grok models

it("should include reasoning.encrypted in THINKING_TYPES Set", () => {
//#when checking if reasoning.encrypted is in THINKING_TYPES
const result = THINKING_TYPES.has("reasoning.encrypted")

//#then it should return true
expect(result).toBe(true)
})

it("should recognize reasoning.encrypted as a ThinkingPartType", () => {
//#given a type assertion for reasoning.encrypted
//#when assigning to ThinkingPartType
const thinkingType: import("./types").ThinkingPartType = "reasoning.encrypted"

//#then it should compile without error
expect(thinkingType).toBe("reasoning.encrypted")
})
})
2 changes: 1 addition & 1 deletion src/hooks/session-recovery/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type ThinkingPartType = "thinking" | "redacted_thinking" | "reasoning"
export type ThinkingPartType = "thinking" | "redacted_thinking" | "reasoning" | "reasoning.encrypted"
export type MetaPartType = "step-start" | "step-finish"
export type ContentPartType = "text" | "tool" | "tool_use" | "tool_result"

Expand Down
40 changes: 40 additions & 0 deletions src/hooks/thinking-block-validator/reasoning-encrypted.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, expect, it } from "bun:test"

describe("reasoning.encrypted in thinking-family checks", () => {
//#given a helper function that checks if a type is thinking-family
const isThinkingFamily = (type: string): boolean => {
return type === "thinking" || type === "reasoning" || type === "reasoning.encrypted"
}

it("should recognize reasoning.encrypted as thinking-family type", () => {
//#when checking if reasoning.encrypted is thinking-family
const result = isThinkingFamily("reasoning.encrypted")

//#then it should return true
expect(result).toBe(true)
})

it("should recognize reasoning as thinking-family type", () => {
//#when checking if reasoning is thinking-family
const result = isThinkingFamily("reasoning")

//#then it should return true
expect(result).toBe(true)
})

it("should recognize thinking as thinking-family type", () => {
//#when checking if thinking is thinking-family
const result = isThinkingFamily("thinking")

//#then it should return true
expect(result).toBe(true)
})

it("should not recognize text as thinking-family type", () => {
//#when checking if text is thinking-family
const result = isThinkingFamily("text")

//#then it should return false
expect(result).toBe(false)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ async function getThinkingSummary(ctx: BabysitterContext, sessionID: string): Pr
if (part.type === "reasoning" && part.text) {
chunks.push(part.text)
}
if (part.type === "reasoning.encrypted" && part.text) {
chunks.push(part.text)
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/tools/background-task/full-session-format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export async function formatFullSession(
const normalizedMessages: BackgroundOutputMessage[] = []
for (const message of filteredMessages) {
const parts = (message.parts ?? []).filter((part) => {
if (part.type === "thinking" || part.type === "reasoning") {
if (part.type === "thinking" || part.type === "reasoning" || part.type === "reasoning.encrypted") {
return includeThinking
}
if (part.type === "tool_result") {
Expand Down Expand Up @@ -135,6 +135,8 @@ export async function formatFullSession(
lines.push(`[thinking] ${truncateText(part.thinking, thinkingMaxChars)}`)
} else if (part.type === "reasoning" && part.text) {
lines.push(`[thinking] ${truncateText(part.text, thinkingMaxChars)}`)
} else if (part.type === "reasoning.encrypted" && part.text) {
lines.push(`[thinking] ${truncateText(part.text, thinkingMaxChars)}`)
} else if (part.type === "tool_result") {
const toolTexts = extractToolResultText(part)
for (const toolText of toolTexts) {
Expand Down
4 changes: 3 additions & 1 deletion src/tools/background-task/modules/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export async function formatFullSession(
const normalizedMessages: FullSessionMessage[] = []
for (const message of filteredMessages) {
const parts = (message.parts ?? []).filter((part) => {
if (part.type === "thinking" || part.type === "reasoning") {
if (part.type === "thinking" || part.type === "reasoning" || part.type === "reasoning.encrypted") {
return includeThinking
}
if (part.type === "tool_result") {
Expand Down Expand Up @@ -302,6 +302,8 @@ export async function formatFullSession(
lines.push(`[thinking] ${truncateText(part.thinking, thinkingMaxChars)}`)
} else if (part.type === "reasoning" && part.text) {
lines.push(`[thinking] ${truncateText(part.text, thinkingMaxChars)}`)
} else if (part.type === "reasoning.encrypted" && part.text) {
lines.push(`[thinking] ${truncateText(part.text, thinkingMaxChars)}`)
} else if (part.type === "tool_result") {
const toolTexts = extractToolResultText(part)
for (const toolText of toolTexts) {
Expand Down
2 changes: 1 addition & 1 deletion src/tools/background-task/modules/message-processing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function extractToolResultText(part: FullSessionMessagePart): string[] {

if (Array.isArray(part.content)) {
const blocks = part.content
.filter((block) => (block.type === "text" || block.type === "reasoning") && block.text)
.filter((block) => (block.type === "text" || block.type === "reasoning" || block.type === "reasoning.encrypted") && block.text)
.map((block) => block.text as string)
if (blocks.length > 0) return blocks
}
Expand Down
49 changes: 49 additions & 0 deletions src/tools/background-task/reasoning-encrypted-extraction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, expect, it } from "bun:test"

describe("reasoning.encrypted content extraction", () => {
//#given content parts with reasoning.encrypted type

it("should extract text from reasoning.encrypted part when text is present", () => {
//#given a part with reasoning.encrypted type and text
const part = { type: "reasoning.encrypted" as const, text: "decrypted reasoning content" }

//#when filtering for text or reasoning.encrypted with text guard
const shouldInclude = !!((part.type === "text" || part.type === "reasoning" || part.type === "reasoning.encrypted") && part.text)

//#then it should be included
expect(shouldInclude).toBe(true)
})

it("should skip reasoning.encrypted part when text is missing", () => {
//#given a part with reasoning.encrypted type but no text
const part = { type: "reasoning.encrypted" as const }

//#when filtering for text or reasoning.encrypted with text guard
const shouldInclude = !!((part.type === "text" || part.type === "reasoning" || part.type === "reasoning.encrypted") && (part as any).text)

//#then it should be excluded
expect(shouldInclude).toBe(false)
})

it("should extract text from reasoning part when text is present", () => {
//#given a part with reasoning type and text
const part = { type: "reasoning" as const, text: "reasoning content" }

//#when filtering for text or reasoning with text guard
const shouldInclude = !!((part.type === "text" || part.type === "reasoning" || part.type === "reasoning.encrypted") && part.text)

//#then it should be included
expect(shouldInclude).toBe(true)
})

it("should extract text from text part", () => {
//#given a part with text type
const part = { type: "text" as const, text: "text content" }

//#when filtering for text or reasoning with text guard
const shouldInclude = !!((part.type === "text" || part.type === "reasoning" || part.type === "reasoning.encrypted") && part.text)

//#then it should be included
expect(shouldInclude).toBe(true)
})
})
2 changes: 1 addition & 1 deletion src/tools/delegate-task/sync-result-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export async function fetchSyncResult(
return { ok: false, error: `No assistant response found.\n\nSession ID: ${sessionID}` }
}

const textParts = lastMessage?.parts?.filter((p) => p.type === "text" || p.type === "reasoning") ?? []
const textParts = lastMessage?.parts?.filter((p) => p.type === "text" || p.type === "reasoning" || p.type === "reasoning.encrypted") ?? []
const textContent = textParts.map((p) => p.text ?? "").filter(Boolean).join("\n")

return { ok: true, textContent }
Expand Down
2 changes: 1 addition & 1 deletion src/tools/delegate-task/sync-session-poller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export async function pollSyncSession(
if (m.info?.role !== "assistant") return false
const parts = m.parts ?? []
return parts.some((p) => {
if (p.type !== "text" && p.type !== "reasoning") return false
if (p.type !== "text" && p.type !== "reasoning" && p.type !== "reasoning.encrypted") return false
const text = (p.text ?? "").trim()
return text.length > 0
})
Expand Down
2 changes: 1 addition & 1 deletion src/tools/delegate-task/unstable-agent-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export async function executeUnstableAgentTask(
return `No assistant response found (task ran in background mode).\n\nSession ID: ${sessionID}`
}

const textParts = lastMessage?.parts?.filter((p) => p.type === "text" || p.type === "reasoning") ?? []
const textParts = lastMessage?.parts?.filter((p) => p.type === "text" || p.type === "reasoning" || p.type === "reasoning.encrypted") ?? []
const textContent = textParts.map((p) => p.text ?? "").filter(Boolean).join("\n")
const duration = formatDuration(startTime)

Expand Down