842 lines · 25.6 KB · TypeScript
RawDownload
import { BusEvent } from "@/bus/bus-event"import z from "zod"import { NamedError } from "@synsci/util/error"import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai"import { Identifier } from "../id/id"import { LSP } from "../lsp"import { Snapshot } from "@/snapshot"import { fn } from "@/util/fn"import { Storage } from "@/storage/storage"import { ProviderTransform } from "@/provider/transform"import { STATUS_CODES } from "http"import { iife } from "@/util/iife"import { type SystemError } from "bun"import type { Provider } from "@/provider/provider"import { correctImageMimeFromBase64 } from "@/util/image"import { Lock } from "@/util/lock" export namespace MessageV2 {  export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))  export const AbortedError = NamedError.create("MessageAbortedError", z.object({ message: z.string() }))  export const AuthError = NamedError.create(    "ProviderAuthError",    z.object({      providerID: z.string(),      message: z.string(),    }),  )  export const APIError = NamedError.create(    "APIError",    z.object({      message: z.string(),      statusCode: z.number().optional(),      isRetryable: z.boolean(),      responseHeaders: z.record(z.string(), z.string()).optional(),      responseBody: z.string().optional(),      metadata: z.record(z.string(), z.string()).optional(),    }),  )  export type APIError = z.infer<typeof APIError.Schema>   const PartBase = z.object({    id: z.string(),    sessionID: z.string(),    messageID: z.string(),  })   export const SnapshotPart = PartBase.extend({    type: z.literal("snapshot"),    snapshot: z.string(),  }).meta({    ref: "SnapshotPart",  })  export type SnapshotPart = z.infer<typeof SnapshotPart>   export const PatchPart = PartBase.extend({    type: z.literal("patch"),    hash: z.string(),    files: z.string().array(),  }).meta({    ref: "PatchPart",  })  export type PatchPart = z.infer<typeof PatchPart>   export const TextPart = PartBase.extend({    type: z.literal("text"),    text: z.string(),    synthetic: z.boolean().optional(),    ignored: z.boolean().optional(),    time: z      .object({        start: z.number(),        end: z.number().optional(),      })      .optional(),    metadata: z.record(z.string(), z.any()).optional(),  }).meta({    ref: "TextPart",  })  export type TextPart = z.infer<typeof TextPart>   export const ReasoningPart = PartBase.extend({    type: z.literal("reasoning"),    text: z.string(),    metadata: z.record(z.string(), z.any()).optional(),    time: z.object({      start: z.number(),      end: z.number().optional(),    }),  }).meta({    ref: "ReasoningPart",  })  export type ReasoningPart = z.infer<typeof ReasoningPart>   const FilePartSourceBase = z.object({    text: z      .object({        value: z.string(),        start: z.number().int(),        end: z.number().int(),      })      .meta({        ref: "FilePartSourceText",      }),  })   export const FileSource = FilePartSourceBase.extend({    type: z.literal("file"),    path: z.string(),  }).meta({    ref: "FileSource",  })   export const SymbolSource = FilePartSourceBase.extend({    type: z.literal("symbol"),    path: z.string(),    range: LSP.Range,    name: z.string(),    kind: z.number().int(),  }).meta({    ref: "SymbolSource",  })   export const ResourceSource = FilePartSourceBase.extend({    type: z.literal("resource"),    clientName: z.string(),    uri: z.string(),  }).meta({    ref: "ResourceSource",  })   export const FilePartSource = z.discriminatedUnion("type", [FileSource, SymbolSource, ResourceSource]).meta({    ref: "FilePartSource",  })   export const FilePart = PartBase.extend({    type: z.literal("file"),    mime: z.string(),    filename: z.string().optional(),    url: z.string(),    source: FilePartSource.optional(),  }).meta({    ref: "FilePart",  })  export type FilePart = z.infer<typeof FilePart>   export const AgentPart = PartBase.extend({    type: z.literal("agent"),    name: z.string(),    source: z      .object({        value: z.string(),        start: z.number().int(),        end: z.number().int(),      })      .optional(),  }).meta({    ref: "AgentPart",  })  export type AgentPart = z.infer<typeof AgentPart>   export const CompactionPart = PartBase.extend({    type: z.literal("compaction"),    auto: z.boolean(),  }).meta({    ref: "CompactionPart",  })  export type CompactionPart = z.infer<typeof CompactionPart>   export const SubtaskPart = PartBase.extend({    type: z.literal("subtask"),    prompt: z.string(),    description: z.string(),    agent: z.string(),    model: z      .object({        providerID: z.string(),        modelID: z.string(),      })      .optional(),    command: z.string().optional(),  }).meta({    ref: "SubtaskPart",  })  export type SubtaskPart = z.infer<typeof SubtaskPart>   export const RetryPart = PartBase.extend({    type: z.literal("retry"),    attempt: z.number(),    error: APIError.Schema,    time: z.object({      created: z.number(),    }),  }).meta({    ref: "RetryPart",  })  export type RetryPart = z.infer<typeof RetryPart>   export const StepStartPart = PartBase.extend({    type: z.literal("step-start"),    snapshot: z.string().optional(),  }).meta({    ref: "StepStartPart",  })  export type StepStartPart = z.infer<typeof StepStartPart>   export const StepFinishPart = PartBase.extend({    type: z.literal("step-finish"),    reason: z.string(),    snapshot: z.string().optional(),    cost: z.number(),    tokens: z.object({      input: z.number(),      output: z.number(),      reasoning: z.number(),      cache: z.object({        read: z.number(),        write: z.number(),      }),    }),  }).meta({    ref: "StepFinishPart",  })  export type StepFinishPart = z.infer<typeof StepFinishPart>   export const ToolStatePending = z    .object({      status: z.literal("pending"),      input: z.record(z.string(), z.any()),      raw: z.string(),    })    .meta({      ref: "ToolStatePending",    })   export type ToolStatePending = z.infer<typeof ToolStatePending>   export const ToolStateRunning = z    .object({      status: z.literal("running"),      input: z.record(z.string(), z.any()),      title: z.string().optional(),      metadata: z.record(z.string(), z.any()).optional(),      time: z.object({        start: z.number(),      }),    })    .meta({      ref: "ToolStateRunning",    })  export type ToolStateRunning = z.infer<typeof ToolStateRunning>   export const ToolStateCompleted = z    .object({      status: z.literal("completed"),      input: z.record(z.string(), z.any()),      output: z.string(),      title: z.string(),      metadata: z.record(z.string(), z.any()),      time: z.object({        start: z.number(),        end: z.number(),        compacted: z.number().optional(),      }),      attachments: FilePart.array().optional(),    })    .meta({      ref: "ToolStateCompleted",    })  export type ToolStateCompleted = z.infer<typeof ToolStateCompleted>   export const ToolStateError = z    .object({      status: z.literal("error"),      input: z.record(z.string(), z.any()),      error: z.string(),      metadata: z.record(z.string(), z.any()).optional(),      time: z.object({        start: z.number(),        end: z.number(),      }),    })    .meta({      ref: "ToolStateError",    })  export type ToolStateError = z.infer<typeof ToolStateError>   export const ToolState = z    .discriminatedUnion("status", [ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError])    .meta({      ref: "ToolState",    })   export const ToolPart = PartBase.extend({    type: z.literal("tool"),    callID: z.string(),    tool: z.string(),    state: ToolState,    metadata: z.record(z.string(), z.any()).optional(),  }).meta({    ref: "ToolPart",  })  export type ToolPart = z.infer<typeof ToolPart>   const Base = z.object({    id: z.string(),    sessionID: z.string(),  })   export const User = Base.extend({    role: z.literal("user"),    time: z.object({      created: z.number(),    }),    summary: z      .object({        title: z.string().optional(),        body: z.string().optional(),        diffs: Snapshot.FileDiff.array(),      })      .optional(),    agent: z.string(),    model: z.object({      providerID: z.string(),      modelID: z.string(),    }),    system: z.string().optional(),    tools: z.record(z.string(), z.boolean()).optional(),    variant: z.string().optional(),    tier: z.enum(["fast", "pro", "ultra"]).optional(),    // Real per-request "fast" API param — currently gpt-5.5 only, mapped to    // OpenAI service_tier:"priority" in llm.ts. Distinct from `tier` (billing).    fast: z.boolean().optional(),  }).meta({    ref: "UserMessage",  })  export type User = z.infer<typeof User>   export const Part = z    .discriminatedUnion("type", [      TextPart,      SubtaskPart,      ReasoningPart,      FilePart,      ToolPart,      StepStartPart,      StepFinishPart,      SnapshotPart,      PatchPart,      AgentPart,      RetryPart,      CompactionPart,    ])    .meta({      ref: "Part",    })  export type Part = z.infer<typeof Part>   export const Assistant = Base.extend({    role: z.literal("assistant"),    time: z.object({      created: z.number(),      completed: z.number().optional(),    }),    error: z      .discriminatedUnion("name", [        AuthError.Schema,        NamedError.Unknown.Schema,        OutputLengthError.Schema,        AbortedError.Schema,        APIError.Schema,      ])      .optional(),    parentID: z.string(),    modelID: z.string(),    providerID: z.string(),    /**     * @deprecated     */    mode: z.string(),    agent: z.string(),    path: z.object({      cwd: z.string(),      root: z.string(),    }),    summary: z.boolean().optional(),    cost: z.number(),    tokens: z.object({      input: z.number(),      output: z.number(),      reasoning: z.number(),      cache: z.object({        read: z.number(),        write: z.number(),      }),    }),    finish: z.string().optional(),  }).meta({    ref: "AssistantMessage",  })  export type Assistant = z.infer<typeof Assistant>   export const Info = z.discriminatedUnion("role", [User, Assistant]).meta({    ref: "Message",  })  export type Info = z.infer<typeof Info>   export const Event = {    Updated: BusEvent.define(      "message.updated",      z.object({        info: Info,      }),    ),    Removed: BusEvent.define(      "message.removed",      z.object({        sessionID: z.string(),        messageID: z.string(),      }),    ),    PartUpdated: BusEvent.define(      "message.part.updated",      z.object({        part: Part,        delta: z.string().optional(),      }),    ),    PartRemoved: BusEvent.define(      "message.part.removed",      z.object({        sessionID: z.string(),        messageID: z.string(),        partID: z.string(),      }),    ),  }   export const WithParts = z.object({    info: Info,    parts: z.array(Part),  })  export type WithParts = z.infer<typeof WithParts>   export function toModelMessages(input: WithParts[], model: Provider.Model): ModelMessage[] {    const result: UIMessage[] = []    const toolNames = new Set<string>()     const toModelOutput = (output: unknown) => {      if (typeof output === "string") {        return { type: "text", value: output }      }       if (typeof output === "object") {        const outputObject = output as {          text: string          attachments?: Array<{ mime: string; url: string }>        }        const attachments = (outputObject.attachments ?? []).filter((attachment) => {          return attachment.url.startsWith("data:") && attachment.url.includes(",")        })         return {          type: "content",          value: [            { type: "text", text: outputObject.text },            ...attachments.map((attachment) => {              const base64 = iife(() => {                const commaIndex = attachment.url.indexOf(",")                return commaIndex === -1 ? attachment.url : attachment.url.slice(commaIndex + 1)              })              const mime = attachment.mime.startsWith("image/")                ? correctImageMimeFromBase64(attachment.mime, base64)                : attachment.mime              return { type: "media", mediaType: mime, data: base64 }            }),          ],        }      }       return { type: "json", value: output as never }    }     for (const msg of input) {      if (msg.parts.length === 0) continue       if (msg.info.role === "user") {        const userMessage: UIMessage = {          id: msg.info.id,          role: "user",          parts: [],        }        result.push(userMessage)        for (const part of msg.parts) {          if (part.type === "text" && !part.ignored)            userMessage.parts.push({              type: "text",              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") {

Lines 1–500 of 842.