Class: Envoy::Surface

Inherits:
Object
  • Object
show all
Defined in:
lib/envoy/surface.rb

Overview

Binds a page to an agent: which tools, which prompt, which model, and how to describe what the user is looking at.

A surface is the source of truth for a conversation opened against it — toolset, prompt and model are read here on every turn, so editing a surface reaches conversations that already exist.

References are stored as keys and resolved lazily (Envoy.validate! checks them in one pass at boot), so definition load order does not matter.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key) ⇒ Surface

Returns a new instance of Surface.



14
15
16
17
18
19
20
21
# File 'lib/envoy/surface.rb', line 14

def initialize(key)
  @key = key.to_s
  @toolset_key = nil
  @model_id = nil
  @read_only = false
  @prompt = nil
  @context_block = nil
end

Instance Attribute Details

#keyObject (readonly)

Returns the value of attribute key.



12
13
14
# File 'lib/envoy/surface.rb', line 12

def key
  @key
end

Instance Method Details

#context(&block) ⇒ Object

Runs on every turn, in the job — so it gets actor and key and nothing else. No request, no params, no session. This is also where a host enforces that this actor may see this subject: raise Envoy::Forbidden and Guard handles it.



48
49
50
# File 'lib/envoy/surface.rb', line 48

def context(&block)
  @context_block = block
end

#context_for(actor:, key:) ⇒ Object



65
66
67
68
# File 'lib/envoy/surface.rb', line 65

def context_for(actor:, key:)
  return nil unless @context_block
  @context_block.call(actor: actor, key: key)
end

#model(id = nil) ⇒ Object



29
30
31
32
# File 'lib/envoy/surface.rb', line 29

def model(id = nil)
  return @model_id if id.nil?
  @model_id = id
end

#model_idObject



54
# File 'lib/envoy/surface.rb', line 54

def model_id    = @model_id

#prompt_bodyObject



57
58
59
60
61
62
63
# File 'lib/envoy/surface.rb', line 57

def prompt_body
  case @prompt
  when nil    then nil
  when String then @prompt
  else Envoy.prompt(@prompt).full_body
  end
end

#read_only(value = nil) ⇒ Object



34
35
36
37
# File 'lib/envoy/surface.rb', line 34

def read_only(value = nil)
  return @read_only if value.nil?
  @read_only = value
end

#read_only?Boolean

Returns:

  • (Boolean)


55
# File 'lib/envoy/surface.rb', line 55

def read_only?  = !!@read_only

#system_prompt(ref = nil) ⇒ Object

A registry key (Symbol) or a literal String.



40
41
42
43
# File 'lib/envoy/surface.rb', line 40

def system_prompt(ref = nil)
  return @prompt if ref.nil?
  @prompt = ref
end

#toolset(name = nil) ⇒ Object

--- DSL ---



24
25
26
27
# File 'lib/envoy/surface.rb', line 24

def toolset(name = nil)
  return @toolset_key if name.nil?
  @toolset_key = name.to_s
end

#toolset_keyObject

--- introspection ---



53
# File 'lib/envoy/surface.rb', line 53

def toolset_key = @toolset_key

#validate!Object

Resolve every lazy reference, naming this surface in any failure.

Raises:



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/envoy/surface.rb', line 71

def validate!
  raise Envoy::Error, "surface #{key.inspect} has no toolset" if @toolset_key.blank?

  begin
    # tools_for walks the whole `use` graph, not just this toolset's own
    # tools — so a typo in a composed toolset's `use` list fails here,
    # at boot, instead of surfacing mid-conversation inside RunJob.
    Envoy.toolset(@toolset_key).tools_for
  rescue Envoy::UnknownToolset, Envoy::ToolsetCycle => e
    raise e.class, "surface #{key.inspect}: #{e.message}"
  end

  begin
    prompt_body
  rescue Envoy::UnknownPrompt => e
    raise Envoy::UnknownPrompt, "surface #{key.inspect}: #{e.message}"
  end
end