Class: Envoy::Conversation

Inherits:
ApplicationRecord show all
Defined in:
app/models/envoy/conversation.rb

Instance Method Summary collapse

Instance Method Details

#effective_system_promptObject

Per-conversation ad-hoc instructions, appended last by Runner.



36
37
38
# File 'app/models/envoy/conversation.rb', line 36

def effective_system_prompt
  system_prompt.presence
end

#heal_interrupted_tail!Object

Truncate an interrupted tail so the next turn starts from a valid state.

acts_as_chat replays the whole message log to the provider every turn. If a turn died without a RubyLLM::Error (a deploy restart mid-turn, an OOM, a network kill), RubyLLM's own cleanup never ran and the log can end with a blank assistant row or an assistant tool_use that has no result — both of which the provider rejects, bricking every future turn. Drop everything after the last completed assistant turn (non-blank content, every tool_use answered); if there is no such checkpoint yet, drop everything but a leading system prefix. Returns the number of messages removed. A steady conversation already ending on a completed turn is a no-op.

Safe because a completed assistant turn could only have been produced if the log before it already replayed cleanly — so nothing before the checkpoint can be malformed.



55
56
57
58
59
60
61
62
63
64
65
# File 'app/models/envoy/conversation.rb', line 55

def heal_interrupted_tail!
  ordered = messages.order(:id).to_a
  checkpoint = ordered.rindex { |m| completed_assistant_turn?(m) }
  stale = checkpoint ? ordered[(checkpoint + 1)..] : ordered.reject { |m| m.role == "system" }
  return 0 if stale.empty?

  # Newest first so a tool-result message is removed before the tool_call it
  # points at (destroyed via the assistant's dependent: :destroy).
  stale.reverse_each(&:destroy!)
  stale.size
end

#prompt_bodyObject



25
26
27
28
# File 'app/models/envoy/conversation.rb', line 25

def prompt_body
  return surface.prompt_body if surface
  prompt_key.present? ? Envoy.prompt(prompt_key).full_body : nil
end

#read_only?Boolean

A surface may pin a chat read-only; so may the conversation's own status.

Returns:

  • (Boolean)


31
32
33
# File 'app/models/envoy/conversation.rb', line 31

def read_only?
  status == "read_only" || !!surface&.read_only?
end

#resolved_model_idObject



21
22
23
# File 'app/models/envoy/conversation.rb', line 21

def resolved_model_id
  surface&.model_id || model_id || Envoy.config.default_model
end

#surfaceObject

Embed conversations resolve everything through their surface, so editing a surface reaches chats that already exist. Console conversations have no surface and fall back to their own columns.



13
14
15
# File 'app/models/envoy/conversation.rb', line 13

def surface
  surface_key.present? ? Envoy.surface(surface_key) : nil
end

#toolsetObject



17
18
19
# File 'app/models/envoy/conversation.rb', line 17

def toolset
  Envoy.toolset(surface&.toolset_key || toolset_key)
end