groq_ruby 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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +57 -0
- data/CLAUDE.md +103 -0
- data/LICENSE.txt +21 -0
- data/README.md +495 -0
- data/Rakefile +11 -0
- data/examples/README.md +39 -0
- data/examples/batch.rb +29 -0
- data/examples/chat_completion.rb +24 -0
- data/examples/chat_completion_stop.rb +19 -0
- data/examples/chat_completion_streaming.rb +23 -0
- data/examples/embedding.rb +20 -0
- data/examples/error_handling.rb +27 -0
- data/examples/file_upload.rb +23 -0
- data/examples/mcp_agent.rb +63 -0
- data/examples/mcp_chat_with_tools.rb +103 -0
- data/examples/mcp_resources_and_prompts.rb +89 -0
- data/examples/models_list.rb +16 -0
- data/examples/speech.rb +23 -0
- data/examples/transcription.rb +23 -0
- data/examples/translation.rb +22 -0
- data/lib/groq_ruby/client.rb +69 -0
- data/lib/groq_ruby/configuration.rb +62 -0
- data/lib/groq_ruby/error_mapper.rb +37 -0
- data/lib/groq_ruby/errors/api_connection_error.rb +8 -0
- data/lib/groq_ruby/errors/api_error.rb +14 -0
- data/lib/groq_ruby/errors/api_response_error.rb +5 -0
- data/lib/groq_ruby/errors/api_status_error.rb +23 -0
- data/lib/groq_ruby/errors/api_timeout_error.rb +8 -0
- data/lib/groq_ruby/errors/authentication_error.rb +4 -0
- data/lib/groq_ruby/errors/bad_request_error.rb +4 -0
- data/lib/groq_ruby/errors/configuration_error.rb +4 -0
- data/lib/groq_ruby/errors/conflict_error.rb +4 -0
- data/lib/groq_ruby/errors/error.rb +5 -0
- data/lib/groq_ruby/errors/internal_server_error.rb +4 -0
- data/lib/groq_ruby/errors/not_found_error.rb +4 -0
- data/lib/groq_ruby/errors/parameter_error.rb +13 -0
- data/lib/groq_ruby/errors/permission_denied_error.rb +4 -0
- data/lib/groq_ruby/errors/rate_limit_error.rb +4 -0
- data/lib/groq_ruby/errors/unprocessable_entity_error.rb +4 -0
- data/lib/groq_ruby/mcp/bridge.rb +239 -0
- data/lib/groq_ruby/mcp/claude_desktop_config.rb +79 -0
- data/lib/groq_ruby/mcp/client.rb +171 -0
- data/lib/groq_ruby/mcp/errors/error.rb +7 -0
- data/lib/groq_ruby/mcp/errors/json_rpc_error.rb +21 -0
- data/lib/groq_ruby/mcp/errors/protocol_error.rb +7 -0
- data/lib/groq_ruby/mcp/errors/timeout_error.rb +7 -0
- data/lib/groq_ruby/mcp/errors/transport_error.rb +6 -0
- data/lib/groq_ruby/mcp/errors/unknown_tool_error.rb +7 -0
- data/lib/groq_ruby/mcp/json_rpc.rb +51 -0
- data/lib/groq_ruby/mcp/prompt.rb +21 -0
- data/lib/groq_ruby/mcp/resource.rb +17 -0
- data/lib/groq_ruby/mcp/server_config.rb +22 -0
- data/lib/groq_ruby/mcp/tool.rb +22 -0
- data/lib/groq_ruby/mcp/transport.rb +32 -0
- data/lib/groq_ruby/mcp/transports/stdio.rb +100 -0
- data/lib/groq_ruby/mcp.rb +25 -0
- data/lib/groq_ruby/models/audio/transcription.rb +10 -0
- data/lib/groq_ruby/models/audio/translation.rb +8 -0
- data/lib/groq_ruby/models/batches/batch.rb +16 -0
- data/lib/groq_ruby/models/batches/batch_list.rb +10 -0
- data/lib/groq_ruby/models/batches/batch_request_counts.rb +8 -0
- data/lib/groq_ruby/models/chat/chat_completion.rb +14 -0
- data/lib/groq_ruby/models/chat/chat_completion_choice.rb +10 -0
- data/lib/groq_ruby/models/chat/chat_completion_chunk.rb +13 -0
- data/lib/groq_ruby/models/chat/chat_completion_chunk_choice.rb +10 -0
- data/lib/groq_ruby/models/chat/chat_completion_delta.rb +8 -0
- data/lib/groq_ruby/models/chat/chat_completion_message.rb +10 -0
- data/lib/groq_ruby/models/embeddings/create_embedding_response.rb +11 -0
- data/lib/groq_ruby/models/embeddings/embedding.rb +8 -0
- data/lib/groq_ruby/models/embeddings/embedding_usage.rb +8 -0
- data/lib/groq_ruby/models/files/file_deleted.rb +8 -0
- data/lib/groq_ruby/models/files/file_list.rb +10 -0
- data/lib/groq_ruby/models/files/file_object.rb +8 -0
- data/lib/groq_ruby/models/model.rb +8 -0
- data/lib/groq_ruby/models/model_deleted.rb +8 -0
- data/lib/groq_ruby/models/model_factory.rb +31 -0
- data/lib/groq_ruby/models/model_list.rb +10 -0
- data/lib/groq_ruby/models/usage.rb +11 -0
- data/lib/groq_ruby/multipart.rb +84 -0
- data/lib/groq_ruby/request.rb +13 -0
- data/lib/groq_ruby/resources/audio/speech.rb +32 -0
- data/lib/groq_ruby/resources/audio/transcriptions.rb +48 -0
- data/lib/groq_ruby/resources/audio/translations.rb +45 -0
- data/lib/groq_ruby/resources/audio.rb +26 -0
- data/lib/groq_ruby/resources/base.rb +33 -0
- data/lib/groq_ruby/resources/batches.rb +44 -0
- data/lib/groq_ruby/resources/chat/completions.rb +94 -0
- data/lib/groq_ruby/resources/chat.rb +16 -0
- data/lib/groq_ruby/resources/embeddings.rb +28 -0
- data/lib/groq_ruby/resources/files.rb +55 -0
- data/lib/groq_ruby/resources/models.rb +35 -0
- data/lib/groq_ruby/response.rb +9 -0
- data/lib/groq_ruby/streaming/chunk_stream.rb +58 -0
- data/lib/groq_ruby/streaming/event_parser.rb +23 -0
- data/lib/groq_ruby/transport.rb +169 -0
- data/lib/groq_ruby/version.rb +5 -0
- data/lib/groq_ruby.rb +36 -0
- data/lib/tasks/gem.rake +5 -0
- data/lib/tasks/lint/all.rake +11 -0
- data/lib/tasks/lint/rubocop.rake +15 -0
- data/lib/tasks/security.rake +11 -0
- data/lib/tasks/types.rake +11 -0
- data/sig/groq_ruby.rbs +191 -0
- data/sig/zeitwerk.rbs +13 -0
- data.tar.gz.sig +0 -0
- metadata +237 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module GroqRuby
|
|
2
|
+
module Resources
|
|
3
|
+
# `client.models.list / retrieve / delete`.
|
|
4
|
+
#
|
|
5
|
+
# @example List
|
|
6
|
+
# client.models.list.data.each { |m| puts m.id }
|
|
7
|
+
#
|
|
8
|
+
# @example Retrieve
|
|
9
|
+
# client.models.retrieve("llama-3.3-70b-versatile")
|
|
10
|
+
class Models < Base
|
|
11
|
+
BASE_PATH = "/openai/v1/models".freeze
|
|
12
|
+
|
|
13
|
+
# @return [GroqRuby::Models::ModelList]
|
|
14
|
+
def list
|
|
15
|
+
payload = perform(Request.new(method: :get, path: BASE_PATH))
|
|
16
|
+
GroqRuby::Models::ModelList.from_hash(payload)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param model_id [String]
|
|
20
|
+
# @return [GroqRuby::Models::Model]
|
|
21
|
+
# @raise [NotFoundError] if no such model exists for this account
|
|
22
|
+
def retrieve(model_id)
|
|
23
|
+
payload = perform(Request.new(method: :get, path: "#{BASE_PATH}/#{model_id}"))
|
|
24
|
+
GroqRuby::Models::Model.from_hash(payload)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @param model_id [String]
|
|
28
|
+
# @return [GroqRuby::Models::ModelDeleted]
|
|
29
|
+
def delete(model_id)
|
|
30
|
+
payload = perform(Request.new(method: :delete, path: "#{BASE_PATH}/#{model_id}"))
|
|
31
|
+
GroqRuby::Models::ModelDeleted.from_hash(payload)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module GroqRuby
|
|
2
|
+
# Value object representing a parsed HTTP response. The body is either a
|
|
3
|
+
# decoded JSON object (Hash/Array) or a raw String for binary responses.
|
|
4
|
+
Response = Data.define(:status, :headers, :body) do
|
|
5
|
+
def success?
|
|
6
|
+
status.between?(200, 299)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module GroqRuby
|
|
4
|
+
module Streaming
|
|
5
|
+
# Lazy stream of parsed chat completion chunks. Wraps the transport's
|
|
6
|
+
# SSE delivery with the `[DONE]` sentinel handling that OpenAI-style
|
|
7
|
+
# APIs use, and the JSON decode → model wrap step.
|
|
8
|
+
#
|
|
9
|
+
# Yields {GroqRuby::Models::ChatCompletionChunk} instances. Honours the
|
|
10
|
+
# `[DONE]` terminator by stopping iteration cleanly.
|
|
11
|
+
#
|
|
12
|
+
# @example Iterate via Enumerable
|
|
13
|
+
# stream = client.chat.completions.create(stream: true, ...)
|
|
14
|
+
# stream.each { |chunk| print chunk.choices.first.delta.content }
|
|
15
|
+
#
|
|
16
|
+
# @example Pass a block directly
|
|
17
|
+
# client.chat.completions.create(stream: true, ...) do |chunk|
|
|
18
|
+
# ...
|
|
19
|
+
# end
|
|
20
|
+
class ChunkStream
|
|
21
|
+
include Enumerable
|
|
22
|
+
|
|
23
|
+
DONE_SENTINEL = "[DONE]".freeze
|
|
24
|
+
|
|
25
|
+
# @param transport [Transport]
|
|
26
|
+
# @param request [Request]
|
|
27
|
+
# @param chunk_model [Class] class with a `.from_hash(Hash)` constructor
|
|
28
|
+
def initialize(transport:, request:, chunk_model:)
|
|
29
|
+
@transport = transport
|
|
30
|
+
@request = request
|
|
31
|
+
@chunk_model = chunk_model
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Iterate through each chunk. When called without a block, returns an
|
|
35
|
+
# Enumerator that lets callers compose with `Enumerable` methods.
|
|
36
|
+
#
|
|
37
|
+
# @yieldparam chunk [GroqRuby::Models::ChatCompletionChunk]
|
|
38
|
+
# @return [Enumerator] when called without a block
|
|
39
|
+
# @return [void] when called with a block
|
|
40
|
+
# @raise [APIError] subclass on non-2xx response or transport failure.
|
|
41
|
+
def each(&block)
|
|
42
|
+
return enum_for(:each) unless block_given?
|
|
43
|
+
parser = EventParser.new
|
|
44
|
+
result = @transport.stream(@request) do |raw_chunk|
|
|
45
|
+
parser.feed(raw_chunk) { |_event, data, _id, _retry| forward_event(data, &block) }
|
|
46
|
+
end
|
|
47
|
+
raise result.failure if result.failure?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def forward_event(data, &block)
|
|
53
|
+
return if data.nil? || data.empty? || data == DONE_SENTINEL
|
|
54
|
+
block.call(@chunk_model.from_hash(JSON.parse(data)))
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require "event_stream_parser"
|
|
2
|
+
|
|
3
|
+
module GroqRuby
|
|
4
|
+
module Streaming
|
|
5
|
+
# Thin adapter over the upstream `event_stream_parser` gem. Feeds raw
|
|
6
|
+
# response chunks in and yields parsed `(event, data, id, retry)`
|
|
7
|
+
# tuples. Single-purpose: it owns the SSE parser state and nothing else.
|
|
8
|
+
class EventParser
|
|
9
|
+
def initialize
|
|
10
|
+
@parser = ::EventStreamParser::Parser.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Feed a raw chunk from the HTTP response. Yields each parsed event.
|
|
14
|
+
# @yieldparam event [String, nil]
|
|
15
|
+
# @yieldparam data [String]
|
|
16
|
+
# @yieldparam id [String, nil]
|
|
17
|
+
# @yieldparam reconnect_time [Integer, nil]
|
|
18
|
+
def feed(chunk, &block)
|
|
19
|
+
@parser.feed(chunk, &block)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
require "json"
|
|
3
|
+
require "uri"
|
|
4
|
+
require "dry/monads"
|
|
5
|
+
|
|
6
|
+
module GroqRuby
|
|
7
|
+
# The single HTTP client used by every resource. Wraps Net::HTTP and
|
|
8
|
+
# exposes two operations: a buffered {#call} for ordinary requests and a
|
|
9
|
+
# streaming {#stream} for SSE (chat completions with `stream: true`).
|
|
10
|
+
#
|
|
11
|
+
# Both methods return a Result. Internal control flow uses Do notation
|
|
12
|
+
# so the happy path stays linear; transport-level failures are wrapped in
|
|
13
|
+
# the appropriate {APIError} subclass.
|
|
14
|
+
#
|
|
15
|
+
# Resources call `.value!` to surface the parsed response (raising on
|
|
16
|
+
# failure), preserving parity with the Python client's exception model.
|
|
17
|
+
class Transport
|
|
18
|
+
include Dry::Monads[:result, :do]
|
|
19
|
+
|
|
20
|
+
JSON_CONTENT_TYPE = "application/json"
|
|
21
|
+
|
|
22
|
+
# @param configuration [Configuration]
|
|
23
|
+
def initialize(configuration)
|
|
24
|
+
@config = configuration
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Execute a buffered request.
|
|
28
|
+
#
|
|
29
|
+
# @param request [Request]
|
|
30
|
+
# @param accept [Symbol] :json (decode body as JSON) or :binary (return raw String)
|
|
31
|
+
# @return [Dry::Monads::Result<Response, APIError>]
|
|
32
|
+
def call(request, accept: :json)
|
|
33
|
+
raw = yield execute(request)
|
|
34
|
+
response = yield decode(raw, accept)
|
|
35
|
+
yield validate(response)
|
|
36
|
+
Success(response)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Execute a streaming request, yielding each raw chunk to the block.
|
|
40
|
+
# The HTTP response is opened in chunked mode; the body is never
|
|
41
|
+
# buffered in full. Validates the status before streaming begins;
|
|
42
|
+
# error responses are read in full so the body can be parsed and
|
|
43
|
+
# surfaced via the mapped exception.
|
|
44
|
+
#
|
|
45
|
+
# @yieldparam chunk [String]
|
|
46
|
+
# @return [Dry::Monads::Result<Response, APIError>]
|
|
47
|
+
def stream(request, &on_chunk)
|
|
48
|
+
uri = build_uri(request)
|
|
49
|
+
http = build_http(uri)
|
|
50
|
+
net_request = build_net_request(request, uri)
|
|
51
|
+
|
|
52
|
+
with_translated_errors do
|
|
53
|
+
outcome = nil
|
|
54
|
+
http.start do |conn|
|
|
55
|
+
conn.request(net_request) do |raw|
|
|
56
|
+
outcome = stream_response(raw, &on_chunk)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
outcome
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def execute(request)
|
|
66
|
+
uri = build_uri(request)
|
|
67
|
+
http = build_http(uri)
|
|
68
|
+
net_request = build_net_request(request, uri)
|
|
69
|
+
|
|
70
|
+
with_translated_errors do
|
|
71
|
+
Success(http.start { |conn| conn.request(net_request) })
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def with_translated_errors
|
|
76
|
+
yield
|
|
77
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
78
|
+
Failure(APITimeoutError.new(e.message))
|
|
79
|
+
rescue SocketError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EHOSTUNREACH => e
|
|
80
|
+
Failure(APIConnectionError.new(e.message))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def decode(raw, accept)
|
|
84
|
+
headers = collect_headers(raw)
|
|
85
|
+
body = raw.body || ""
|
|
86
|
+
decoded = (accept == :json) ? decode_json(body) : body
|
|
87
|
+
Success(Response.new(status: raw.code.to_i, headers: headers, body: decoded))
|
|
88
|
+
rescue JSON::ParserError => e
|
|
89
|
+
Failure(APIResponseError.new("invalid JSON response: #{e.message}"))
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def decode_json(body)
|
|
93
|
+
body.empty? ? nil : JSON.parse(body)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def validate(response)
|
|
97
|
+
response.success? ? Success(response) : Failure(map_error(response))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def stream_response(raw, &on_chunk)
|
|
101
|
+
status = raw.code.to_i
|
|
102
|
+
headers = collect_headers(raw)
|
|
103
|
+
if status.between?(200, 299)
|
|
104
|
+
raw.read_body(&on_chunk)
|
|
105
|
+
Success(Response.new(status: status, headers: headers, body: nil))
|
|
106
|
+
else
|
|
107
|
+
body = decode_json_safe(raw.read_body)
|
|
108
|
+
Failure(map_error(Response.new(status: status, headers: headers, body: body)))
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def decode_json_safe(body)
|
|
113
|
+
decode_json(body)
|
|
114
|
+
rescue JSON::ParserError
|
|
115
|
+
body
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def map_error(response)
|
|
119
|
+
ErrorMapper.call(status: response.status, headers: response.headers, body: response.body)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def collect_headers(raw)
|
|
123
|
+
raw.each_header.to_h
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def build_uri(request)
|
|
127
|
+
uri = URI.parse("#{@config.base_url.chomp("/")}/#{request.path.delete_prefix("/")}")
|
|
128
|
+
uri.query = URI.encode_www_form(request.query) if request.query && !request.query.empty?
|
|
129
|
+
uri
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def build_http(uri)
|
|
133
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
134
|
+
http.use_ssl = (uri.scheme == "https")
|
|
135
|
+
http.open_timeout = @config.open_timeout
|
|
136
|
+
http.read_timeout = @config.read_timeout
|
|
137
|
+
http
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def build_net_request(request, uri)
|
|
141
|
+
klass = net_http_class(request.method)
|
|
142
|
+
net_request = klass.new(uri.request_uri)
|
|
143
|
+
apply_headers(net_request, request)
|
|
144
|
+
apply_body(net_request, request)
|
|
145
|
+
net_request
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def net_http_class(method)
|
|
149
|
+
case method
|
|
150
|
+
when :get then Net::HTTP::Get
|
|
151
|
+
when :post then Net::HTTP::Post
|
|
152
|
+
when :delete then Net::HTTP::Delete
|
|
153
|
+
else raise ArgumentError, "unsupported HTTP method: #{method.inspect}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def apply_headers(net_request, request)
|
|
158
|
+
@config.default_headers.each { |k, v| net_request[k] = v }
|
|
159
|
+
request.headers.each { |k, v| net_request[k] = v }
|
|
160
|
+
net_request["Content-Type"] = request.content_type if request.content_type
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def apply_body(net_request, request)
|
|
164
|
+
return if request.body.nil?
|
|
165
|
+
net_request.body = request.body.is_a?(String) ? request.body : JSON.generate(request.body)
|
|
166
|
+
net_request["Content-Type"] ||= JSON_CONTENT_TYPE
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
data/lib/groq_ruby.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require "zeitwerk"
|
|
2
|
+
|
|
3
|
+
loader = Zeitwerk::Loader.for_gem
|
|
4
|
+
# Acronyms that can't be derived from the default snake_case → CamelCase
|
|
5
|
+
# inflection. Each maps a basename (without `.rb`) to its constant.
|
|
6
|
+
loader.inflector.inflect(
|
|
7
|
+
"mcp" => "MCP",
|
|
8
|
+
"api_error" => "APIError",
|
|
9
|
+
"api_connection_error" => "APIConnectionError",
|
|
10
|
+
"api_timeout_error" => "APITimeoutError",
|
|
11
|
+
"api_status_error" => "APIStatusError",
|
|
12
|
+
"api_response_error" => "APIResponseError"
|
|
13
|
+
)
|
|
14
|
+
# Collapse organizational sub-directories so files inside them define
|
|
15
|
+
# constants in the parent namespace. Public API stays flat — callers
|
|
16
|
+
# write `GroqRuby::AuthenticationError`, not `GroqRuby::Errors::Auth…`.
|
|
17
|
+
loader.collapse("#{__dir__}/groq_ruby/errors")
|
|
18
|
+
loader.collapse("#{__dir__}/groq_ruby/mcp/errors")
|
|
19
|
+
loader.collapse("#{__dir__}/groq_ruby/models/chat")
|
|
20
|
+
loader.collapse("#{__dir__}/groq_ruby/models/audio")
|
|
21
|
+
loader.collapse("#{__dir__}/groq_ruby/models/embeddings")
|
|
22
|
+
loader.collapse("#{__dir__}/groq_ruby/models/files")
|
|
23
|
+
loader.collapse("#{__dir__}/groq_ruby/models/batches")
|
|
24
|
+
loader.setup
|
|
25
|
+
|
|
26
|
+
# Idiomatic Ruby client for the Groq API. See {GroqRuby::Client} for the
|
|
27
|
+
# main entry point and the `examples/` directory for end-to-end snippets.
|
|
28
|
+
module GroqRuby
|
|
29
|
+
# Convenience constructor — `GroqRuby.client` reads configuration from
|
|
30
|
+
# the `GROQ_API_KEY` and `GROQ_BASE_URL` env vars by default.
|
|
31
|
+
#
|
|
32
|
+
# @return [Client]
|
|
33
|
+
def self.client(**opts)
|
|
34
|
+
Client.new(**opts)
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/tasks/gem.rake
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require "rubocop/rake_task"
|
|
2
|
+
|
|
3
|
+
namespace :lint do
|
|
4
|
+
desc "Run rubocop linter check"
|
|
5
|
+
task :rubocop do
|
|
6
|
+
exec "bundle exec rubocop"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
namespace :rubocop do
|
|
10
|
+
desc "Run rubocop autocorrect"
|
|
11
|
+
task :autocorrect do
|
|
12
|
+
exec "bundle exec rubocop --autocorrect-all"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
namespace :types do
|
|
2
|
+
desc "Validate RBS syntax in sig/ (catches sigs that reference nonexistent types)"
|
|
3
|
+
task :validate do
|
|
4
|
+
sh "bundle exec rbs " \
|
|
5
|
+
"-r uri -r ipaddr -r stringio -r tempfile -r net-http -r openssl -r resolv -r zlib " \
|
|
6
|
+
"-I sig validate"
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
desc "Validate RBS sigs"
|
|
11
|
+
task types: ["types:validate"]
|
data/sig/groq_ruby.rbs
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
module GroqRuby
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
def self.client: (**untyped opts) -> Client
|
|
5
|
+
|
|
6
|
+
class Error < StandardError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class ConfigurationError < Error
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class ParameterError < Error
|
|
13
|
+
attr_reader messages: Hash[untyped, untyped]
|
|
14
|
+
def initialize: (Hash[untyped, untyped] messages) -> void
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class APIError < Error
|
|
18
|
+
attr_reader request: untyped
|
|
19
|
+
def initialize: (String message, ?request: untyped?) -> void
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class APIConnectionError < APIError
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class APITimeoutError < APIConnectionError
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class APIStatusError < APIError
|
|
29
|
+
attr_reader status: Integer
|
|
30
|
+
attr_reader headers: Hash[String, String]
|
|
31
|
+
attr_reader body: untyped
|
|
32
|
+
|
|
33
|
+
def initialize: (
|
|
34
|
+
String message,
|
|
35
|
+
status: Integer,
|
|
36
|
+
?headers: Hash[String, String],
|
|
37
|
+
?body: untyped,
|
|
38
|
+
?request: untyped?
|
|
39
|
+
) -> void
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class BadRequestError < APIStatusError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class AuthenticationError < APIStatusError
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class PermissionDeniedError < APIStatusError
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class NotFoundError < APIStatusError
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class ConflictError < APIStatusError
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class UnprocessableEntityError < APIStatusError
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class RateLimitError < APIStatusError
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class InternalServerError < APIStatusError
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class APIResponseError < APIError
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class Client
|
|
70
|
+
attr_reader configuration: untyped
|
|
71
|
+
|
|
72
|
+
def initialize: (
|
|
73
|
+
?api_key: String?,
|
|
74
|
+
?base_url: String?,
|
|
75
|
+
?open_timeout: (Float | Integer)?,
|
|
76
|
+
?read_timeout: (Float | Integer)?,
|
|
77
|
+
?user_agent: String?
|
|
78
|
+
) -> void
|
|
79
|
+
|
|
80
|
+
def chat: () -> untyped
|
|
81
|
+
def embeddings: () -> untyped
|
|
82
|
+
def audio: () -> untyped
|
|
83
|
+
def models: () -> untyped
|
|
84
|
+
def files: () -> untyped
|
|
85
|
+
def batches: () -> untyped
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
module MCP
|
|
89
|
+
PROTOCOL_VERSION: String
|
|
90
|
+
|
|
91
|
+
class Error < GroqRuby::Error
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class TransportError < Error
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class TimeoutError < Error
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class ProtocolError < Error
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
class JsonRpcError < Error
|
|
104
|
+
attr_reader code: Integer
|
|
105
|
+
attr_reader data: untyped
|
|
106
|
+
|
|
107
|
+
def initialize: (code: Integer, message: String, ?data: untyped) -> void
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class UnknownToolError < Error
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
class ServerConfig
|
|
114
|
+
attr_reader name: String
|
|
115
|
+
attr_reader command: String
|
|
116
|
+
attr_reader args: Array[String]
|
|
117
|
+
attr_reader env: Hash[String, String]
|
|
118
|
+
|
|
119
|
+
def initialize: (
|
|
120
|
+
name: String,
|
|
121
|
+
command: String,
|
|
122
|
+
?args: Array[String],
|
|
123
|
+
?env: Hash[String, String]
|
|
124
|
+
) -> void
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
module ClaudeDesktopConfig
|
|
128
|
+
VAR_PATTERN: Regexp
|
|
129
|
+
|
|
130
|
+
def self.load: (String path) -> Array[ServerConfig]
|
|
131
|
+
def self.parse: ((Hash[untyped, untyped] | String) input) -> Array[ServerConfig]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
class Tool
|
|
135
|
+
attr_reader name: String
|
|
136
|
+
attr_reader description: String?
|
|
137
|
+
attr_reader input_schema: Hash[String, untyped]
|
|
138
|
+
def self.from_hash: (Hash[untyped, untyped]?) -> Tool?
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class Resource
|
|
142
|
+
attr_reader uri: String
|
|
143
|
+
attr_reader name: String?
|
|
144
|
+
attr_reader description: String?
|
|
145
|
+
attr_reader mime_type: String?
|
|
146
|
+
def self.from_hash: (Hash[untyped, untyped]?) -> Resource?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
class Prompt
|
|
150
|
+
attr_reader name: String
|
|
151
|
+
attr_reader description: String?
|
|
152
|
+
attr_reader arguments: Array[untyped]
|
|
153
|
+
def self.from_hash: (Hash[untyped, untyped]?) -> Prompt?
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
class Client
|
|
157
|
+
DEFAULT_REQUEST_TIMEOUT: Float
|
|
158
|
+
|
|
159
|
+
attr_reader server_name: String?
|
|
160
|
+
attr_reader server_version: String?
|
|
161
|
+
attr_reader server_capabilities: Hash[String, untyped]
|
|
162
|
+
|
|
163
|
+
def self.connect: (ServerConfig, ?request_timeout: Numeric) -> Client
|
|
164
|
+
def initialize: (untyped transport, ?request_timeout: Numeric) -> void
|
|
165
|
+
def initialize_session: () -> Hash[String, untyped]
|
|
166
|
+
def tools_list: () -> Array[Tool]
|
|
167
|
+
def tools_call: (name: String, ?arguments: Hash[untyped, untyped]) -> Hash[String, untyped]
|
|
168
|
+
def resources_list: () -> Array[Resource]
|
|
169
|
+
def resources_read: (String uri) -> Hash[String, untyped]
|
|
170
|
+
def prompts_list: () -> Array[Prompt]
|
|
171
|
+
def prompts_get: (String name, ?Hash[untyped, untyped] arguments) -> Hash[String, untyped]
|
|
172
|
+
def supports?: (String capability) -> bool
|
|
173
|
+
def stop: () -> void
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
class Bridge
|
|
177
|
+
NAME_SEPARATOR: String
|
|
178
|
+
READ_RESOURCE_SUFFIX: String
|
|
179
|
+
|
|
180
|
+
def initialize: (Array[ServerConfig], ?request_timeout: Numeric) -> void
|
|
181
|
+
def tools: () -> Array[Hash[Symbol, untyped]]
|
|
182
|
+
def tool_names: () -> Array[String]
|
|
183
|
+
def resources: () -> Array[Hash[Symbol, untyped]]
|
|
184
|
+
def prompts: () -> Array[Hash[Symbol, untyped]]
|
|
185
|
+
def call: (String namespaced_name, (Hash[untyped, untyped] | String) arguments) -> Hash[String, untyped]
|
|
186
|
+
def read_resource: (String uri, ?server: String?) -> Hash[String, untyped]
|
|
187
|
+
def get_prompt: (String namespaced_name, ?Hash[untyped, untyped] arguments) -> Hash[String, untyped]
|
|
188
|
+
def stop: () -> void
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
data/sig/zeitwerk.rbs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Minimal type stubs for Zeitwerk. We only use a tiny slice of its API in the
|
|
2
|
+
# gem entry point — `Zeitwerk::Loader.for_gem` plus `#ignore` and `#setup` on
|
|
3
|
+
# the returned loader. The community gem_rbs_collection has fuller sigs; this
|
|
4
|
+
# stub avoids pulling that in for one bootstrap call.
|
|
5
|
+
module Zeitwerk
|
|
6
|
+
class Loader
|
|
7
|
+
def self.for_gem: () -> Loader
|
|
8
|
+
|
|
9
|
+
def ignore: (*String paths) -> void
|
|
10
|
+
|
|
11
|
+
def setup: () -> void
|
|
12
|
+
end
|
|
13
|
+
end
|
data.tar.gz.sig
ADDED
|
Binary file
|