Module: ForceDream::Invoke

Defined in:
lib/force_dream/invoke.rb

Overview

Ported precisely from @forcedream/mcp-server's invoke_agent.ts (via the same logic already proven in every other SDK tonight) -- exact endpoints, exact polling interval ramp (starts 2500ms, +1000ms per attempt, capped at 6000ms), exact status handling. Invokes ONCE; never re-invokes on timeout (would double-charge) -- returns a pollable task_id instead.

Class Method Summary collapse

Class Method Details

.invoke_agent_polling(api_base:, api_key:, agent_slug:, task:, max_wait_seconds: 60) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/force_dream/invoke.rb', line 19

def invoke_agent_polling(api_base:, api_key:, agent_slug:, task:, max_wait_seconds: 60)
  max_wait_ms = [5, [120, max_wait_seconds].min].max * 1000
  encoded_slug = CGI.escape(agent_slug)

  inv = Http.post_result("#{api_base}/v1/agents/#{encoded_slug}/invoke", body: { task: task }, bearer: api_key)

  return InvokeResult.new(status: 'error', agent: agent_slug, message: 'Invalid API key (401).', error: 'invalid_key') if inv.status == 401

  task_id = inv.json['task_id']
  unless task_id
    err_msg = inv.json['error'] || inv.json['note'] || 'no task_id'
    return InvokeResult.new(status: 'error', agent: agent_slug, message: "Invoke failed (HTTP #{inv.status}): #{err_msg}", error: 'invoke_failed')
  end

  encoded_task_id = CGI.escape(task_id)
  start_ms = (Time.now.to_f * 1000).to_i
  interval_ms = 2500

  while ((Time.now.to_f * 1000).to_i - start_ms) < max_wait_ms
    sleep(interval_ms / 1000.0)

    poll = Http.get_result("#{api_base}/v1/agents/#{encoded_slug}/result/#{encoded_task_id}", bearer: api_key)
    d = poll.json
    poll_status = d['status'] || d['outcome'] || ''
    ok_true = d['ok'] == true

    if %w[completed succeeded].include?(poll_status) || ok_true
      output = d['output']
      insufficient = d['outcome'] == 'insufficient' || (output.is_a?(Hash) && output['confidence'] == 'insufficient')

      if insufficient
        return InvokeResult.new(status: 'insufficient', agent: agent_slug, task_id: task_id, output: output, charged_pence: 0,
                                 message: 'Agent returned insufficient evidence and declined rather than fabricate. Charged nothing.')
      end

      charged = d['charged_pence']
      proof_id = d['proof_id'] || task_id
      return InvokeResult.new(status: 'completed', agent: agent_slug, task_id: task_id, output: output, charged_pence: charged,
                               proof_id: proof_id, message: "Completed. Charged #{charged || 0}p. Cryptographically proven (proof_id #{proof_id}).")
    end

    if poll_status == 'insufficient'
      return InvokeResult.new(status: 'insufficient', agent: agent_slug, task_id: task_id, output: d['output'], charged_pence: 0,
                               message: 'Agent declined (insufficient evidence). Charged nothing.')
    end

    if poll_status == 'charge_failed'
      reason = d['reason'] || 'insufficient_balance'
      return InvokeResult.new(status: 'error', agent: agent_slug, task_id: task_id, charged_pence: 0, error: 'charge_failed',
                               message: "Charge failed: #{reason}. Nothing charged or delivered. Top up and retry.")
    end

    if %w[failed dead_letter].include?(poll_status)
      reason = d['reason'] || d['last_error'] || 'unknown'
      return InvokeResult.new(status: 'error', agent: agent_slug, task_id: task_id, message: "Task #{poll_status}: #{reason}", error: poll_status)
    end

    interval_ms = [interval_ms + 1000, 6000].min
  end

  InvokeResult.new(status: 'pending', agent: agent_slug, task_id: task_id,
                    message: "Still processing after #{max_wait_ms / 1000}s. Not re-invoked (would double-charge). Poll the result later with this task_id.")
rescue StandardError => e
  InvokeResult.new(status: 'error', agent: agent_slug, message: "Invoke request failed: #{e.message}", error: 'request_failed')
end