Skip to content

Latest commit

 

History

History
161 lines (132 loc) · 6.58 KB

File metadata and controls

161 lines (132 loc) · 6.58 KB

4.2 Message Model

Model: claude-opus-4-6 (anthropic/claude-opus-4-6) Generation Date: 2025-02-17


A Message is the smallest unit of information exchange within a session. OpenCode's message model is elegantly designed, supporting rich content types through the Part system.

4.2.1 The MessageV2 Namespace in Detail

The message system is defined in session/message-v2.ts. Messages are divided into two roles:

User Messages:

export const User = Base.extend({
  role: z.literal("user"),
  time: z.object({ created: z.number() }),
  summary: z.object({           // Change summary (optional)
    title: z.string().optional(),
    body: z.string().optional(),
    diffs: Snapshot.FileDiff.array(),
  }).optional(),
  agent: z.string(),             // Name of the Agent being used
  model: z.object({              // Model being used
    providerID: z.string(),
    modelID: z.string(),
  }),
  system: z.string().optional(), // Additional system instructions
  tools: z.record(z.string(), z.boolean()).optional(), // Tool toggles (deprecated)
  variant: z.string().optional(), // Variant identifier
})

Assistant Messages:

export const Assistant = Base.extend({
  role: z.literal("assistant"),
  time: z.object({
    created: z.number(),
    completed: z.number().optional(),  // Generation completion time
  }),
  error: z.discriminatedUnion("name", [
    AuthError.Schema,
    NamedError.Unknown.Schema,
    OutputLengthError.Schema,
    AbortedError.Schema,
    ContextOverflowError.Schema,
    APIError.Schema,
  ]).optional(),
  parentID: z.string(),          // ID of the corresponding User message
  modelID: z.string(),
  providerID: z.string(),
  agent: z.string(),
  cost: z.number(),              // Cost of this response (in USD)
  tokens: z.object({
    total: z.number().optional(),
    input: z.number(),           // Input token count
    output: z.number(),          // Output token count
    reasoning: z.number(),       // Chain-of-thought token count
    cache: z.object({
      read: z.number(),          // Cache read token count
      write: z.number(),         // Cache write token count
    }),
  }),
  summary: z.boolean().optional(), // Whether this is a compaction summary
  finish: z.string().optional(),   // Finish reason
})

Token Statistics Field Breakdown:

The tokens object records the token consumption for this LLM call:

  • input: The number of input tokens sent to the model (including system prompt, message history, tool results, etc.)
  • output: The number of output tokens generated by the model
  • reasoning: The number of chain-of-thought tokens used by the model (only for models that support chain-of-thought, such as Claude's extended thinking)
  • cache.read: The number of tokens read from the Prompt Cache
  • cache.write: The number of tokens written to the Prompt Cache
  • total: Total token count (some Providers return the total directly)

Extended Explanation: Prompt Cache

In multi-turn conversations, each request sends the full message history. The System Prompt and earlier messages are identical in every request. Prompt Cache is an optimization offered by LLM Providers -- it caches these repeated portions on the server side to avoid redundant computation.

cache.write indicates how many tokens were cached for the first time in this request; cache.read indicates how many tokens hit the cache in this request. Cache-hit tokens are billed at a much lower rate than regular tokens, significantly reducing costs.

4.2.2 The Multimodal Part Type System

The actual content of a message is not stored directly on the Message object; instead, it is organized through the Part system. Each message can contain multiple Parts of different types. All Parts share a common base structure:

const PartBase = z.object({
  id: z.string(),         // Unique Part identifier
  sessionID: z.string(),  // ID of the owning session
  messageID: z.string(),  // ID of the owning message
})

Complete Part types (12 types):

Part Type Purpose Key Fields
TextPart Text content text, time, synthetic (whether synthesized), ignored
ReasoningPart Chain of thought text, time.start, time.end, metadata
ToolPart Tool invocation tool, callID, state (four states)
FilePart File attachment mime, filename, url, source
AgentPart Agent reference name, source
SubtaskPart Subtask prompt, description, agent, model
CompactionPart Compaction marker auto (whether auto-triggered)
StepStartPart Step start snapshot
StepFinishPart Step finish reason, snapshot, cost, tokens
SnapshotPart Snapshot snapshot
PatchPart Patch hash, files
RetryPart Retry marker attempt, error, time

The Four States of ToolPart:

ToolPart is the most complex Part type, tracking the full lifecycle of a tool invocation via its state field:

pending ---> running ---> completed
                    +---> error
// 1. Pending: Tool call identified, arguments still being received
ToolStatePending = { status: "pending", input: {}, raw: "" }

// 2. Running: Arguments complete, execution begins
ToolStateRunning = { status: "running", input: {...}, time: { start } }

// 3. Completed: Execution succeeded
ToolStateCompleted = {
  status: "completed",
  input: {...}, output: "...",
  title: "Read file /src/index.ts",
  metadata: { truncated: false },
  time: { start, end, compacted? }  // compacted: Prune timestamp
}

// 4. Error: Execution failed
ToolStateError = {
  status: "error",
  input: {...}, error: "Permission denied",
  time: { start, end }
}

4.2.3 Message Version Migration Strategy

The v2 in the filename hints that this is not the first version of the message model. The coexistence of message.ts (legacy) and message-v2.ts (current) indicates that OpenCode underwent a major refactoring of its message model.

Key improvements in the new version:

  1. Part System: The legacy version stored all content directly on the message; the new version achieves content modularity through Parts
  2. Finer-Grained State Tracking: The four-state model for ToolPart, StepStart/StepFinish markers
  3. Cost Tracking: Each Assistant message records precise token consumption and cost

This incremental migration strategy (keeping the old version, introducing the new version, and gradually switching over) is a common practice in large projects -- it ensures that all existing data is not broken at once.