Class: Legion::Gaia::Channels::TeamsAdapter

Inherits:
Legion::Gaia::ChannelAdapter show all
Includes:
Logging::Helper
Defined in:
lib/legion/gaia/channels/teams_adapter.rb

Constant Summary collapse

CAPABILITIES =
%i[rich_text adaptive_cards proactive_messaging mobile desktop mentions].freeze
MOBILE_CAPABILITIES =
%i[rich_text adaptive_cards mobile mentions].freeze
DESKTOP_CAPABILITIES =
%i[rich_text adaptive_cards desktop mentions file_attachment].freeze

Constants inherited from Legion::Gaia::ChannelAdapter

Legion::Gaia::ChannelAdapter::DIRECT_ADDRESS_PATTERN

Instance Attribute Summary collapse

Attributes inherited from Legion::Gaia::ChannelAdapter

#capabilities, #channel_id

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Legion::Gaia::ChannelAdapter

inherited, register_adapter, #start, #started?, #stop, #supports?

Constructor Details

#initialize(app_id: nil, default_conversation_id: nil) ⇒ TeamsAdapter

Returns a new instance of TeamsAdapter.



29
30
31
32
33
34
35
# File 'lib/legion/gaia/channels/teams_adapter.rb', line 29

def initialize(app_id: nil, default_conversation_id: nil)
  super(channel_id: :teams, capabilities: CAPABILITIES)
  @app_id = app_id
  @default_conversation_id = default_conversation_id
  @conversation_store = Teams::ConversationStore.new
  @last_presence_status = nil
end

Instance Attribute Details

#app_idObject (readonly)

Returns the value of attribute app_id.



18
19
20
# File 'lib/legion/gaia/channels/teams_adapter.rb', line 18

def app_id
  @app_id
end

#conversation_storeObject (readonly)

Returns the value of attribute conversation_store.



18
19
20
# File 'lib/legion/gaia/channels/teams_adapter.rb', line 18

def conversation_store
  @conversation_store
end

#last_presence_statusObject (readonly)

Returns the value of attribute last_presence_status.



18
19
20
# File 'lib/legion/gaia/channels/teams_adapter.rb', line 18

def last_presence_status
  @last_presence_status
end

Class Method Details

.from_settings(settings) ⇒ Object



20
21
22
23
24
25
26
27
# File 'lib/legion/gaia/channels/teams_adapter.rb', line 20

def self.from_settings(settings)
  return nil unless settings&.dig(:channels, :teams, :enabled)

  new(
    app_id: settings.dig(:channels, :teams, :app_id),
    default_conversation_id: settings.dig(:channels, :teams, :default_conversation_id)
  )
end

Instance Method Details

#create_proactive_conversation(user_id:, tenant_id: nil) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/legion/gaia/channels/teams_adapter.rb', line 104

def create_proactive_conversation(user_id:, tenant_id: nil)
  profile = conversation_store.(user_id)
  service_url = profile&.service_url
  resolved_tenant = tenant_id || profile&.tenant_id
  return { error: :no_service_url } unless service_url
  return { error: :bot_runner_not_available } unless bot_runner_available?

  bot = Legion::Extensions::MicrosoftTeams::Client.new
  result = bot.create_conversation(
    service_url: service_url,
    bot_id: app_id,
    user_id: user_id,
    tenant_id: resolved_tenant
  )
  return result if result.is_a?(Hash) && result[:error]

  conversation_id = result[:conversation_id] || result['id']
  conversation_store.store(
    conversation_id: conversation_id,
    service_url: service_url,
    tenant_id: resolved_tenant
  )
  log.info("TeamsAdapter created proactive conversation user_id=#{user_id} conversation_id=#{conversation_id}")
  conversation_id
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'gaia.channels.teams_adapter.create_proactive_conversation',
                      user_id: user_id, tenant_id: resolved_tenant)
  { error: :create_conversation_failed, message: e.message }
end

#deliver(rendered_content, conversation_id: nil) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/legion/gaia/channels/teams_adapter.rb', line 80

def deliver(rendered_content, conversation_id: nil)
  resolved_id = conversation_id || @default_conversation_id
  ref = resolved_id && conversation_store.lookup(resolved_id)
  unless ref
    log.error("TeamsAdapter deliver failed conversation_id=#{resolved_id} error=no_conversation_reference")
    return { error: :no_conversation_reference }
  end

  log.info("TeamsAdapter delivering conversation_id=#{ref.conversation_id}")
  deliver_via_bot(rendered_content, ref)
end

#deliver_proactive(output_frame) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
# File 'lib/legion/gaia/channels/teams_adapter.rb', line 92

def deliver_proactive(output_frame)
  user_id = output_frame.[:target_user]
  return { error: :no_target_user } unless user_id

  conversation_id = resolve_proactive_conversation(user_id)
  return conversation_id if conversation_id.is_a?(Hash) && conversation_id[:error]

  rendered = translate_outbound(output_frame)
  log.info("TeamsAdapter proactive delivery user_id=#{user_id} conversation_id=#{conversation_id}")
  deliver(rendered, conversation_id: conversation_id)
end

#translate_inbound(activity) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/legion/gaia/channels/teams_adapter.rb', line 42

def translate_inbound(activity)
  return nil unless activity.is_a?(Hash)

  identity = Teams::BotFrameworkAuth.extract_identity(activity)
  conversation = activity['conversation'] || activity[:conversation] || {}
  text = activity['text'] || activity[:text] || ''
  text = strip_mention(text, activity)

  conversation_store.store_from_activity(activity)
  log.debug("TeamsAdapter translated inbound activity_id=#{activity['id'] || activity[:id]}")

  InputFrame.new(
    content: text.strip,
    channel_id: :teams,
    content_type: detect_content_type(activity),
    channel_capabilities: capabilities_for_device(activity),
    device_context: build_device_context(activity),
    auth_context: build_auth_context(identity, activity),
    metadata: {
      source_type: :human_direct,
      salience: 0.9,
      **(text.strip),
      conversation_id: conversation['id'] || conversation[:id],
      activity_id: activity['id'] || activity[:id],
      activity_type: activity['type'] || activity[:type]
    }
  )
end

#translate_outbound(output_frame) ⇒ Object



71
72
73
74
75
76
77
78
# File 'lib/legion/gaia/channels/teams_adapter.rb', line 71

def translate_outbound(output_frame)
  content = output_frame.content.to_s
  if output_frame.content_type == :adaptive_card
    { type: 'adaptive_card', card: output_frame.content }
  else
    { type: 'text', text: content }
  end
end

#update_presence_status(status) ⇒ Object



37
38
39
40
# File 'lib/legion/gaia/channels/teams_adapter.rb', line 37

def update_presence_status(status)
  @last_presence_status = status
  log.info("TeamsAdapter presence updated status=#{status}")
end

#validate_inbound(token, allow_emulator: false) ⇒ Object



134
135
136
137
138
# File 'lib/legion/gaia/channels/teams_adapter.rb', line 134

def validate_inbound(token, allow_emulator: false)
  return { valid: false, error: :no_app_id } unless app_id

  Teams::BotFrameworkAuth.validate_token(token, app_id: app_id, allow_emulator: allow_emulator)
end