Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/opencode/src/provider/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export namespace ProviderError {
/context window exceeds limit/i, // MiniMax
/exceeded model token limit/i, // Kimi For Coding, Moonshot
/context[_ ]length[_ ]exceeded/i, // Generic fallback
/request entity too large/i, // HTTP 413
]

function isOpenAiErrorRetryable(e: APICallError) {
Expand Down Expand Up @@ -165,7 +166,7 @@ export namespace ProviderError {

export function parseAPICallError(input: { providerID: string; error: APICallError }): ParsedAPICallError {
const m = message(input.providerID, input.error)
if (isOverflow(m)) {
if (isOverflow(m) || input.error.statusCode === 413) {
return {
type: "context_overflow",
message: m,
Expand Down
20 changes: 18 additions & 2 deletions packages/opencode/src/session/compaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export namespace SessionCompaction {
sessionID: string
abort: AbortSignal
auto: boolean
overflow?: boolean
}) {
const userMessage = input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User
const agent = await Agent.get("compaction")
Expand Down Expand Up @@ -185,7 +186,7 @@ When constructing the summary, try to stick to this template:
tools: {},
system: [],
messages: [
...MessageV2.toModelMessages(input.messages, model),
...MessageV2.toModelMessages(input.messages, model, { stripMedia: true }),
{
role: "user",
content: [
Expand All @@ -199,6 +200,15 @@ When constructing the summary, try to stick to this template:
model,
})

if (result === "compact") {
processor.message.error = new MessageV2.ContextOverflowError({
message: "Session too large to compact - context exceeds model limit even after stripping media",
}).toObject()
processor.message.finish = "error"
await Session.updateMessage(processor.message)
return "stop"
}

if (result === "continue" && input.auto) {
const continueMsg = await Session.updateMessage({
id: Identifier.ascending("message"),
Expand All @@ -210,13 +220,17 @@ When constructing the summary, try to stick to this template:
agent: userMessage.agent,
model: userMessage.model,
})
const text =
(input.overflow
? "The previous request exceeded the provider's size limit due to large media attachments. The conversation was compacted and media files were removed from context. If the user was asking about attached images or files, explain that the attachments were too large to process and suggest they try again with smaller or fewer files.\n\n"
: "") + "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed."
await Session.updatePart({
id: Identifier.ascending("part"),
messageID: continueMsg.id,
sessionID: input.sessionID,
type: "text",
synthetic: true,
text: "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.",
text,
time: {
start: Date.now(),
end: Date.now(),
Expand All @@ -237,6 +251,7 @@ When constructing the summary, try to stick to this template:
modelID: z.string(),
}),
auto: z.boolean(),
overflow: z.boolean().optional(),
}),
async (input) => {
const msg = await Session.updateMessage({
Expand All @@ -255,6 +270,7 @@ When constructing the summary, try to stick to this template:
sessionID: msg.sessionID,
type: "compaction",
auto: input.auto,
overflow: input.overflow,
})
},
)
Expand Down
32 changes: 23 additions & 9 deletions packages/opencode/src/session/message-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export namespace MessageV2 {
export const CompactionPart = PartBase.extend({
type: z.literal("compaction"),
auto: z.boolean(),
overflow: z.boolean().optional(),
}).meta({
ref: "CompactionPart",
})
Expand Down Expand Up @@ -488,7 +489,11 @@ export namespace MessageV2 {
})
export type WithParts = z.infer<typeof WithParts>

export function toModelMessages(input: WithParts[], model: Provider.Model): ModelMessage[] {
export function toModelMessages(
input: WithParts[],
model: Provider.Model,
options?: { stripMedia?: boolean },
): ModelMessage[] {
const result: UIMessage[] = []
const toolNames = new Set<string>()
// Track media from tool results that need to be injected as user messages
Expand Down Expand Up @@ -562,13 +567,22 @@ export namespace MessageV2 {
text: part.text,
})
// text/plain and directory files are converted into text parts, ignore them
if (part.type === "file" && part.mime !== "text/plain" && part.mime !== "application/x-directory")
userMessage.parts.push({
type: "file",
url: part.url,
mediaType: part.mime,
filename: part.filename,
})
if (part.type === "file" && part.mime !== "text/plain" && part.mime !== "application/x-directory") {
const isMedia = part.mime.startsWith("image/") || part.mime === "application/pdf"
if (options?.stripMedia && isMedia) {
userMessage.parts.push({
type: "text",
text: `[Attached ${part.mime}: ${part.filename ?? "file"}]`,
})
} else {
userMessage.parts.push({
type: "file",
url: part.url,
mediaType: part.mime,
filename: part.filename,
})
}
}

if (part.type === "compaction") {
userMessage.parts.push({
Expand Down Expand Up @@ -618,7 +632,7 @@ export namespace MessageV2 {
toolNames.add(part.tool)
if (part.state.status === "completed") {
const outputText = part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output
const attachments = part.state.time.compacted ? [] : (part.state.attachments ?? [])
const attachments = part.state.time.compacted || options?.stripMedia ? [] : (part.state.attachments ?? [])

// For providers that don't support media in tool results, extract media files
// (images, PDFs) to be sent as a separate user message
Expand Down
43 changes: 24 additions & 19 deletions packages/opencode/src/session/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,27 +354,32 @@ export namespace SessionProcessor {
})
const error = MessageV2.fromError(e, { providerID: input.model.providerID })
if (MessageV2.ContextOverflowError.isInstance(error)) {
// TODO: Handle context overflow error
}
const retry = SessionRetry.retryable(error)
if (retry !== undefined) {
attempt++
const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined)
SessionStatus.set(input.sessionID, {
type: "retry",
attempt,
message: retry,
next: Date.now() + delay,
needsCompaction = true
Bus.publish(Session.Event.Error, {
sessionID: input.sessionID,
error,
})
await SessionRetry.sleep(delay, input.abort).catch(() => {})
continue
} else {
const retry = SessionRetry.retryable(error)
if (retry !== undefined) {
attempt++
const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined)
SessionStatus.set(input.sessionID, {
type: "retry",
attempt,
message: retry,
next: Date.now() + delay,
})
await SessionRetry.sleep(delay, input.abort).catch(() => {})
continue
}
input.assistantMessage.error = error
Bus.publish(Session.Event.Error, {
sessionID: input.assistantMessage.sessionID,
error: input.assistantMessage.error,
})
SessionStatus.set(input.sessionID, { type: "idle" })
}
input.assistantMessage.error = error
Bus.publish(Session.Event.Error, {
sessionID: input.assistantMessage.sessionID,
error: input.assistantMessage.error,
})
SessionStatus.set(input.sessionID, { type: "idle" })
}
if (snapshot) {
const patch = await Snapshot.patch(snapshot)
Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ export namespace SessionPrompt {
abort,
sessionID,
auto: task.auto,
overflow: task.overflow,
})
if (result === "stop") break
continue
Expand Down Expand Up @@ -707,6 +708,7 @@ export namespace SessionPrompt {
agent: lastUser.agent,
model: lastUser.model,
auto: true,
overflow: !processor.message.finish,
})
}
continue
Expand Down
Loading