Class: FloopFloop::Projects

Inherits:
Object
  • Object
show all
Defined in:
lib/floopfloop/projects.rb

Instance Method Summary collapse

Constructor Details

#initialize(client) ⇒ Projects

Returns a new instance of Projects.



7
8
9
# File 'lib/floopfloop/projects.rb', line 7

def initialize(client)
  @client = client
end

Instance Method Details

#cancel(ref) ⇒ Object



51
52
53
54
# File 'lib/floopfloop/projects.rb', line 51

def cancel(ref)
  @client.request("POST", "/api/v1/projects/#{url_encode(ref)}/cancel")
  nil
end

#conversations(ref, limit: nil) ⇒ Object



74
75
76
77
# File 'lib/floopfloop/projects.rb', line 74

def conversations(ref, limit: nil)
  query = limit && limit.positive? ? { limit: limit } : nil
  @client.request("GET", "/api/v1/projects/#{url_encode(ref)}/conversations", query: query)
end

#create(prompt:, **opts) ⇒ Object

POST /api/v1/projects

Input keys: :prompt (required), :name, :subdomain, :bot_type, :is_auth_protected, :team_id. Returns the raw backend shape: { “project” => …, “deployment” => … }.



16
17
18
19
20
21
22
23
24
# File 'lib/floopfloop/projects.rb', line 16

def create(prompt:, **opts)
  body = { prompt: prompt }
  body[:name]            = opts[:name]             if opts.key?(:name)
  body[:subdomain]       = opts[:subdomain]        if opts.key?(:subdomain)
  body[:botType]         = opts[:bot_type]         if opts.key?(:bot_type)
  body[:isAuthProtected] = opts[:is_auth_protected] if opts.key?(:is_auth_protected)
  body[:teamId]          = opts[:team_id]          if opts.key?(:team_id)
  @client.request("POST", "/api/v1/projects", body: body)
end

#get(ref, team_id: nil) ⇒ Object

Fetch a single project by id or subdomain. No dedicated backend route —filters list() locally, matching the other SDKs.



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/floopfloop/projects.rb', line 33

def get(ref, team_id: nil)
  list(team_id: team_id).find do |p|
    p["id"] == ref || p["subdomain"] == ref
  end.tap do |match|
    unless match
      raise FloopFloop::Error.new(
        code: "NOT_FOUND",
        message: "project not found: #{ref}",
        status: 404,
      )
    end
  end
end

#list(team_id: nil) ⇒ Object



26
27
28
29
# File 'lib/floopfloop/projects.rb', line 26

def list(team_id: nil)
  query = team_id ? { teamId: team_id } : nil
  @client.request("GET", "/api/v1/projects", query: query)
end

#reactivate(ref) ⇒ Object



56
57
58
59
# File 'lib/floopfloop/projects.rb', line 56

def reactivate(ref)
  @client.request("POST", "/api/v1/projects/#{url_encode(ref)}/reactivate")
  nil
end

#refine(ref, message:, **opts) ⇒ Object

Refine returns one of three response shapes. Rather than raising on “unexpected” ones we return the raw hash — callers can inspect .fetch(“queued”) / .fetch(“processing”) to branch. Matches the Python / Go / Node SDKs’ behaviour where the caller decides.



65
66
67
68
69
70
71
72
# File 'lib/floopfloop/projects.rb', line 65

def refine(ref, message:, **opts)
  body = { message: message }
  if opts.key?(:attachments)
    body[:attachments] = opts[:attachments]
  end
  body[:codeEditOnly] = opts[:code_edit_only] if opts.key?(:code_edit_only)
  @client.request("POST", "/api/v1/projects/#{url_encode(ref)}/refine", body: body)
end

#status(ref) ⇒ Object



47
48
49
# File 'lib/floopfloop/projects.rb', line 47

def status(ref)
  @client.request("GET", "/api/v1/projects/#{url_encode(ref)}/status")
end

#stream(ref, interval: 2, max_wait: 600) {|status_hash| ... } ⇒ Hash

Poll the status endpoint, yielding each de-duplicated snapshot to the block until a terminal state (live / failed / cancelled), the max_wait elapses, or the block raises / breaks.

Events are de-duplicated on (status, step, progress, queuePosition) so callers don’t see dozens of identical “queued” snapshots.

Yields:

  • (status_hash)

    every unique event (status/step/progress/queue)

Returns:

  • (Hash)

    the final status hash on “live”

Raises:



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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
133
134
# File 'lib/floopfloop/projects.rb', line 89

def stream(ref, interval: 2, max_wait: 600)
  deadline  = Time.now + max_wait
  last_key  = nil
  last_event = nil

  loop do
    if Time.now >= deadline
      raise FloopFloop::Error.new(
        code: "TIMEOUT",
        message: "stream: project #{ref} did not reach a terminal state within #{max_wait}s",
      )
    end

    event = status(ref)
    key = dedup_key(event)
    if key != last_key
      last_key = key
      yield(event) if block_given?
    end
    last_event = event

    case event["status"]
    when "live", "archived"
      # Both are terminal-success states. Archived projects are the
      # post-active form (still hydrated, just not running) — matches
      # the Node, Python, Swift, and Kotlin SDKs' handling.
      return event
    when "failed"
      raise FloopFloop::Error.new(
        code: "BUILD_FAILED",
        message: event["message"].to_s.empty? ? "build failed" : event["message"],
      )
    when "cancelled"
      raise FloopFloop::Error.new(
        code: "BUILD_CANCELLED",
        message: event["message"].to_s.empty? ? "build cancelled" : event["message"],
      )
    end

    remaining = deadline - Time.now
    sleep_for = [interval, remaining].min
    sleep(sleep_for) if sleep_for.positive?
  end

  last_event
end

#wait_for_live(ref, interval: 2, max_wait: 600) ⇒ Object

Block until the project reaches ‘live’ and return the hydrated project hash. Wraps #stream with a no-op block.



138
139
140
141
# File 'lib/floopfloop/projects.rb', line 138

def wait_for_live(ref, interval: 2, max_wait: 600)
  stream(ref, interval: interval, max_wait: max_wait)
  get(ref)
end