big format

This commit is contained in:
Dax Raad
2025-11-06 13:03:02 -05:00
parent 8729edc5e0
commit 1ea3a8eb9b
183 changed files with 2629 additions and 2497 deletions

View File

@@ -1,4 +1,4 @@
import { streamText, type ModelMessage, LoadAPIKeyError, type StreamTextResult, type Tool as AITool } from "ai"
import { streamText, type ModelMessage, type StreamTextResult, type Tool as AITool } from "ai"
import { Session } from "."
import { Identifier } from "../id/id"
import { Instance } from "../project/instance"
@@ -30,12 +30,17 @@ export namespace SessionCompaction {
),
}
export function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: ModelsDev.Model }) {
export function isOverflow(input: {
tokens: MessageV2.Assistant["tokens"]
model: ModelsDev.Model
}) {
if (Flag.OPENCODE_DISABLE_AUTOCOMPACT) return false
const context = input.model.limit.context
if (context === 0) return false
const count = input.tokens.input + input.tokens.cache.read + input.tokens.output
const output = Math.min(input.model.limit.output, SessionPrompt.OUTPUT_TOKEN_MAX) || SessionPrompt.OUTPUT_TOKEN_MAX
const output =
Math.min(input.model.limit.output, SessionPrompt.OUTPUT_TOKEN_MAX) ||
SessionPrompt.OUTPUT_TOKEN_MAX
const usable = context - output
return count > usable
}
@@ -87,9 +92,15 @@ export namespace SessionCompaction {
}
}
export async function run(input: { sessionID: string; providerID: string; modelID: string; signal?: AbortSignal }) {
export async function run(input: {
sessionID: string
providerID: string
modelID: string
signal?: AbortSignal
}) {
if (!input.signal) SessionLock.assertUnlocked(input.sessionID)
await using lock = input.signal === undefined ? SessionLock.acquire({ sessionID: input.sessionID }) : undefined
await using lock =
input.signal === undefined ? SessionLock.acquire({ sessionID: input.sessionID }) : undefined
const signal = input.signal ?? lock!.signal
await Session.update(input.sessionID, (draft) => {
@@ -113,7 +124,6 @@ export namespace SessionCompaction {
role: "assistant",
parentID: toSummarize.findLast((m) => m.info.role === "user")?.info.id!,
sessionID: input.sessionID,
system,
mode: "build",
path: {
cwd: Instance.directory,
@@ -150,7 +160,11 @@ export namespace SessionCompaction {
// set to 0, we handle loop
maxRetries: 0,
model: model.language,
providerOptions: ProviderTransform.providerOptions(model.npm, model.providerID, model.info.options),
providerOptions: ProviderTransform.providerOptions(
model.npm,
model.providerID,
model.info.options,
),
headers: model.info.headers,
abortSignal: signal,
onError(error) {
@@ -230,7 +244,11 @@ export namespace SessionCompaction {
error: e,
})
const error = MessageV2.fromError(e, { providerID: input.providerID })
if (retries.count < retries.max && MessageV2.APIError.isInstance(error) && error.data.isRetryable) {
if (
retries.count < retries.max &&
MessageV2.APIError.isInstance(error) &&
error.data.isRetryable
) {
shouldRetry = true
await Session.updatePart({
id: Identifier.ascending("part"),

View File

@@ -50,7 +50,10 @@ export namespace SessionLock {
export function acquire(input: { sessionID: string }) {
const lock = get(input.sessionID)
if (lock) {
throw new LockedError({ sessionID: input.sessionID, message: `Session ${input.sessionID} is locked` })
throw new LockedError({
sessionID: input.sessionID,
message: `Session ${input.sessionID} is locked`,
})
}
const controller = new AbortController()
state().locks.set(input.sessionID, {

View File

@@ -2,14 +2,23 @@ import z from "zod"
import { Bus } from "../bus"
import { NamedError } from "../util/error"
import { Message } from "./message"
import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai"
import {
APICallError,
convertToModelMessages,
LoadAPIKeyError,
type ModelMessage,
type UIMessage,
} from "ai"
import { Identifier } from "../id/id"
import { LSP } from "../lsp"
import { Snapshot } from "@/snapshot"
export namespace MessageV2 {
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
export const AbortedError = NamedError.create("MessageAbortedError", z.object({ message: z.string() }))
export const AbortedError = NamedError.create(
"MessageAbortedError",
z.object({ message: z.string() }),
)
export const AuthError = NamedError.create(
"ProviderAuthError",
z.object({
@@ -242,7 +251,12 @@ export namespace MessageV2 {
export type ToolStateError = z.infer<typeof ToolStateError>
export const ToolState = z
.discriminatedUnion("status", [ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError])
.discriminatedUnion("status", [
ToolStatePending,
ToolStateRunning,
ToolStateCompleted,
ToolStateError,
])
.meta({
ref: "ToolState",
})
@@ -313,7 +327,6 @@ export namespace MessageV2 {
APIError.Schema,
])
.optional(),
system: z.string().array(),
parentID: z.string(),
modelID: z.string(),
providerID: z.string(),
@@ -397,7 +410,6 @@ export namespace MessageV2 {
tokens: v1.metadata.assistant!.tokens,
modelID: v1.metadata.assistant!.modelID,
providerID: v1.metadata.assistant!.providerID,
system: v1.metadata.assistant!.system,
mode: "build",
error: v1.metadata.error,
}
@@ -440,7 +452,8 @@ export namespace MessageV2 {
}
}
const { title, time, ...metadata } = v1.metadata.tool[part.toolInvocation.toolCallId] ?? {}
const { title, time, ...metadata } =
v1.metadata.tool[part.toolInvocation.toolCallId] ?? {}
if (part.toolInvocation.state === "call") {
return {
status: "running",
@@ -541,7 +554,11 @@ export namespace MessageV2 {
},
]
// 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")
if (
part.type === "file" &&
part.mime !== "text/plain" &&
part.mime !== "application/x-directory"
)
return [
{
type: "file",
@@ -600,7 +617,9 @@ export namespace MessageV2 {
state: "output-available",
toolCallId: part.callID,
input: part.state.input,
output: part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output,
output: part.state.time.compacted
? "[Old tool result content cleared]"
: part.state.output,
callProviderMetadata: part.metadata,
},
]

View File

@@ -51,9 +51,11 @@ export namespace Message {
})
export type ToolResult = z.infer<typeof ToolResult>
export const ToolInvocation = z.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult]).meta({
ref: "ToolInvocation",
})
export const ToolInvocation = z
.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult])
.meta({
ref: "ToolInvocation",
})
export type ToolInvocation = z.infer<typeof ToolInvocation>
export const TextPart = z
@@ -122,7 +124,14 @@ export namespace Message {
export type StepStartPart = z.infer<typeof StepStartPart>
export const MessagePart = z
.discriminatedUnion("type", [TextPart, ReasoningPart, ToolInvocationPart, SourceUrlPart, FilePart, StepStartPart])
.discriminatedUnion("type", [
TextPart,
ReasoningPart,
ToolInvocationPart,
SourceUrlPart,
FilePart,
StepStartPart,
])
.meta({
ref: "MessagePart",
})
@@ -140,7 +149,11 @@ export namespace Message {
completed: z.number().optional(),
}),
error: z
.discriminatedUnion("name", [AuthError.Schema, NamedError.Unknown.Schema, OutputLengthError.Schema])
.discriminatedUnion("name", [
AuthError.Schema,
NamedError.Unknown.Schema,
OutputLengthError.Schema,
])
.optional(),
sessionID: z.string(),
tool: z.record(

View File

@@ -961,7 +961,6 @@ export namespace SessionPrompt {
id: Identifier.ascending("message"),
parentID,
role: "assistant",
system: input.system,
mode: input.agent,
path: {
cwd: Instance.directory,
@@ -1412,7 +1411,6 @@ export namespace SessionPrompt {
id: Identifier.ascending("message"),
sessionID: input.sessionID,
parentID: userMsg.id,
system: [],
mode: input.agent,
cost: 0,
path: {
@@ -1709,7 +1707,6 @@ export namespace SessionPrompt {
id: Identifier.ascending("message"),
sessionID: input.sessionID,
parentID: userMsg.id,
system: [],
mode: agentName,
cost: 0,
path: {

View File

@@ -45,7 +45,9 @@ export namespace SessionRevert {
if (!revert) {
if ((msg.info.id === input.messageID && !input.partID) || part.id === input.partID) {
// if no useful parts left in message, same as reverting whole message
const partID = remaining.some((item) => ["text", "tool"].includes(item.type)) ? input.partID : undefined
const partID = remaining.some((item) => ["text", "tool"].includes(item.type))
? input.partID
: undefined
revert = {
messageID: !partID && lastUser ? lastUser.id : msg.info.id,
partID,

View File

@@ -24,7 +24,8 @@ export namespace SystemPrompt {
export function provider(modelID: string) {
if (modelID.includes("gpt-5")) return [PROMPT_CODEX]
if (modelID.includes("gpt-") || modelID.includes("o1") || modelID.includes("o3")) return [PROMPT_BEAST]
if (modelID.includes("gpt-") || modelID.includes("o1") || modelID.includes("o3"))
return [PROMPT_BEAST]
if (modelID.includes("gemini-")) return [PROMPT_GEMINI]
if (modelID.includes("claude")) return [PROMPT_ANTHROPIC]
return [PROMPT_ANTHROPIC_WITHOUT_TODO]
@@ -99,7 +100,11 @@ export namespace SystemPrompt {
}),
).catch(() => [])
} else {
matches = await Filesystem.globUp(instruction, Instance.directory, Instance.worktree).catch(() => [])
matches = await Filesystem.globUp(
instruction,
Instance.directory,
Instance.worktree,
).catch(() => [])
}
matches.forEach((path) => paths.add(path))
}

View File

@@ -6,7 +6,9 @@ export namespace Todo {
export const Info = z
.object({
content: z.string().describe("Brief description of the task"),
status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
status: z
.string()
.describe("Current status of the task: pending, in_progress, completed, cancelled"),
priority: z.string().describe("Priority level of the task: high, medium, low"),
id: z.string().describe("Unique identifier for the todo item"),
})