buble 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 +7 -0
- data/LICENSE +21 -0
- data/README.md +273 -0
- data/docs/technical-design.md +145 -0
- data/examples/anthropic_messages.rb +16 -0
- data/examples/app_generation.rb +14 -0
- data/examples/gemini_generate.rb +18 -0
- data/examples/image_to_image.rb +22 -0
- data/examples/openai_chat.rb +15 -0
- data/examples/text_to_image.rb +16 -0
- data/examples/text_to_video.rb +17 -0
- data/lib/buble/apps.rb +72 -0
- data/lib/buble/chat.rb +101 -0
- data/lib/buble/client.rb +36 -0
- data/lib/buble/errors.rb +53 -0
- data/lib/buble/files.rb +60 -0
- data/lib/buble/generations.rb +101 -0
- data/lib/buble/http.rb +213 -0
- data/lib/buble/media_models.rb +13 -0
- data/lib/buble/streaming.rb +97 -0
- data/lib/buble/version.rb +5 -0
- data/lib/buble.rb +8 -0
- data/tools/live_smoke.rb +18 -0
- metadata +113 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9a7f0b94c29ce6c1dac4973776d3963d6c5089930b6401a6a4dfd770bb204860
|
|
4
|
+
data.tar.gz: d5235b3f9911f2f731f9d09902e198887464e7cecd8e1743bd3b2b5df5f3289b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 54f916c30d5162bd18582f01cfd1c33f902d2ea64517e6727dc756621146c8d67a0ff6fe5fd153843df9c84acef6ccc82782ca133ef64965cb26d2f38d91215b
|
|
7
|
+
data.tar.gz: befb6e7610c07676ed1ced3ee4ac6a8e2bc366dd044629536d49a6acf45bb27235e3c37ba8e771ed1ccf85c11985d472b04346af219ea2ef581e72da51258520
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Buble
|
|
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,273 @@
|
|
|
1
|
+
# Buble SDK for Ruby
|
|
2
|
+
|
|
3
|
+
Official Ruby SDK for [Buble](https://buble.ai/), built for the [Buble public API](https://buble.ai/docs).
|
|
4
|
+
|
|
5
|
+
Use this SDK from server-side Ruby applications to discover media models, upload source media, create asynchronous image and video generation tasks, run preconfigured Buble app workflows, and call chat models through OpenAI, Anthropic Messages, and Gemini-compatible API formats.
|
|
6
|
+
|
|
7
|
+
Keep API keys on the server. Do not expose `BUBLE_API_KEY` in browser, mobile, or other client-side code.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
After publication to RubyGems:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
gem install buble
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Bundler:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
gem "buble"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The gem requires Ruby 3.3+ and has no runtime dependencies outside the Ruby standard library.
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
Set your API key:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
export BUBLE_API_KEY="sk_..."
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The generation examples below create real Buble generation tasks and may consume credits.
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
require "buble"
|
|
37
|
+
|
|
38
|
+
client = Buble::Client.new
|
|
39
|
+
|
|
40
|
+
task = client.generations.create(
|
|
41
|
+
model: "google/nano-banana",
|
|
42
|
+
mode: "text_to_image",
|
|
43
|
+
prompt: "A cinematic product photo of a matte black espresso cup",
|
|
44
|
+
aspect_ratio: "1:1",
|
|
45
|
+
output_format: "png"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
result = client.generations.wait(task.dig("data", "id"))
|
|
49
|
+
puts result.dig("data", "result", "images", 0, "url")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The client reads `BUBLE_API_KEY` and `BUBLE_BASE_URL` from the environment when omitted.
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
client = Buble::Client.new(
|
|
58
|
+
api_key: "sk_...",
|
|
59
|
+
base_url: "https://buble.ai",
|
|
60
|
+
timeout: 60,
|
|
61
|
+
headers: {
|
|
62
|
+
"X-Request-Id" => "request-id"
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Discover Media Models
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
models = client.media_models.list(media_type: "video")
|
|
71
|
+
|
|
72
|
+
models.fetch("data", []).each do |model|
|
|
73
|
+
puts model["model"]
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Use media model discovery as the source of truth for model keys, modes, required inputs, and public parameters. New Buble models can become available without an SDK release.
|
|
78
|
+
|
|
79
|
+
## Upload Files
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
upload = Buble::FileUpload.from_path("reference.png", content_type: "image/png")
|
|
83
|
+
|
|
84
|
+
uploaded = client.files.upload(
|
|
85
|
+
upload,
|
|
86
|
+
file_type: "image",
|
|
87
|
+
model: "google/nano-banana",
|
|
88
|
+
mode: "image_to_image"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
task = client.generations.create(
|
|
92
|
+
model: "google/nano-banana",
|
|
93
|
+
mode: "image_to_image",
|
|
94
|
+
prompt: "Turn this reference into a polished ecommerce hero image.",
|
|
95
|
+
image_urls: [uploaded.dig("data", "url")]
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Uploads support local paths, IO objects, and `Buble::FileUpload`. Path uploads are streamed from disk.
|
|
100
|
+
|
|
101
|
+
## Video Generation
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
task = client.generations.create(
|
|
105
|
+
model: "gork/grok-imagine-video",
|
|
106
|
+
mode: "text_to_video",
|
|
107
|
+
prompt: "A slow cinematic shot of a futuristic train station at sunrise.",
|
|
108
|
+
duration: "5s",
|
|
109
|
+
resolution: "480p",
|
|
110
|
+
aspect_ratio: "16:9"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
result = client.generations.wait(
|
|
114
|
+
task.dig("data", "id"),
|
|
115
|
+
interval: 2,
|
|
116
|
+
timeout: 900
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
puts result.dig("data", "result", "videos", 0, "url")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Generation request bodies use Buble's flat public API shape. Put model-specific controls in keyword arguments; the SDK serializes those controls at the JSON request root.
|
|
123
|
+
|
|
124
|
+
Do not send internal Buble fields such as `input`, `options`, `scene`, `sub_mode_id`, `provider`, `mediaType`, or `media_type`.
|
|
125
|
+
|
|
126
|
+
## Apps
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
app = client.apps.retrieve("video-background-remover")
|
|
130
|
+
puts app.dig("data", "input_parameters")
|
|
131
|
+
|
|
132
|
+
task = client.apps.generations.create("video-background-remover", {
|
|
133
|
+
"source_video" => ["https://example.com/source.mp4"],
|
|
134
|
+
"refine_foreground_edges" => true,
|
|
135
|
+
"subject_is_person" => true
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
result = client.apps.generations.wait("video-background-remover", task.dig("data", "id"))
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Apps are preconfigured workflows. Only send parameter names returned by `client.apps.list` or `client.apps.retrieve(...)`.
|
|
142
|
+
|
|
143
|
+
## Chat
|
|
144
|
+
|
|
145
|
+
### OpenAI-Compatible
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
completion = client.chat.completions.create(
|
|
149
|
+
model: "openai/gpt-5.4",
|
|
150
|
+
messages: [
|
|
151
|
+
{ role: "user", content: "Write a short launch summary." }
|
|
152
|
+
],
|
|
153
|
+
max_completion_tokens: 800
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
puts completion.dig("choices", 0, "message", "content")
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Streaming
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
client.chat.completions.stream_text(
|
|
163
|
+
model: "openai/gpt-5.4",
|
|
164
|
+
messages: [
|
|
165
|
+
{ role: "user", content: "Write one sentence at a time." }
|
|
166
|
+
]
|
|
167
|
+
).each do |text|
|
|
168
|
+
print text
|
|
169
|
+
end
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Anthropic-Compatible
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
message = client.chat.messages.create(
|
|
176
|
+
model: "openai/gpt-5.4",
|
|
177
|
+
system: "You are concise.",
|
|
178
|
+
messages: [
|
|
179
|
+
{ role: "user", content: "Summarize this release." }
|
|
180
|
+
],
|
|
181
|
+
max_tokens: 800
|
|
182
|
+
)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Gemini-Compatible
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
response = client.chat.gemini.generate_content("openai/gpt-5.4", {
|
|
189
|
+
contents: [
|
|
190
|
+
{
|
|
191
|
+
role: "user",
|
|
192
|
+
parts: [
|
|
193
|
+
{ text: "Write a short launch summary." }
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Gemini streaming uses `stream_generate_content`, not `stream: true` on `generate_content`.
|
|
201
|
+
|
|
202
|
+
Chat methods preserve protocol-native response shapes as Ruby Hashes with string keys.
|
|
203
|
+
|
|
204
|
+
## Error Handling
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
begin
|
|
208
|
+
client.generations.retrieve("task_id")
|
|
209
|
+
rescue Buble::APIError => error
|
|
210
|
+
warn error.status
|
|
211
|
+
warn error.code
|
|
212
|
+
warn error.message
|
|
213
|
+
warn error.details
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
begin
|
|
217
|
+
client.generations.wait("task_id")
|
|
218
|
+
rescue Buble::GenerationFailedError => error
|
|
219
|
+
warn error.task["error"]
|
|
220
|
+
end
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Development
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
cd ruby
|
|
227
|
+
bundle install
|
|
228
|
+
bundle exec rake test
|
|
229
|
+
bundle exec rubocop
|
|
230
|
+
gem build buble.gemspec
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Live smoke test:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
BUBLE_API_KEY=sk_... ruby -Ilib tools/live_smoke.rb
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The live smoke test calls discovery and chat endpoints only and does not create billable generation tasks.
|
|
240
|
+
|
|
241
|
+
## Publishing Checklist
|
|
242
|
+
|
|
243
|
+
RubyGems package identity:
|
|
244
|
+
|
|
245
|
+
- Gem name: `buble`
|
|
246
|
+
- Namespace: `Buble`
|
|
247
|
+
- License: MIT
|
|
248
|
+
- Homepage: `https://buble.ai/`
|
|
249
|
+
|
|
250
|
+
Build and publish:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
cd ruby
|
|
254
|
+
bundle exec rake test
|
|
255
|
+
bundle exec rubocop
|
|
256
|
+
gem build buble.gemspec
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Publication is handled by the monorepo `Release Ruby SDK` GitHub Actions workflow. Configure RubyGems Trusted Publishing for:
|
|
260
|
+
|
|
261
|
+
- Repository owner: `bublehq`
|
|
262
|
+
- Repository name: `sdks`
|
|
263
|
+
- Workflow filename: `release-ruby-sdk.yml`
|
|
264
|
+
- Environment: `release`
|
|
265
|
+
|
|
266
|
+
Then publish from the monorepo with:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
git tag ruby-v0.1.0
|
|
270
|
+
git push origin ruby-v0.1.0
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
RubyGems versions are immutable. After `0.1.0` is published, fixes must use a new version such as `0.1.1`.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Buble Ruby SDK Technical Design
|
|
2
|
+
|
|
3
|
+
## Goals
|
|
4
|
+
|
|
5
|
+
The Ruby SDK mirrors the public Buble API in the same style as the existing JavaScript, Python, Go, Java, .NET, and PHP SDKs.
|
|
6
|
+
|
|
7
|
+
The SDK should:
|
|
8
|
+
|
|
9
|
+
- Keep the public API shape close to Buble's HTTP API.
|
|
10
|
+
- Preserve protocol-native chat response shapes.
|
|
11
|
+
- Use flat generation request bodies.
|
|
12
|
+
- Reject internal Buble workflow fields before sending requests.
|
|
13
|
+
- Avoid runtime dependencies outside the Ruby standard library.
|
|
14
|
+
- Be safe for server-side use and never encourage client-side API key exposure.
|
|
15
|
+
|
|
16
|
+
## Package Shape
|
|
17
|
+
|
|
18
|
+
The gem is named `buble` and exposes the `Buble` namespace.
|
|
19
|
+
|
|
20
|
+
Primary entry point:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
require "buble"
|
|
24
|
+
|
|
25
|
+
client = Buble::Client.new
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The package root contains the gemspec and build metadata. Runtime source lives under `lib/`.
|
|
29
|
+
|
|
30
|
+
## HTTP Layer
|
|
31
|
+
|
|
32
|
+
`Buble::HTTP` is a small wrapper around `Net::HTTP`.
|
|
33
|
+
|
|
34
|
+
Responsibilities:
|
|
35
|
+
|
|
36
|
+
- Resolve base URL and paths.
|
|
37
|
+
- Add bearer token authentication.
|
|
38
|
+
- Encode query strings.
|
|
39
|
+
- Encode JSON request bodies.
|
|
40
|
+
- Decode JSON responses.
|
|
41
|
+
- Raise `Buble::APIError` for non-2xx responses.
|
|
42
|
+
- Send multipart file uploads.
|
|
43
|
+
- Stream SSE response lines.
|
|
44
|
+
|
|
45
|
+
The SDK intentionally avoids Faraday or other HTTP dependencies. This keeps installation lightweight and reduces dependency surface for Rails and non-Rails server applications.
|
|
46
|
+
|
|
47
|
+
## Resources
|
|
48
|
+
|
|
49
|
+
Resources map directly to API areas:
|
|
50
|
+
|
|
51
|
+
- `Buble::MediaModelsResource`
|
|
52
|
+
- `Buble::FilesResource`
|
|
53
|
+
- `Buble::GenerationsResource`
|
|
54
|
+
- `Buble::AppsResource`
|
|
55
|
+
- `Buble::AppGenerationsResource`
|
|
56
|
+
- `Buble::ChatResource`
|
|
57
|
+
- `Buble::ChatModelsResource`
|
|
58
|
+
- `Buble::ChatCompletionsResource`
|
|
59
|
+
- `Buble::MessagesResource`
|
|
60
|
+
- `Buble::GeminiResource`
|
|
61
|
+
|
|
62
|
+
Responses are Ruby Hashes with string keys. This preserves Buble API response shapes and avoids symbolizing arbitrary server-provided keys.
|
|
63
|
+
|
|
64
|
+
## Generation Requests
|
|
65
|
+
|
|
66
|
+
Generation requests use keyword arguments for stable fields and `**params` for model-specific controls:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
client.generations.create(
|
|
70
|
+
model: "google/nano-banana",
|
|
71
|
+
mode: "text_to_image",
|
|
72
|
+
prompt: "A product photo",
|
|
73
|
+
aspect_ratio: "1:1",
|
|
74
|
+
output_format: "png"
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The resulting HTTP body is flat:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"model": "google/nano-banana",
|
|
83
|
+
"mode": "text_to_image",
|
|
84
|
+
"prompt": "A product photo",
|
|
85
|
+
"aspect_ratio": "1:1",
|
|
86
|
+
"output_format": "png"
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The SDK rejects internal fields:
|
|
91
|
+
|
|
92
|
+
- `input`
|
|
93
|
+
- `options`
|
|
94
|
+
- `scene`
|
|
95
|
+
- `sub_mode_id`
|
|
96
|
+
- `subModeId`
|
|
97
|
+
- `provider`
|
|
98
|
+
- `mediaType`
|
|
99
|
+
- `media_type`
|
|
100
|
+
- `images`
|
|
101
|
+
- `image_input`
|
|
102
|
+
- `video_input`
|
|
103
|
+
- `audio_input`
|
|
104
|
+
|
|
105
|
+
## Streaming
|
|
106
|
+
|
|
107
|
+
`Buble::Streaming::SSEParser` parses server-sent event lines into `Buble::Streaming::Event` objects.
|
|
108
|
+
|
|
109
|
+
Text helpers extract deltas for:
|
|
110
|
+
|
|
111
|
+
- OpenAI-compatible chat: `choices[0].delta.content`
|
|
112
|
+
- Anthropic-compatible messages: `delta.text`
|
|
113
|
+
- Gemini-compatible chat: `candidates[0].content.parts[0].text`
|
|
114
|
+
|
|
115
|
+
The lower-level `stream` APIs expose parsed SSE events for callers that need protocol details.
|
|
116
|
+
|
|
117
|
+
## Errors
|
|
118
|
+
|
|
119
|
+
The error hierarchy is:
|
|
120
|
+
|
|
121
|
+
- `Buble::Error`
|
|
122
|
+
- `Buble::APIError`
|
|
123
|
+
- `Buble::TimeoutError`
|
|
124
|
+
- `Buble::GenerationFailedError`
|
|
125
|
+
- `Buble::GenerationCanceledError`
|
|
126
|
+
- `Buble::UnsupportedGenerationFieldError`
|
|
127
|
+
|
|
128
|
+
`APIError` carries HTTP status, API error code, details, and raw response body.
|
|
129
|
+
|
|
130
|
+
## Testing
|
|
131
|
+
|
|
132
|
+
Unit tests use Minitest and `FakeTransport`.
|
|
133
|
+
|
|
134
|
+
Coverage focuses on:
|
|
135
|
+
|
|
136
|
+
- Request paths and bodies.
|
|
137
|
+
- Flat generation request serialization.
|
|
138
|
+
- Forbidden generation field rejection.
|
|
139
|
+
- Wait polling behavior.
|
|
140
|
+
- Multipart upload shape.
|
|
141
|
+
- App generation paths.
|
|
142
|
+
- Chat response preservation.
|
|
143
|
+
- SSE parsing and text extraction.
|
|
144
|
+
|
|
145
|
+
Live smoke tests are intentionally limited to low-cost discovery and chat checks.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'buble'
|
|
4
|
+
|
|
5
|
+
client = Buble::Client.new
|
|
6
|
+
|
|
7
|
+
message = client.chat.messages.create(
|
|
8
|
+
model: 'openai/gpt-5.4',
|
|
9
|
+
system: 'You are concise.',
|
|
10
|
+
messages: [
|
|
11
|
+
{ role: 'user', content: 'Summarize this release.' }
|
|
12
|
+
],
|
|
13
|
+
max_tokens: 800
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
puts message
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'buble'
|
|
4
|
+
|
|
5
|
+
client = Buble::Client.new
|
|
6
|
+
|
|
7
|
+
task = client.apps.generations.create('video-background-remover', {
|
|
8
|
+
'source_video' => ['https://example.com/source.mp4'],
|
|
9
|
+
'refine_foreground_edges' => true,
|
|
10
|
+
'subject_is_person' => true
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
result = client.apps.generations.wait('video-background-remover', task.dig('data', 'id'))
|
|
14
|
+
puts result.dig('data', 'result', 'videos', 0, 'url')
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'buble'
|
|
4
|
+
|
|
5
|
+
client = Buble::Client.new
|
|
6
|
+
|
|
7
|
+
response = client.chat.gemini.generate_content('openai/gpt-5.4', {
|
|
8
|
+
contents: [
|
|
9
|
+
{
|
|
10
|
+
role: 'user',
|
|
11
|
+
parts: [
|
|
12
|
+
{ text: 'Write a short launch summary.' }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
puts response
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'buble'
|
|
4
|
+
|
|
5
|
+
client = Buble::Client.new
|
|
6
|
+
|
|
7
|
+
uploaded = client.files.upload(
|
|
8
|
+
Buble::FileUpload.from_path('reference.png', content_type: 'image/png'),
|
|
9
|
+
file_type: 'image',
|
|
10
|
+
model: 'google/nano-banana',
|
|
11
|
+
mode: 'image_to_image'
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
task = client.generations.create(
|
|
15
|
+
model: 'google/nano-banana',
|
|
16
|
+
mode: 'image_to_image',
|
|
17
|
+
prompt: 'Turn this reference into a polished ecommerce hero image.',
|
|
18
|
+
image_urls: [uploaded.dig('data', 'url')]
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
result = client.generations.wait(task.dig('data', 'id'))
|
|
22
|
+
puts result.dig('data', 'result', 'images', 0, 'url')
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'buble'
|
|
4
|
+
|
|
5
|
+
client = Buble::Client.new
|
|
6
|
+
|
|
7
|
+
completion = client.chat.completions.create(
|
|
8
|
+
model: 'openai/gpt-5.4',
|
|
9
|
+
messages: [
|
|
10
|
+
{ role: 'user', content: 'Write a short launch summary.' }
|
|
11
|
+
],
|
|
12
|
+
max_completion_tokens: 800
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
puts completion.dig('choices', 0, 'message', 'content')
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'buble'
|
|
4
|
+
|
|
5
|
+
client = Buble::Client.new
|
|
6
|
+
|
|
7
|
+
task = client.generations.create(
|
|
8
|
+
model: 'google/nano-banana',
|
|
9
|
+
mode: 'text_to_image',
|
|
10
|
+
prompt: 'A cinematic product photo of a matte black espresso cup',
|
|
11
|
+
aspect_ratio: '1:1',
|
|
12
|
+
output_format: 'png'
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
result = client.generations.wait(task.dig('data', 'id'))
|
|
16
|
+
puts result.dig('data', 'result', 'images', 0, 'url')
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'buble'
|
|
4
|
+
|
|
5
|
+
client = Buble::Client.new
|
|
6
|
+
|
|
7
|
+
task = client.generations.create(
|
|
8
|
+
model: 'gork/grok-imagine-video',
|
|
9
|
+
mode: 'text_to_video',
|
|
10
|
+
prompt: 'A slow cinematic shot of a futuristic train station at sunrise.',
|
|
11
|
+
duration: '5s',
|
|
12
|
+
resolution: '480p',
|
|
13
|
+
aspect_ratio: '16:9'
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
result = client.generations.wait(task.dig('data', 'id'), interval: 2, timeout: 900)
|
|
17
|
+
puts result.dig('data', 'result', 'videos', 0, 'url')
|
data/lib/buble/apps.rb
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Buble
|
|
4
|
+
class AppGenerationsResource
|
|
5
|
+
TERMINAL_STATUSES = GenerationsResource::TERMINAL_STATUSES
|
|
6
|
+
|
|
7
|
+
def initialize(http)
|
|
8
|
+
@http = http
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def create(app_id, params = {})
|
|
12
|
+
@http.request('POST', "/api/v1/apps/#{HTTP.encode_segment(app_id)}/generations", body: params)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def retrieve(app_id, id)
|
|
16
|
+
@http.request('GET', "/api/v1/apps/#{HTTP.encode_segment(app_id)}/generations/#{HTTP.encode_segment(id)}")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def wait(app_id, id, interval: 2, timeout: 600, throw_on_failed: true, throw_on_canceled: true)
|
|
20
|
+
deadline = Time.now + timeout
|
|
21
|
+
|
|
22
|
+
loop do
|
|
23
|
+
envelope = retrieve(app_id, id)
|
|
24
|
+
task = envelope['data'] || {}
|
|
25
|
+
status = task['status']
|
|
26
|
+
if TERMINAL_STATUSES.include?(status)
|
|
27
|
+
raise_if_terminal_error!(id, task, status, throw_on_failed, throw_on_canceled)
|
|
28
|
+
return envelope
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if Time.now >= deadline
|
|
32
|
+
raise TimeoutError.new(
|
|
33
|
+
"App generation #{id} did not finish within #{timeout} seconds.",
|
|
34
|
+
timeout: timeout
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
sleep interval
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def raise_if_terminal_error!(id, task, status, throw_on_failed, throw_on_canceled)
|
|
45
|
+
if status == 'failed' && throw_on_failed
|
|
46
|
+
error = task['error'] || {}
|
|
47
|
+
raise GenerationFailedError.new(error['message'] || 'Generation failed.', task: task)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
return unless status == 'canceled' && throw_on_canceled
|
|
51
|
+
|
|
52
|
+
raise GenerationCanceledError.new("App generation #{id} was canceled.", task: task)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class AppsResource
|
|
57
|
+
attr_reader :generations
|
|
58
|
+
|
|
59
|
+
def initialize(http)
|
|
60
|
+
@http = http
|
|
61
|
+
@generations = AppGenerationsResource.new(http)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def list
|
|
65
|
+
@http.request('GET', '/api/v1/apps')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def retrieve(app_id)
|
|
69
|
+
@http.request('GET', "/api/v1/apps/#{HTTP.encode_segment(app_id)}")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|