runapi-core 0.3.5 → 0.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 01303e8c4435de080fde0aa485b59ee5bca4ce29e532796b8d7d1540368f4b26
4
- data.tar.gz: 39a789fb54bfa15a94475ac47ab50e6c815ac8561ab688acffccd2c92f3fbbc5
3
+ metadata.gz: ba473ca16c3bf3642549224cc1e8e2ace81c302f91a767afc10b06b93cde95ce
4
+ data.tar.gz: a01ba0a52c124fd881b45705ce8a7580e9b429ca776842693bc1ad262cf85804
5
5
  SHA512:
6
- metadata.gz: 45a68064b4442e093ae896a7d65f1b0c8b30ce6d8d438b9ce7a90d62959134de1b62f25a19efbc6466b069cdd98515170fba2ed7742b2db0f17ea0f09821a784
7
- data.tar.gz: 46d4313b136bd5f11a25bec4364b1f2c80f8ca7439668f7dc272514cb7807b7d52218e356218538fd3ec0666e6ea94961c259e190699712da95b44476b013f64
6
+ metadata.gz: 908a114d22a7113898f4ec49595c40b2f414c02c4c528dba774b3262a871a429627d2337b6516a1923ae25662b38ee3f1352a9b30b31900d0d91e4ab5ad1863c
7
+ data.tar.gz: 36a9539bb562174708f6d94354b2e4f2c4fe742773665d8603b8c77ff6ed6019e9c1c848ba0decf970e6cd96bec50def79d398057dc748a4bd8bc7ee97511d60
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # RunAPI Core Ruby SDK
2
2
 
3
- The RunAPI Core Ruby SDK provides shared authentication, HTTP, retry, error, and polling primitives for RunAPI model gems. Install `runapi-core` only when you are building SDK infrastructure or shared Ruby tooling; application code should normally install a concrete model gem such as `runapi-suno`.
3
+ The RunAPI Core Ruby SDK provides shared authentication, HTTP, retry, error, Files, Uploads, and polling primitives for RunAPI model gems. Install `runapi-core` only when you are building SDK infrastructure or shared Ruby tooling; application code should normally install a concrete model gem such as `runapi-suno`.
4
4
 
5
5
  ## Install
6
6
 
@@ -67,7 +67,7 @@ runapi_task_id = response.runapi_task_id
67
67
  runapi_task_id = response.response_headers["X-RunAPI-Task-Id"]
68
68
  ```
69
69
 
70
- ## File Upload
70
+ ## Temporary File Upload
71
71
 
72
72
  ```ruby
73
73
  client = RunApi::NanoBanana::Client.new(api_key: ENV["RUNAPI_API_KEY"])
@@ -79,6 +79,25 @@ puts upload.url
79
79
  > [!IMPORTANT]
80
80
  > Uploaded file URLs expire 1 hour after creation. Pass them to a model promptly rather than storing them for later use.
81
81
 
82
+ ## Persistent Files And Multipart Uploads
83
+
84
+ The existing `files.create` method above keeps its temporary URL behavior. Use `create_file` for a persistent File object and `uploads` when sending one or more Parts:
85
+
86
+ ```ruby
87
+ file = client.files.create_file(file: "./knowledge.pdf")
88
+ content = client.files.content(file.id)
89
+
90
+ upload = client.uploads.create(
91
+ bytes: 1_048_576,
92
+ filename: "archive.bin",
93
+ mime_type: "application/octet-stream"
94
+ )
95
+ part = client.uploads.add_part(upload.id, data: "./archive.part-01")
96
+ completed = client.uploads.complete(upload.id, part_ids: [part.id])
97
+ ```
98
+
99
+ Use `files.list`, `retrieve`, and `delete_file` for the remaining File lifecycle. See https://runapi.ai/docs/resources/files for limits and REST examples.
100
+
82
101
  ## License
83
102
 
84
103
  Licensed under the Apache License, Version 2.0.
@@ -3,18 +3,20 @@
3
3
  module RunApi
4
4
  module Core
5
5
  # Base class for every RunAPI client. Resolves the API key, builds the shared
6
- # HTTP client, and exposes the Universal Resources (file upload, account, pricing) that
6
+ # HTTP client, and exposes the Universal Resources (Files, Uploads, account, pricing) that
7
7
  # are available on any client regardless of which model gem was required.
8
8
  #
9
9
  # Provider clients inherit from this and build their model resources from the
10
10
  # protected +http+ reader.
11
11
  class Client
12
- # @return [Files] Temporary file upload operations.
12
+ # @return [Files] Persistent File lifecycle and temporary URL upload operations.
13
13
  attr_reader :files
14
14
  # @return [Account] Account info and balance operations.
15
15
  attr_reader :account
16
16
  # @return [Pricing] Live Price Schedule lookup and Price Quote operations.
17
17
  attr_reader :pricing
18
+ # @return [Uploads] Multipart Upload lifecycle operations.
19
+ attr_reader :uploads
18
20
 
19
21
  def initialize(api_key: nil, **options)
20
22
  @api_key = Core::Auth.resolve_api_key(api_key)
@@ -24,6 +26,7 @@ module RunApi
24
26
  @files = Files.new(@http)
25
27
  @account = Account.new(@http)
26
28
  @pricing = Pricing.new(@http)
29
+ @uploads = Uploads.new(@http)
27
30
  end
28
31
 
29
32
  # Closes the SDK-created HTTP client. Injected clients remain caller-owned.
@@ -10,6 +10,7 @@ module RunApi
10
10
  ENDPOINT = "/api/v1/files"
11
11
  PREPARE_ENDPOINT = "#{ENDPOINT}/prepare"
12
12
  CONFIRM_ENDPOINT = "#{ENDPOINT}/confirm"
13
+ PROTOCOL_ENDPOINT = "/v1/files"
13
14
 
14
15
  class UploadResponse < RunApi::Core::BaseModel
15
16
  required :file_name, String
@@ -20,6 +21,30 @@ module RunApi
20
21
  required :expires_at, String
21
22
  end
22
23
 
24
+ class FileObject < RunApi::Core::BaseModel
25
+ required :id, String
26
+ required :object, String
27
+ required :bytes, Integer
28
+ required :created_at, Integer
29
+ optional :expires_at, Integer
30
+ required :filename, String
31
+ required :purpose, String
32
+ end
33
+
34
+ class FileList < RunApi::Core::BaseModel
35
+ required :object, String
36
+ required :data, [FileObject]
37
+ optional :first_id, String
38
+ optional :last_id, String
39
+ required :has_more
40
+ end
41
+
42
+ class DeletedFile < RunApi::Core::BaseModel
43
+ required :id, String
44
+ required :object, String
45
+ required :deleted
46
+ end
47
+
23
48
  RESPONSE_CLASS = UploadResponse
24
49
 
25
50
  def initialize(http)
@@ -34,6 +59,33 @@ module RunApi
34
59
  request(:post, ENDPOINT, body: compact_params(source:, file_name:), options:)
35
60
  end
36
61
 
62
+ def create_file(file:, purpose: "user_data", file_name: nil, options: nil)
63
+ path = file_path(file)
64
+ body = MultipartBody.new(
65
+ fields: {purpose:},
66
+ files: {file: MultipartFile.new(path:, filename: file_name || File.basename(path))}
67
+ )
68
+ request(:post, PROTOCOL_ENDPOINT, body:, options:, response_class: FileObject)
69
+ end
70
+
71
+ def list(after_id: nil, limit: nil, order: nil, purpose: nil, options: nil)
72
+ query = URI.encode_www_form(compact_params(after: after_id, limit:, order:, purpose:))
73
+ path = query.empty? ? PROTOCOL_ENDPOINT : "#{PROTOCOL_ENDPOINT}?#{query}"
74
+ request(:get, path, options:, response_class: FileList)
75
+ end
76
+
77
+ def retrieve(file_id, options: nil)
78
+ request(:get, protocol_file_path(file_id), options:, response_class: FileObject)
79
+ end
80
+
81
+ def content(file_id, options: nil)
82
+ @http.request(:get, "#{protocol_file_path(file_id)}/content", options:, raw: true)
83
+ end
84
+
85
+ def delete_file(file_id, options: nil)
86
+ request(:delete, protocol_file_path(file_id), options:, response_class: DeletedFile)
87
+ end
88
+
37
89
  private
38
90
 
39
91
  def validate_source!(file:, source:)
@@ -71,6 +123,12 @@ module RunApi
71
123
 
72
124
  raise ArgumentError, "file must be a file path or respond to :path"
73
125
  end
126
+
127
+ def protocol_file_path(file_id)
128
+ raise ArgumentError, "file_id is required" if file_id.to_s.strip.empty?
129
+
130
+ "#{PROTOCOL_ENDPOINT}/#{URI.encode_www_form_component(file_id)}"
131
+ end
74
132
  end
75
133
  end
76
134
  end
@@ -12,7 +12,7 @@ module RunApi
12
12
  end
13
13
  end
14
14
 
15
- def request(method, path, body: nil, options: nil)
15
+ def request(method, path, body: nil, options: nil, raw: false)
16
16
  uri = URI.join(@options.base_url, path)
17
17
  req = build_request(method, uri, body, options)
18
18
  max_retries = options&.max_retries || @options.max_retries
@@ -42,6 +42,8 @@ module RunApi
42
42
  end
43
43
 
44
44
  if response.is_a?(Net::HTTPSuccess)
45
+ return response.body if raw
46
+
45
47
  body = parse_body(response.body)
46
48
  return nil if body.nil?
47
49
  return body unless body.is_a?(Hash) || body.is_a?(Array)
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunApi
4
+ module Core
5
+ class Uploads
6
+ include RunApi::Core::ResourceHelpers
7
+
8
+ ENDPOINT = "/v1/uploads"
9
+
10
+ class UploadObject < RunApi::Core::BaseModel
11
+ required :id, String
12
+ required :object, String
13
+ required :bytes, Integer
14
+ required :created_at, Integer
15
+ required :filename, String
16
+ required :purpose, String
17
+ required :status, String
18
+ required :expires_at, Integer
19
+ optional :file, -> { Files::FileObject }
20
+ end
21
+
22
+ class UploadPart < RunApi::Core::BaseModel
23
+ required :id, String
24
+ required :object, String
25
+ required :created_at, Integer
26
+ required :upload_id, String
27
+ end
28
+
29
+ RESPONSE_CLASS = UploadObject
30
+
31
+ def initialize(http)
32
+ @http = http
33
+ end
34
+
35
+ def create(bytes:, filename:, mime_type:, purpose: "user_data", options: nil)
36
+ request(:post, ENDPOINT, body: {bytes:, filename:, mime_type:, purpose:}, options:)
37
+ end
38
+
39
+ def add_part(upload_id, data:, file_name: nil, options: nil)
40
+ path = file_path(data)
41
+ body = MultipartBody.new(
42
+ files: {data: MultipartFile.new(path:, filename: file_name || File.basename(path))}
43
+ )
44
+ request(:post, "#{upload_path(upload_id)}/parts", body:, options:, response_class: UploadPart)
45
+ end
46
+
47
+ def complete(upload_id, part_ids:, options: nil)
48
+ request(:post, "#{upload_path(upload_id)}/complete", body: {part_ids:}, options:)
49
+ end
50
+
51
+ def cancel(upload_id, options: nil)
52
+ request(:post, "#{upload_path(upload_id)}/cancel", body: {}, options:)
53
+ end
54
+
55
+ private
56
+
57
+ def file_path(file)
58
+ return file if file.is_a?(String)
59
+ return file.path if file.respond_to?(:path)
60
+
61
+ raise ArgumentError, "data must be a file path or respond to :path"
62
+ end
63
+
64
+ def upload_path(upload_id)
65
+ raise ArgumentError, "upload_id is required" if upload_id.to_s.strip.empty?
66
+
67
+ "#{ENDPOINT}/#{URI.encode_www_form_component(upload_id)}"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module RunApi
4
4
  module Core
5
- VERSION = "0.3.5"
5
+ VERSION = "0.4.1"
6
6
  end
7
7
 
8
8
  VERSION = Core::VERSION
data/lib/runapi/core.rb CHANGED
@@ -20,6 +20,7 @@ require_relative "core/http_client"
20
20
  require_relative "core/polling"
21
21
  require_relative "core/resource_helpers"
22
22
  require_relative "core/files"
23
+ require_relative "core/uploads"
23
24
  require_relative "core/account"
24
25
  require_relative "core/pricing"
25
26
  require_relative "core/client"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: runapi-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - RunAPI
@@ -30,9 +30,9 @@ dependencies:
30
30
  - !ruby/object:Gem::Version
31
31
  version: '4'
32
32
  description: The RunAPI Core Ruby SDK provides shared authentication, HTTP, retry,
33
- error, and polling primitives for RunAPI model gems. Install `runapi-core` only
34
- when you are building SDK infrastructure or shared Ruby tooling; application code
35
- should normally install a concrete model gem such as `runapi-suno`.
33
+ error, Files, Uploads, and polling primitives for RunAPI model gems. Install `runapi-core`
34
+ only when you are building SDK infrastructure or shared Ruby tooling; application
35
+ code should normally install a concrete model gem such as `runapi-suno`.
36
36
  email:
37
37
  - contact@runapi.ai
38
38
  executables: []
@@ -59,6 +59,7 @@ files:
59
59
  - lib/runapi/core/resource_helpers.rb
60
60
  - lib/runapi/core/response.rb
61
61
  - lib/runapi/core/types.rb
62
+ - lib/runapi/core/uploads.rb
62
63
  - lib/runapi/core/version.rb
63
64
  homepage: https://runapi.ai/models
64
65
  licenses: