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.}", error: 'request_failed') end |