devin_api 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6c8ff85d6a59c3442dcf00776af4e5b8e80903f2ccdcad81f79db2a89d91cb9f
4
+ data.tar.gz: 3f14dc6752122758ae1a95915db964bc1812ce58d07afe26a02b45df1ef13457
5
+ SHA512:
6
+ metadata.gz: c99ddb448b733f01522c47664ecb8dc223792c7728a999cb87536aca2612893a7c989c21b8d89684f30e82df7210529940aed52cffb4b7de7b3a45b226b1dba9
7
+ data.tar.gz: 239eb0b9069025d187350c853d3c9ebd0bc65606dad01c32b1732fa312279d0e11a605066e823047d3f845accc2d5526ea604bb849f23d79ac4bde912f14ce89
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.1.0] - 2025-05-18
6
+
7
+ * Initial version of the Devin API client
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Masato Sugiyama
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # Devin API Client
2
+ [![Test](https://github.com/smasato/devin_api_client_rb/actions/workflows/rspec.yml/badge.svg)](https://github.com/smasato/devin_api_client_rb/actions/workflows/rspec.yml?query=branch%3Amain)
3
+
4
+ This Ruby gem is a client for the Devin API.
5
+
6
+ This gem is not officially supported by Cognition AI, Inc.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'devin_api'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ ```bash
19
+ $ bundle install
20
+ ```
21
+
22
+ Or install it yourself as:
23
+
24
+ ```bash
25
+ $ gem install devin_api
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### Endpoint-based API
31
+
32
+ ```ruby
33
+ require 'devin_api'
34
+
35
+ client = DevinApi::Client.new do |config|
36
+ config.url = 'https://api.devin.ai'
37
+ config.access_token = 'your_access_token'
38
+ end
39
+
40
+ # Sessions
41
+ sessions = client.list_sessions(limit: 10)
42
+ puts "Sessions: #{sessions}"
43
+
44
+ new_session = client.create_session(prompt: 'Build a simple web app')
45
+ session_id = new_session['id']
46
+ puts "Created session: #{session_id}"
47
+
48
+ session_details = client.get_session(session_id)
49
+ puts "Session details: #{session_details}"
50
+
51
+ message_response = client.send_message(session_id, message: 'How do I start?')
52
+ puts "Message response: #{message_response}"
53
+
54
+ # Upload files to a session
55
+ file = File.open('path/to/file.txt')
56
+ upload_response = client.upload_file(file)
57
+ puts "Upload response: #{upload_response}"
58
+
59
+ # Update session tags
60
+ tags_response = client.update_session_tags(session_id, tags: ['web', 'app'])
61
+ puts "Tags update response: #{tags_response}"
62
+
63
+ # Secrets
64
+ secrets = client.list_secrets
65
+ puts "Secrets: #{secrets}"
66
+
67
+ # Knowledge
68
+ knowledge_items = client.list_knowledge
69
+ puts "Knowledge items: #{knowledge_items}"
70
+
71
+ new_knowledge = client.create_knowledge(
72
+ name: 'API Documentation',
73
+ body: 'This is the API documentation for our service.',
74
+ trigger_description: 'When API documentation is needed'
75
+ )
76
+ knowledge_id = new_knowledge['id']
77
+ puts "Created knowledge: #{knowledge_id}"
78
+
79
+ # Enterprise (for enterprise customers)
80
+ audit_logs = client.list_audit_logs(start_time: '2023-01-01T00:00:00Z')
81
+ puts "Audit logs: #{audit_logs}"
82
+
83
+ consumption = client.get_enterprise_consumption(period: 'current_month')
84
+ puts "Consumption: #{consumption}"
85
+ ```
86
+
87
+ ### Resource-based API
88
+
89
+ ```ruby
90
+ require 'devin_api'
91
+
92
+ client = DevinApi::Client.new do |config|
93
+ config.url = 'https://api.devin.ai'
94
+ config.access_token = 'your_access_token'
95
+ end
96
+
97
+ # Resource-based usage
98
+ # Get a collection of sessions
99
+ sessions = client.sessions
100
+ sessions.each do |session|
101
+ puts "Session ID: #{session.id}, Prompt: #{session[:prompt]}"
102
+ end
103
+
104
+ # Create a new session
105
+ new_session = client.sessions.create(prompt: 'Build a simple web app')
106
+ puts "Created session: #{new_session.session_id}"
107
+
108
+ # Get a specific session
109
+ session = client.session('session_id')
110
+
111
+ # Send a message to the session
112
+ response = session.send_message('How do I start?')
113
+
114
+ # Upload files to the session
115
+ files = [File.open('path/to/file.txt')]
116
+ upload_response = session.upload_files(files)
117
+
118
+ # Update session tags
119
+ session.update_tags(['web', 'app'])
120
+
121
+ # Pagination
122
+ next_page = sessions.next_page
123
+ if next_page
124
+ next_page.each do |session|
125
+ puts "Next page session: #{session.id}"
126
+ end
127
+ end
128
+ ```
129
+
130
+ ## Contributing
131
+
132
+ Bug reports and pull requests are welcome on GitHub at https://github.com/smasato/devin_api_client_rb.
133
+
134
+ ## License
135
+
136
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday/multipart'
5
+ require 'json'
6
+
7
+ require 'devin_api/version'
8
+ require 'devin_api/configuration'
9
+ require 'devin_api/endpoints'
10
+ require 'devin_api/collection'
11
+ require 'devin_api/resources/session'
12
+ require 'devin_api/resources/secret'
13
+ require 'devin_api/resources/knowledge'
14
+ require 'devin_api/resources/enterprise'
15
+ require 'devin_api/resources/attachment'
16
+
17
+ module DevinApi
18
+ # The top-level class that handles configuration and connection to the Devin API.
19
+ class Client
20
+ include DevinApi::Endpoints
21
+ # @return [Configuration] Config instance
22
+ attr_reader :config
23
+
24
+ # Creates a new {Client} instance and yields {#config}.
25
+ #
26
+ # Requires a block to be given.
27
+ def initialize
28
+ raise ArgumentError, 'block not given' unless block_given?
29
+
30
+ @config = DevinApi::Configuration.new
31
+ yield config
32
+
33
+ check_url
34
+ set_access_token
35
+ end
36
+
37
+ # Creates a connection if there is none, otherwise returns the existing connection.
38
+ #
39
+ # @return [Faraday::Connection] Faraday connection for the client
40
+ def connection
41
+ @connection ||= build_connection
42
+ end
43
+
44
+ # Executes a GET request
45
+ # @param [String] path The path to request
46
+ # @param [Hash] params Query parameters
47
+ # @return [Hash] Response body
48
+ def get(path, params = {})
49
+ response = connection.get(path, params)
50
+ parse_response(response)
51
+ end
52
+
53
+ # Executes a POST request
54
+ # @param [String] path The path to request
55
+ # @param [Hash] params Body parameters
56
+ # @return [Hash] Response body
57
+ def post(path, params = {})
58
+ response = connection.post(path) do |req|
59
+ req.body = params.to_json
60
+ end
61
+ parse_response(response)
62
+ end
63
+
64
+ # Executes a PUT request
65
+ # @param [String] path The path to request
66
+ # @param [Hash] params Body parameters
67
+ # @return [Hash] Response body
68
+ def put(path, params = {})
69
+ response = connection.put(path) do |req|
70
+ req.body = params.to_json
71
+ end
72
+ parse_response(response)
73
+ end
74
+
75
+ # Executes a DELETE request
76
+ # @param [String] path The path to request
77
+ # @param [Hash] params Query parameters
78
+ # @return [Hash] Response body
79
+ def delete(path, params = {})
80
+ response = connection.delete(path, params)
81
+ parse_response(response)
82
+ end
83
+
84
+ protected
85
+
86
+ # Called by {#connection} to build a connection.
87
+ #
88
+ # Uses middleware according to configuration options.
89
+ def build_connection
90
+ Faraday.new(url: config.url) do |conn|
91
+ # Response middlewares
92
+ conn.use DevinApi::Middleware::Response::RaiseError
93
+ conn.response :json, content_type: /\bjson$/
94
+
95
+ # Request middlewares
96
+ conn.request :json
97
+ conn.request :multipart
98
+
99
+ # Authentication
100
+ conn.headers['Authorization'] = "Bearer #{config.access_token}"
101
+ conn.headers['User-Agent'] = "DevinApi Ruby Client/#{DevinApi::VERSION}"
102
+
103
+ conn.adapter Faraday.default_adapter
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def parse_response(response)
110
+ return nil if response.status == 204
111
+ return {} if response.body.nil? || response.body.empty?
112
+
113
+ if response.body.is_a?(String) && response.body.start_with?('{', '[')
114
+ begin
115
+ JSON.parse(response.body)
116
+ rescue JSON::ParserError
117
+ response.body
118
+ end
119
+ else
120
+ response.body
121
+ end
122
+ end
123
+
124
+ def check_url
125
+ raise ArgumentError, 'url must be provided' unless config.url
126
+
127
+ return if config.url.start_with?('https://')
128
+
129
+ raise ArgumentError, 'devin_api is ssl only; url must begin with https://'
130
+ end
131
+
132
+ def set_access_token
133
+ return if config.access_token
134
+
135
+ raise ArgumentError, 'access_token must be provided'
136
+ end
137
+
138
+ def method_missing(method, *args, &)
139
+ method_name = method.to_s
140
+
141
+ if resource_exists?(method_name.singularize)
142
+ resource_class = resource_class_for(method_name.singularize)
143
+
144
+ # Special case for 'knowledge' which is both singular and plural
145
+ return Collection.new(self, resource_class, args.first || {}) if method_name == 'knowledge'
146
+
147
+ if method_name.singularize == method_name
148
+ id = args.first
149
+ resource_class.new(self, get("#{resource_class.resource_path}/#{id}"))
150
+ else
151
+ Collection.new(self, resource_class, args.first || {})
152
+ end
153
+ else
154
+ super
155
+ end
156
+ end
157
+
158
+ def respond_to_missing?(method, include_private = false)
159
+ resource_exists?(method.to_s.singularize) || super
160
+ end
161
+
162
+ def resource_class_for(resource_name)
163
+ class_name = resource_name.capitalize
164
+ DevinApi::Resources.const_get(class_name) if DevinApi::Resources.const_defined?(class_name)
165
+ end
166
+
167
+ def resource_exists?(resource_name)
168
+ class_name = resource_name.capitalize
169
+ DevinApi::Resources.const_defined?(class_name)
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevinApi
4
+ # Collection class for handling resource lists and pagination
5
+ class Collection
6
+ include Enumerable
7
+
8
+ attr_reader :resource_class, :client, :options, :resources
9
+
10
+ def initialize(client, resource_class, options = {})
11
+ @client = client
12
+ @resource_class = resource_class
13
+ @options = options
14
+
15
+ # Process array options
16
+ @options.each do |key, value|
17
+ @options[key] = value.join(',') if value.is_a?(Array)
18
+ end
19
+ end
20
+
21
+ def each(&)
22
+ fetch if @resources.nil?
23
+ @resources.each(&)
24
+ end
25
+
26
+ def fetch
27
+ response = client.get(path, @options)
28
+ resource_key = "#{resource_class.resource_name.downcase}s"
29
+
30
+ @resources = if response[resource_key]
31
+ response[resource_key].map do |attrs|
32
+ resource_class.new(client, attrs)
33
+ end
34
+ else
35
+ []
36
+ end
37
+
38
+ @next_cursor = response['pagination'] && response['pagination']['next_cursor']
39
+ @resources
40
+ end
41
+
42
+ def find(options = {})
43
+ if options[:id]
44
+ response = client.get("#{path}/#{options[:id]}")
45
+ resource_class.new(client, response)
46
+ else
47
+ self
48
+ end
49
+ end
50
+
51
+ def next_page
52
+ return nil unless @next_cursor
53
+
54
+ new_options = @options.dup
55
+ new_options[:cursor] = @next_cursor
56
+ self.class.new(@client, @resource_class, new_options)
57
+ end
58
+
59
+ def path
60
+ resource_class.resource_path
61
+ end
62
+
63
+ def create(attributes = {})
64
+ response = client.post(path, attributes)
65
+ resource_class.new(client, response)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevinApi
4
+ # Configuration class for the Devin API client
5
+ class Configuration
6
+ # @return [String] The base URL of the Devin API
7
+ attr_accessor :url
8
+
9
+ # @return [String] The access token for authentication
10
+ attr_accessor :access_token
11
+
12
+ # @return [Logger] The logger to use
13
+ attr_accessor :logger
14
+
15
+ # @return [Hash] Additional options to pass to Faraday
16
+ attr_accessor :options
17
+
18
+ def initialize
19
+ @options = {}
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'inflection'
4
+
5
+ module DevinApi
6
+ module CoreExt
7
+ # String extensions for inflection
8
+ module StringExtensions
9
+ def singularize
10
+ Inflection.singular(self)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ String.include DevinApi::CoreExt::StringExtensions
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevinApi
4
+ module Endpoints
5
+ # Attachment endpoints for the Devin API
6
+ module Attachment
7
+ # Upload a file for Devin to work with during sessions
8
+ # @see https://docs.devin.ai/api-reference/attachments/upload-files-for-devin-to-work-with
9
+ #
10
+ # @param [File] file File object to upload
11
+ # @return [Hash] Response body
12
+ def upload_file(file)
13
+ payload = Faraday::UploadIO.new(
14
+ file.path,
15
+ file.content_type || 'application/octet-stream',
16
+ File.basename(file.path)
17
+ )
18
+
19
+ connection.post('/v1/attachments', payload).body
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevinApi
4
+ module Endpoints
5
+ # Enterprise endpoints for the Devin API
6
+ module Enterprise
7
+ # Get enterprise collection
8
+ #
9
+ # @param [Hash] options Query parameters
10
+ # @return [DevinApi::Collection] Collection of enterprise resources
11
+ def enterprise(options = {})
12
+ Collection.new(self, DevinApi::Resources::Enterprise, options)
13
+ end
14
+
15
+ # List all audit logs
16
+ # @see https://docs.devin.ai/api-reference/audit-logs/list-audit-logs
17
+ #
18
+ # @param [Hash] params Query parameters
19
+ # @option params [Integer] :limit Maximum number of logs to return (default: 100, min: 1)
20
+ # @option params [String] :before Filter logs before a specific timestamp
21
+ # @option params [String] :after Filter logs after a specific timestamp
22
+ # @return [Hash] Response body with audit logs
23
+ def list_audit_logs(params = {})
24
+ get('/v1/enterprise/audit-logs', params)
25
+ end
26
+
27
+ # Get enterprise consumption data
28
+ # @see https://docs.devin.ai/api-reference/enterprise/get-enterprise-consumption-data
29
+ #
30
+ # @param [Hash] params Query parameters
31
+ # @option params [String] :period Period for consumption data (e.g., 'current_month', 'previous_month')
32
+ # @option params [String] :start_date Start date for custom period (ISO 8601 format)
33
+ # @option params [String] :end_date End date for custom period (ISO 8601 format)
34
+ # @return [Hash] Response body with consumption data
35
+ def get_enterprise_consumption(params = {})
36
+ get('/v1/enterprise/consumption', params)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevinApi
4
+ module Endpoints
5
+ # Knowledge endpoints for the Devin API
6
+ module Knowledge
7
+ # List knowledge
8
+ # @see https://docs.devin.ai/api-reference/knowledge/list-knowledge
9
+ #
10
+ # @param [Hash] params Query parameters
11
+ # @option params [Integer] :limit Maximum number of knowledge items to return (default: 100, max: 1000)
12
+ # @option params [Integer] :offset Number of knowledge items to skip for pagination (default: 0)
13
+ # @return [Hash] Response body with knowledge and folders
14
+ def list_knowledge(params = {})
15
+ get('/v1/knowledge', params)
16
+ end
17
+
18
+ # Create knowledge
19
+ # @see https://docs.devin.ai/api-reference/knowledge/create-knowledge
20
+ #
21
+ # @param [Hash] params Body parameters
22
+ # @option params [String] :body Content of the knowledge (required)
23
+ # @option params [String] :name Name of the knowledge (required)
24
+ # @option params [String] :trigger_description Description of when this knowledge should be used (required)
25
+ # @option params [String] :parent_folder_id ID of the folder that this knowledge is located in
26
+ # @return [Hash] Response body with the created knowledge
27
+ def create_knowledge(params = {})
28
+ post('/v1/knowledge', params)
29
+ end
30
+
31
+ # Update knowledge
32
+ # @see https://docs.devin.ai/api-reference/knowledge/update-knowledge
33
+ #
34
+ # @param [String] knowledge_id The ID of the knowledge to update
35
+ # @param [Hash] params Body parameters
36
+ # @option params [String] :body Content of the knowledge
37
+ # @option params [String] :name Name of the knowledge
38
+ # @option params [String] :trigger_description Description of when this knowledge should be used
39
+ # @option params [String] :parent_folder_id ID of the folder that this knowledge is located in
40
+ # @return [Hash] Response body with the updated knowledge
41
+ def update_knowledge(knowledge_id, params = {})
42
+ put("/v1/knowledge/#{knowledge_id}", params)
43
+ end
44
+
45
+ # Delete knowledge
46
+ # @see https://docs.devin.ai/api-reference/knowledge/delete-knowledge
47
+ #
48
+ # @param [String] knowledge_id The ID of the knowledge to delete
49
+ # @return [nil] Returns nil on success (204 No Content)
50
+ def delete_knowledge(knowledge_id)
51
+ delete("/v1/knowledge/#{knowledge_id}")
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevinApi
4
+ module Endpoints
5
+ # Secrets endpoints for the Devin API
6
+ module Secrets
7
+ # List all secrets metadata
8
+ # @see https://docs.devin.ai/api-reference/sessions/list-secrets
9
+ #
10
+ # @param [Hash] params Query parameters
11
+ # @option params [Integer] :limit Maximum number of secrets to return (default: 100, max: 1000)
12
+ # @option params [Integer] :offset Number of secrets to skip for pagination (default: 0)
13
+ # @return [Hash] Response body with secrets metadata (does not return secret values)
14
+ def list_secrets(params = {})
15
+ get('/v1/secrets', params)
16
+ end
17
+
18
+ # Delete a secret
19
+ # @see https://docs.devin.ai/api-reference/sessions/delete-secret
20
+ #
21
+ # @param [String] secret_id The ID of the secret to delete
22
+ # @return [nil] Returns nil on success (204 No Content)
23
+ def delete_secret(secret_id)
24
+ delete("/v1/secrets/#{secret_id}")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevinApi
4
+ module Endpoints
5
+ # Session endpoint for the Devin API
6
+ module Session
7
+ # Retrieve details about an existing session
8
+ # @see https://docs.devin.ai/api-reference/sessions/retrieve-details-about-an-existing-session
9
+ #
10
+ # @param [String] session_id The ID of the session
11
+ # @return [Hash] Response body
12
+ def get_session(session_id)
13
+ get("/v1/session/#{session_id}")
14
+ end
15
+
16
+ # Send a message to an existing session
17
+ # @see https://docs.devin.ai/api-reference/sessions/send-a-message-to-an-existing-devin-session
18
+ #
19
+ # @param [String] session_id The ID of the session
20
+ # @param [Hash] params Body parameters
21
+ # @option params [String] :message The message to send (required)
22
+ # @return [Hash] Response body
23
+ def send_message(session_id, params = {})
24
+ post("/v1/session/#{session_id}/messages", params)
25
+ end
26
+
27
+ # Update session tags
28
+ # @see https://docs.devin.ai/api-reference/sessions/update-session-tags
29
+ #
30
+ # @param [String] session_id The ID of the session
31
+ # @param [Hash] params Body parameters
32
+ # @option params [Array<String>] :tags Tags to associate with the session (required)
33
+ # @return [Hash] Response body
34
+ def update_session_tags(session_id, params = {})
35
+ put("/v1/session/#{session_id}/tags", params)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevinApi
4
+ module Endpoints
5
+ # Sessions endpoints for the Devin API
6
+ module Sessions
7
+ # List all sessions
8
+ # @see https://docs.devin.ai/api-reference/sessions/list-sessions
9
+ #
10
+ # @param [Hash] params Query parameters
11
+ # @option params [Integer] :limit Maximum number of sessions to return (default: 100, max: 1000)
12
+ # @option params [Integer] :offset Number of sessions to skip for pagination (default: 0)
13
+ # @option params [Array<String>] :tags Filter sessions by tags
14
+ # @return [Hash] Response body
15
+ def list_sessions(params = {})
16
+ get('/v1/sessions', params)
17
+ end
18
+
19
+ # Create a new session
20
+ # @see https://docs.devin.ai/api-reference/sessions/create-a-new-devin-session
21
+ #
22
+ # @param [Hash] params Body parameters
23
+ # @option params [String] :prompt The task description for Devin (required)
24
+ # @option params [String, nil] :snapshot_id ID of a machine snapshot to use
25
+ # @option params [Boolean, nil] :unlisted Whether the session should be unlisted
26
+ # @option params [Boolean, nil] :idempotent Enable idempotent session creation
27
+ # @option params [Integer, nil] :max_acu_limit Maximum ACU limit for the session
28
+ # @option params [Array<String>, nil] :secret_ids Array of secret IDs to use. If nil, use all secrets. If empty array, use no secrets.
29
+ # @option params [Array<String>, nil] :knowledge_ids Array of knowledge IDs to use. If nil, use all knowledge. If empty array, use no knowledge.
30
+ # @option params [Array<String>, nil] :tags Array of tags to add to the session.
31
+ # @option params [String, nil] :title Custom title for the session. If nil, a title will be generated automatically.
32
+ # @return [Hash] Response body
33
+ def create_session(params = {})
34
+ post('/v1/sessions', params)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'devin_api/endpoints/sessions'
4
+ require 'devin_api/endpoints/session'
5
+ require 'devin_api/endpoints/secrets'
6
+ require 'devin_api/endpoints/knowledge'
7
+ require 'devin_api/endpoints/enterprise'
8
+ require 'devin_api/endpoints/attachment'
9
+
10
+ module DevinApi
11
+ # Endpoints module for the Devin API
12
+ module Endpoints
13
+ # Include all endpoint modules
14
+ def self.included(base)
15
+ base.include(Sessions)
16
+ base.include(Session)
17
+ base.include(Secrets)
18
+ base.include(Knowledge)
19
+ base.include(Enterprise)
20
+ base.include(Attachment)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevinApi
4
+ module Error
5
+ # Base class for all errors
6
+ class Error < StandardError
7
+ attr_reader :response
8
+
9
+ def initialize(response = nil)
10
+ @response = response
11
+ super(build_message)
12
+ end
13
+
14
+ private
15
+
16
+ def build_message
17
+ return response if response.is_a?(String)
18
+ return nil unless response
19
+
20
+ message = "Status: #{response[:status]}"
21
+
22
+ message += " - #{response[:body]['error']}" if response[:body].is_a?(Hash) && response[:body]['error']
23
+
24
+ message
25
+ end
26
+ end
27
+
28
+ class ClientError < Error; end
29
+ class ServerError < Error; end
30
+ class BadRequest < ClientError; end
31
+ class Unauthorized < ClientError; end
32
+ class Forbidden < ClientError; end
33
+ class NotFound < ClientError; end
34
+ class RateLimited < ClientError; end
35
+ end
36
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevinApi
4
+ module Middleware
5
+ module Response
6
+ # Middleware for raising errors based on HTTP status
7
+ class RaiseError < Faraday::Middleware
8
+ def call(env)
9
+ @app.call(env).on_complete do |response|
10
+ handle_error_response(response)
11
+ end
12
+ rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
13
+ raise DevinApi::Error::ClientError, "Connection error: #{e.message}"
14
+ end
15
+
16
+ private
17
+
18
+ def handle_error_response(response)
19
+ status = response[:status]
20
+ case status
21
+ when 400 then raise DevinApi::Error::BadRequest, response
22
+ when 401 then raise DevinApi::Error::Unauthorized, response
23
+ when 403 then raise DevinApi::Error::Forbidden, response
24
+ when 404 then raise DevinApi::Error::NotFound, response
25
+ when 429 then raise DevinApi::Error::RateLimited, response
26
+ when 400..499 then raise DevinApi::Error::ClientError, response
27
+ when 500..599 then raise DevinApi::Error::ServerError, response
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'devin_api/resources/base'
4
+
5
+ module DevinApi
6
+ module Resources
7
+ # Attachment resource for the Devin API
8
+ class Attachment < Base
9
+ def path
10
+ '/v1/attachments'
11
+ end
12
+
13
+ def upload_file(file)
14
+ payload = Faraday::UploadIO.new(
15
+ file.path,
16
+ file.content_type || 'application/octet-stream',
17
+ File.basename(file.path)
18
+ )
19
+
20
+ client.connection.post(path, payload).body
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevinApi
4
+ module Resources
5
+ # Base class for all API resources
6
+ class Base
7
+ attr_reader :client, :attributes
8
+
9
+ def initialize(client, attributes = {})
10
+ @client = client
11
+ @attributes = attributes
12
+ end
13
+
14
+ def [](key)
15
+ @attributes[key.to_s] || @attributes[key.to_sym]
16
+ end
17
+
18
+ def []=(key, value)
19
+ @attributes[key.to_sym] = value
20
+ end
21
+
22
+ def id
23
+ self[:id]
24
+ end
25
+
26
+ def path
27
+ "#{self.class.resource_path}/#{id}"
28
+ end
29
+
30
+ def to_hash
31
+ @attributes.dup
32
+ end
33
+
34
+ def method_missing(method, *_args)
35
+ if @attributes.key?(method.to_s)
36
+ @attributes[method.to_s]
37
+ elsif @attributes.key?(method.to_sym)
38
+ @attributes[method.to_sym]
39
+ end
40
+ end
41
+
42
+ def respond_to_missing?(method, include_private = false)
43
+ @attributes.key?(method.to_s) || @attributes.key?(method.to_sym) || super
44
+ end
45
+
46
+ class << self
47
+ def resource_name
48
+ @resource_name ||= name.split('::').last.downcase
49
+ end
50
+
51
+ def resource_path
52
+ "/v1/#{resource_name.gsub('_', '-')}s"
53
+ end
54
+
55
+ def create(client, attributes = {})
56
+ response = client.post(resource_path, attributes)
57
+ new(client, response)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'devin_api/resources/base'
4
+
5
+ module DevinApi
6
+ module Resources
7
+ # Enterprise resource for the Devin API
8
+ class Enterprise < Base
9
+ def path
10
+ '/v1/enterprise'
11
+ end
12
+
13
+ def audit_logs(params = {})
14
+ client.get("#{path}/audit-logs", params)
15
+ end
16
+
17
+ def consumption(params = {})
18
+ client.get("#{path}/consumption", params)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'devin_api/resources/base'
4
+
5
+ module DevinApi
6
+ module Resources
7
+ # Knowledge resource for the Devin API
8
+ class Knowledge < Base
9
+ def path
10
+ "/v1/knowledge/#{id}"
11
+ end
12
+
13
+ def update(attributes = {})
14
+ client.put(path, attributes)
15
+ end
16
+
17
+ def delete
18
+ client.delete(path)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'devin_api/resources/base'
4
+
5
+ module DevinApi
6
+ module Resources
7
+ # Secret resource for the Devin API
8
+ class Secret < Base
9
+ def path
10
+ "/v1/secrets/#{id}"
11
+ end
12
+
13
+ def delete
14
+ client.delete(path)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'devin_api/resources/base'
4
+
5
+ module DevinApi
6
+ module Resources
7
+ # Session resource for the Devin API
8
+ class Session < Base
9
+ def path
10
+ "/v1/session/#{session_id}"
11
+ end
12
+
13
+ def send_message(message)
14
+ client.post("#{path}/messages", { message: message })
15
+ end
16
+
17
+ def update_tags(tags)
18
+ client.put("#{path}/tags", { tags: tags })
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevinApi
4
+ VERSION = '0.1.0'
5
+ end
data/lib/devin_api.rb ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevinApi; end
4
+
5
+ require 'faraday'
6
+ require 'faraday/multipart'
7
+ require 'inflection'
8
+
9
+ require 'devin_api/version'
10
+ require 'devin_api/core_ext/inflection'
11
+ require 'devin_api/error'
12
+ require 'devin_api/middleware/response/raise_error'
13
+ require 'devin_api/client'
14
+ require 'devin_api/endpoints'
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: devin_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Masato Sugiyama
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-05-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday-multipart
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: hashie
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: inflection
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ description: Unofficial Ruby client library for the Devin API
70
+ email:
71
+ - public@smasato.net
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - CHANGELOG.md
77
+ - LICENSE
78
+ - README.md
79
+ - lib/devin_api.rb
80
+ - lib/devin_api/client.rb
81
+ - lib/devin_api/collection.rb
82
+ - lib/devin_api/configuration.rb
83
+ - lib/devin_api/core_ext/inflection.rb
84
+ - lib/devin_api/endpoints.rb
85
+ - lib/devin_api/endpoints/attachment.rb
86
+ - lib/devin_api/endpoints/enterprise.rb
87
+ - lib/devin_api/endpoints/knowledge.rb
88
+ - lib/devin_api/endpoints/secrets.rb
89
+ - lib/devin_api/endpoints/session.rb
90
+ - lib/devin_api/endpoints/sessions.rb
91
+ - lib/devin_api/error.rb
92
+ - lib/devin_api/middleware/response/raise_error.rb
93
+ - lib/devin_api/resources/attachment.rb
94
+ - lib/devin_api/resources/base.rb
95
+ - lib/devin_api/resources/enterprise.rb
96
+ - lib/devin_api/resources/knowledge.rb
97
+ - lib/devin_api/resources/secret.rb
98
+ - lib/devin_api/resources/session.rb
99
+ - lib/devin_api/version.rb
100
+ homepage: https://github.com/smasato/devin_api_client_rb
101
+ licenses:
102
+ - MIT
103
+ metadata:
104
+ homepage_uri: https://github.com/smasato/devin_api_client_rb
105
+ source_code_uri: https://github.com/smasato/devin_api_client_rb
106
+ changelog_uri: https://github.com/smasato/devin_api_client_rb/blob/main/CHANGELOG.md
107
+ rubygems_mfa_required: 'true'
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 3.2.0
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubygems_version: 3.5.22
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Unofficial Ruby client for the Devin API
127
+ test_files: []