jules-ruby 0.0.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.jules/bolt.md +4 -0
- data/.rubocop.yml +51 -0
- data/AGENTS.md +250 -0
- data/CHANGELOG.md +20 -0
- data/CONTRIBUTING.md +82 -0
- data/LICENSE +21 -0
- data/README.md +330 -0
- data/Rakefile +70 -0
- data/SECURITY.md +41 -0
- data/assets/banner.png +0 -0
- data/bin/jules-ruby +7 -0
- data/jules-ruby.gemspec +43 -0
- data/lib/jules-ruby/cli/activities.rb +142 -0
- data/lib/jules-ruby/cli/banner.rb +113 -0
- data/lib/jules-ruby/cli/base.rb +38 -0
- data/lib/jules-ruby/cli/interactive/activity_renderer.rb +81 -0
- data/lib/jules-ruby/cli/interactive/session_creator.rb +112 -0
- data/lib/jules-ruby/cli/interactive/session_manager.rb +285 -0
- data/lib/jules-ruby/cli/interactive/source_manager.rb +65 -0
- data/lib/jules-ruby/cli/interactive.rb +48 -0
- data/lib/jules-ruby/cli/prompts.rb +184 -0
- data/lib/jules-ruby/cli/sessions.rb +185 -0
- data/lib/jules-ruby/cli/sources.rb +72 -0
- data/lib/jules-ruby/cli.rb +127 -0
- data/lib/jules-ruby/client.rb +130 -0
- data/lib/jules-ruby/configuration.rb +20 -0
- data/lib/jules-ruby/errors.rb +35 -0
- data/lib/jules-ruby/models/activity.rb +137 -0
- data/lib/jules-ruby/models/artifact.rb +78 -0
- data/lib/jules-ruby/models/github_branch.rb +17 -0
- data/lib/jules-ruby/models/github_repo.rb +31 -0
- data/lib/jules-ruby/models/plan.rb +23 -0
- data/lib/jules-ruby/models/plan_step.rb +25 -0
- data/lib/jules-ruby/models/pull_request.rb +23 -0
- data/lib/jules-ruby/models/session.rb +111 -0
- data/lib/jules-ruby/models/source.rb +23 -0
- data/lib/jules-ruby/models/source_context.rb +35 -0
- data/lib/jules-ruby/resources/activities.rb +76 -0
- data/lib/jules-ruby/resources/base.rb +27 -0
- data/lib/jules-ruby/resources/sessions.rb +125 -0
- data/lib/jules-ruby/resources/sources.rb +61 -0
- data/lib/jules-ruby/version.rb +5 -0
- data/lib/jules-ruby.rb +43 -0
- data/mise.toml +2 -0
- metadata +232 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'async'
|
|
4
|
+
require 'async/http/internet'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'uri'
|
|
7
|
+
|
|
8
|
+
module JulesRuby
|
|
9
|
+
class Client
|
|
10
|
+
attr_reader :configuration
|
|
11
|
+
|
|
12
|
+
DEFAULT_HEADERS = {
|
|
13
|
+
'Content-Type' => 'application/json',
|
|
14
|
+
'Accept' => 'application/json'
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def initialize(api_key: nil, base_url: nil, timeout: nil)
|
|
18
|
+
@configuration = JulesRuby.configuration&.dup || Configuration.new
|
|
19
|
+
|
|
20
|
+
@configuration.api_key = api_key if api_key
|
|
21
|
+
@configuration.base_url = base_url if base_url
|
|
22
|
+
@configuration.timeout = timeout if timeout
|
|
23
|
+
|
|
24
|
+
validate_configuration!
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Resource accessors
|
|
28
|
+
def sources
|
|
29
|
+
@sources ||= Resources::Sources.new(self)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def sessions
|
|
33
|
+
@sessions ||= Resources::Sessions.new(self)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def activities
|
|
37
|
+
@activities ||= Resources::Activities.new(self)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# HTTP methods
|
|
41
|
+
def get(path, params: {})
|
|
42
|
+
request(:get, path, params: params)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def post(path, body: {})
|
|
46
|
+
request(:post, path, body: body)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def delete(path)
|
|
50
|
+
request(:delete, path)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def validate_configuration!
|
|
56
|
+
return if configuration.valid?
|
|
57
|
+
|
|
58
|
+
raise ConfigurationError,
|
|
59
|
+
'API key is required. Set JULES_API_KEY environment variable or pass api_key to Client.new'
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def request(method, path, params: {}, body: nil)
|
|
63
|
+
url = build_url(path, params)
|
|
64
|
+
|
|
65
|
+
Async do
|
|
66
|
+
internet = Async::HTTP::Internet.new
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
headers = build_headers
|
|
70
|
+
|
|
71
|
+
response = case method
|
|
72
|
+
when :get
|
|
73
|
+
internet.get(url, headers)
|
|
74
|
+
when :post
|
|
75
|
+
internet.post(url, headers, body ? JSON.generate(body) : nil)
|
|
76
|
+
when :delete
|
|
77
|
+
internet.delete(url, headers)
|
|
78
|
+
else
|
|
79
|
+
raise ArgumentError, "Unsupported HTTP method: #{method}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
handle_response(response)
|
|
83
|
+
ensure
|
|
84
|
+
internet.close
|
|
85
|
+
end
|
|
86
|
+
end.wait
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def build_url(path, params)
|
|
90
|
+
# Ensure base_url ends without slash and path starts with slash
|
|
91
|
+
base = configuration.base_url.chomp('/')
|
|
92
|
+
path = "/#{path}" unless path.start_with?('/')
|
|
93
|
+
|
|
94
|
+
uri = URI.parse("#{base}#{path}")
|
|
95
|
+
uri.query = URI.encode_www_form(params.compact) unless params.empty?
|
|
96
|
+
uri.to_s
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def build_headers
|
|
100
|
+
# Optimization: Reuse DEFAULT_HEADERS hash to avoid multiple array allocations per request
|
|
101
|
+
headers = DEFAULT_HEADERS.dup
|
|
102
|
+
headers['X-Goog-Api-Key'] = configuration.api_key
|
|
103
|
+
headers
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def handle_response(response)
|
|
107
|
+
body = response.read
|
|
108
|
+
status = response.status
|
|
109
|
+
|
|
110
|
+
case status
|
|
111
|
+
when 200..299
|
|
112
|
+
body.nil? || body.empty? ? {} : JSON.parse(body)
|
|
113
|
+
when 400
|
|
114
|
+
raise BadRequestError.new('Bad request', status_code: status, response: body)
|
|
115
|
+
when 401
|
|
116
|
+
raise AuthenticationError.new('Invalid API key', status_code: status, response: body)
|
|
117
|
+
when 403
|
|
118
|
+
raise ForbiddenError.new('Access forbidden', status_code: status, response: body)
|
|
119
|
+
when 404
|
|
120
|
+
raise NotFoundError.new('Resource not found', status_code: status, response: body)
|
|
121
|
+
when 429
|
|
122
|
+
raise RateLimitError.new('Rate limit exceeded', status_code: status, response: body)
|
|
123
|
+
when 500..599
|
|
124
|
+
raise ServerError.new('Server error', status_code: status, response: body)
|
|
125
|
+
else
|
|
126
|
+
raise Error.new("Unexpected response: #{status}", status_code: status, response: body)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JulesRuby
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :api_key, :base_url, :timeout
|
|
6
|
+
|
|
7
|
+
DEFAULT_BASE_URL = 'https://jules.googleapis.com/v1alpha'
|
|
8
|
+
DEFAULT_TIMEOUT = 30
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@api_key = ENV.fetch('JULES_API_KEY', nil)
|
|
12
|
+
@base_url = DEFAULT_BASE_URL
|
|
13
|
+
@timeout = DEFAULT_TIMEOUT
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def valid?
|
|
17
|
+
!api_key.nil? && !api_key.empty?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JulesRuby
|
|
4
|
+
# Base error class
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
attr_reader :response, :status_code
|
|
7
|
+
|
|
8
|
+
def initialize(message = nil, response: nil, status_code: nil)
|
|
9
|
+
@response = response
|
|
10
|
+
@status_code = status_code
|
|
11
|
+
super(message)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# 400 Bad Request
|
|
16
|
+
class BadRequestError < Error; end
|
|
17
|
+
|
|
18
|
+
# 401 Unauthorized
|
|
19
|
+
class AuthenticationError < Error; end
|
|
20
|
+
|
|
21
|
+
# 403 Forbidden
|
|
22
|
+
class ForbiddenError < Error; end
|
|
23
|
+
|
|
24
|
+
# 404 Not Found
|
|
25
|
+
class NotFoundError < Error; end
|
|
26
|
+
|
|
27
|
+
# 429 Too Many Requests
|
|
28
|
+
class RateLimitError < Error; end
|
|
29
|
+
|
|
30
|
+
# 5xx Server Errors
|
|
31
|
+
class ServerError < Error; end
|
|
32
|
+
|
|
33
|
+
# Configuration error
|
|
34
|
+
class ConfigurationError < Error; end
|
|
35
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JulesRuby
|
|
4
|
+
module Models
|
|
5
|
+
class Activity
|
|
6
|
+
ORIGINATORS = %w[user agent system].freeze
|
|
7
|
+
|
|
8
|
+
attr_reader :name, :id, :description, :create_time, :originator, :artifacts,
|
|
9
|
+
:agent_messaged, :user_messaged, :plan_generated, :plan_approved,
|
|
10
|
+
:progress_updated, :session_completed, :session_failed
|
|
11
|
+
|
|
12
|
+
def initialize(data)
|
|
13
|
+
@name = data['name']
|
|
14
|
+
@id = data['id']
|
|
15
|
+
@description = data['description']
|
|
16
|
+
@create_time = data['createTime']
|
|
17
|
+
@originator = data['originator']
|
|
18
|
+
@artifacts = (data['artifacts'] || []).map { |a| Artifact.new(a) }
|
|
19
|
+
|
|
20
|
+
# Activity type (union field)
|
|
21
|
+
@agent_messaged = data['agentMessaged']
|
|
22
|
+
@user_messaged = data['userMessaged']
|
|
23
|
+
@plan_generated = parse_plan_generated(data['planGenerated'])
|
|
24
|
+
@plan_approved = data['planApproved']
|
|
25
|
+
@progress_updated = data['progressUpdated']
|
|
26
|
+
@session_completed = data['sessionCompleted']
|
|
27
|
+
@session_failed = data['sessionFailed']
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def type
|
|
31
|
+
if agent_messaged
|
|
32
|
+
:agent_messaged
|
|
33
|
+
elsif user_messaged
|
|
34
|
+
:user_messaged
|
|
35
|
+
elsif plan_generated
|
|
36
|
+
:plan_generated
|
|
37
|
+
elsif plan_approved
|
|
38
|
+
:plan_approved
|
|
39
|
+
elsif progress_updated
|
|
40
|
+
:progress_updated
|
|
41
|
+
elsif session_completed
|
|
42
|
+
:session_completed
|
|
43
|
+
elsif session_failed
|
|
44
|
+
:session_failed
|
|
45
|
+
else
|
|
46
|
+
:unknown
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Type check helpers
|
|
51
|
+
def agent_message?
|
|
52
|
+
!agent_messaged.nil?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def user_message?
|
|
56
|
+
!user_messaged.nil?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def plan_generated?
|
|
60
|
+
!plan_generated.nil?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def plan_approved?
|
|
64
|
+
!plan_approved.nil?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def progress_update?
|
|
68
|
+
!progress_updated.nil?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def session_completed?
|
|
72
|
+
!session_completed.nil?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def session_failed?
|
|
76
|
+
!session_failed.nil?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def from_agent?
|
|
80
|
+
originator == 'agent'
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def from_user?
|
|
84
|
+
originator == 'user'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def from_system?
|
|
88
|
+
originator == 'system'
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Content helpers
|
|
92
|
+
def message
|
|
93
|
+
agent_messaged&.dig('agentMessage') || user_messaged&.dig('userMessage')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def plan
|
|
97
|
+
plan_generated
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def approved_plan_id
|
|
101
|
+
plan_approved&.dig('planId')
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def progress_title
|
|
105
|
+
progress_updated&.dig('title')
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def progress_description
|
|
109
|
+
progress_updated&.dig('description')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def failure_reason
|
|
113
|
+
session_failed&.dig('reason')
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def to_h
|
|
117
|
+
{
|
|
118
|
+
name: name,
|
|
119
|
+
id: id,
|
|
120
|
+
description: description,
|
|
121
|
+
create_time: create_time,
|
|
122
|
+
originator: originator,
|
|
123
|
+
type: type,
|
|
124
|
+
artifacts: artifacts.map(&:to_h)
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
def parse_plan_generated(data)
|
|
131
|
+
return nil unless data
|
|
132
|
+
|
|
133
|
+
data['plan'] ? Plan.new(data['plan']) : nil
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JulesRuby
|
|
4
|
+
module Models
|
|
5
|
+
class Artifact
|
|
6
|
+
attr_reader :change_set, :media, :bash_output
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@change_set = data['changeSet']
|
|
10
|
+
@media = data['media']
|
|
11
|
+
@bash_output = data['bashOutput']
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def type
|
|
15
|
+
if change_set
|
|
16
|
+
:change_set
|
|
17
|
+
elsif media
|
|
18
|
+
:media
|
|
19
|
+
elsif bash_output
|
|
20
|
+
:bash_output
|
|
21
|
+
else
|
|
22
|
+
:unknown
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# ChangeSet helpers
|
|
27
|
+
def source
|
|
28
|
+
change_set&.dig('source')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def git_patch
|
|
32
|
+
change_set&.dig('gitPatch')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def unidiff_patch
|
|
36
|
+
git_patch&.dig('unidiffPatch')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def base_commit_id
|
|
40
|
+
git_patch&.dig('baseCommitId')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def suggested_commit_message
|
|
44
|
+
git_patch&.dig('suggestedCommitMessage')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Media helpers
|
|
48
|
+
def media_data
|
|
49
|
+
media&.dig('data')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def media_mime_type
|
|
53
|
+
media&.dig('mimeType')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# BashOutput helpers
|
|
57
|
+
def bash_command
|
|
58
|
+
bash_output&.dig('command')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def bash_output_text
|
|
62
|
+
bash_output&.dig('output')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def bash_exit_code
|
|
66
|
+
bash_output&.dig('exitCode')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def to_h
|
|
70
|
+
{
|
|
71
|
+
change_set: change_set,
|
|
72
|
+
media: media,
|
|
73
|
+
bash_output: bash_output
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JulesRuby
|
|
4
|
+
module Models
|
|
5
|
+
class GitHubBranch
|
|
6
|
+
attr_reader :display_name
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@display_name = data['displayName']
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_h
|
|
13
|
+
{ display_name: display_name }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JulesRuby
|
|
4
|
+
module Models
|
|
5
|
+
class GitHubRepo
|
|
6
|
+
attr_reader :owner, :repo, :is_private, :default_branch, :branches
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@owner = data['owner']
|
|
10
|
+
@repo = data['repo']
|
|
11
|
+
@is_private = data['isPrivate']
|
|
12
|
+
@default_branch = data['defaultBranch'] ? GitHubBranch.new(data['defaultBranch']) : nil
|
|
13
|
+
@branches = (data['branches'] || []).map { |b| GitHubBranch.new(b) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_h
|
|
17
|
+
{
|
|
18
|
+
owner: owner,
|
|
19
|
+
repo: repo,
|
|
20
|
+
is_private: is_private,
|
|
21
|
+
default_branch: default_branch&.to_h,
|
|
22
|
+
branches: branches.map(&:to_h)
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def full_name
|
|
27
|
+
"#{owner}/#{repo}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JulesRuby
|
|
4
|
+
module Models
|
|
5
|
+
class Plan
|
|
6
|
+
attr_reader :id, :steps, :create_time
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@id = data['id']
|
|
10
|
+
@steps = (data['steps'] || []).map { |s| PlanStep.new(s) }
|
|
11
|
+
@create_time = data['createTime']
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_h
|
|
15
|
+
{
|
|
16
|
+
id: id,
|
|
17
|
+
steps: steps.map(&:to_h),
|
|
18
|
+
create_time: create_time
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JulesRuby
|
|
4
|
+
module Models
|
|
5
|
+
class PlanStep
|
|
6
|
+
attr_reader :id, :title, :description, :index
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@id = data['id']
|
|
10
|
+
@title = data['title']
|
|
11
|
+
@description = data['description']
|
|
12
|
+
@index = data['index']
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_h
|
|
16
|
+
{
|
|
17
|
+
id: id,
|
|
18
|
+
title: title,
|
|
19
|
+
description: description,
|
|
20
|
+
index: index
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JulesRuby
|
|
4
|
+
module Models
|
|
5
|
+
class PullRequest
|
|
6
|
+
attr_reader :url, :title, :description
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@url = data['url']
|
|
10
|
+
@title = data['title']
|
|
11
|
+
@description = data['description']
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_h
|
|
15
|
+
{
|
|
16
|
+
url: url,
|
|
17
|
+
title: title,
|
|
18
|
+
description: description
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JulesRuby
|
|
4
|
+
module Models
|
|
5
|
+
class Session
|
|
6
|
+
# Session states
|
|
7
|
+
STATES = %w[
|
|
8
|
+
STATE_UNSPECIFIED
|
|
9
|
+
QUEUED
|
|
10
|
+
PLANNING
|
|
11
|
+
AWAITING_PLAN_APPROVAL
|
|
12
|
+
AWAITING_USER_FEEDBACK
|
|
13
|
+
IN_PROGRESS
|
|
14
|
+
PAUSED
|
|
15
|
+
FAILED
|
|
16
|
+
COMPLETED
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
# Automation modes
|
|
20
|
+
AUTOMATION_MODES = %w[
|
|
21
|
+
AUTOMATION_MODE_UNSPECIFIED
|
|
22
|
+
AUTO_CREATE_PR
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
attr_reader :name, :id, :prompt, :title, :source_context, :state,
|
|
26
|
+
:url, :outputs, :create_time, :update_time,
|
|
27
|
+
:require_plan_approval, :automation_mode
|
|
28
|
+
|
|
29
|
+
def initialize(data)
|
|
30
|
+
@name = data['name']
|
|
31
|
+
@id = data['id']
|
|
32
|
+
@prompt = data['prompt']
|
|
33
|
+
@title = data['title']
|
|
34
|
+
@source_context = data['sourceContext'] ? SourceContext.new(data['sourceContext']) : nil
|
|
35
|
+
@state = data['state']
|
|
36
|
+
@url = data['url']
|
|
37
|
+
@create_time = data['createTime']
|
|
38
|
+
@update_time = data['updateTime']
|
|
39
|
+
@require_plan_approval = data['requirePlanApproval']
|
|
40
|
+
@automation_mode = data['automationMode']
|
|
41
|
+
@outputs = parse_outputs(data['outputs'])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_h
|
|
45
|
+
{
|
|
46
|
+
name: name,
|
|
47
|
+
id: id,
|
|
48
|
+
prompt: prompt,
|
|
49
|
+
title: title,
|
|
50
|
+
source_context: source_context&.to_h,
|
|
51
|
+
state: state,
|
|
52
|
+
url: url,
|
|
53
|
+
outputs: outputs.map(&:to_h),
|
|
54
|
+
create_time: create_time,
|
|
55
|
+
update_time: update_time
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# State check helpers
|
|
60
|
+
def queued?
|
|
61
|
+
state == 'QUEUED'
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def planning?
|
|
65
|
+
state == 'PLANNING'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def awaiting_plan_approval?
|
|
69
|
+
state == 'AWAITING_PLAN_APPROVAL'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def awaiting_user_feedback?
|
|
73
|
+
state == 'AWAITING_USER_FEEDBACK'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def in_progress?
|
|
77
|
+
state == 'IN_PROGRESS'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def paused?
|
|
81
|
+
state == 'PAUSED'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def failed?
|
|
85
|
+
state == 'FAILED'
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def completed?
|
|
89
|
+
state == 'COMPLETED'
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def active?
|
|
93
|
+
%w[QUEUED PLANNING AWAITING_PLAN_APPROVAL AWAITING_USER_FEEDBACK IN_PROGRESS].include?(state)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def parse_outputs(outputs_data)
|
|
99
|
+
return [] unless outputs_data
|
|
100
|
+
|
|
101
|
+
outputs_data.map do |output|
|
|
102
|
+
if output['pullRequest']
|
|
103
|
+
PullRequest.new(output['pullRequest'])
|
|
104
|
+
else
|
|
105
|
+
output
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JulesRuby
|
|
4
|
+
module Models
|
|
5
|
+
class Source
|
|
6
|
+
attr_reader :name, :id, :github_repo
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@name = data['name']
|
|
10
|
+
@id = data['id']
|
|
11
|
+
@github_repo = data['githubRepo'] ? GitHubRepo.new(data['githubRepo']) : nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_h
|
|
15
|
+
{
|
|
16
|
+
name: name,
|
|
17
|
+
id: id,
|
|
18
|
+
github_repo: github_repo&.to_h
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JulesRuby
|
|
4
|
+
module Models
|
|
5
|
+
class SourceContext
|
|
6
|
+
attr_reader :source, :github_repo_context
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@source = data['source']
|
|
10
|
+
@github_repo_context = data['githubRepoContext']
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def starting_branch
|
|
14
|
+
github_repo_context&.dig('startingBranch')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_h
|
|
18
|
+
{
|
|
19
|
+
source: source,
|
|
20
|
+
github_repo_context: github_repo_context
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Build a SourceContext hash for API requests
|
|
25
|
+
def self.build(source:, starting_branch:)
|
|
26
|
+
{
|
|
27
|
+
'source' => source,
|
|
28
|
+
'githubRepoContext' => {
|
|
29
|
+
'startingBranch' => starting_branch
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|