hermes-client 0.0.0 → 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 +4 -4
- data/.yardopts +11 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.md +21 -0
- data/README.md +105 -7
- data/lib/hermes-client.rb +3 -8
- data/lib/hermes_agent/client/configuration.rb +98 -0
- data/lib/hermes_agent/client/conversation.rb +134 -0
- data/lib/hermes_agent/client/entities/capabilities.rb +289 -0
- data/lib/hermes_agent/client/entities/chat_completion.rb +370 -0
- data/lib/hermes_agent/client/entities/health.rb +140 -0
- data/lib/hermes_agent/client/entities/job.rb +394 -0
- data/lib/hermes_agent/client/entities/model.rb +68 -0
- data/lib/hermes_agent/client/entities/response.rb +429 -0
- data/lib/hermes_agent/client/entities/run.rb +427 -0
- data/lib/hermes_agent/client/entities/session_headers.rb +78 -0
- data/lib/hermes_agent/client/entity.rb +89 -0
- data/lib/hermes_agent/client/errors.rb +228 -0
- data/lib/hermes_agent/client/resources/capabilities.rb +34 -0
- data/lib/hermes_agent/client/resources/chat.rb +139 -0
- data/lib/hermes_agent/client/resources/health.rb +49 -0
- data/lib/hermes_agent/client/resources/jobs.rb +213 -0
- data/lib/hermes_agent/client/resources/models.rb +38 -0
- data/lib/hermes_agent/client/resources/responses.rb +204 -0
- data/lib/hermes_agent/client/resources/runs.rb +156 -0
- data/lib/hermes_agent/client/stream.rb +166 -0
- data/lib/hermes_agent/client/transport.rb +281 -0
- data/lib/hermes_agent/client/util.rb +56 -0
- data/lib/hermes_agent/client/version.rb +11 -0
- data/lib/hermes_agent/client.rb +137 -0
- metadata +72 -11
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hermes_agent/client/entity"
|
|
4
|
+
require "hermes_agent/client/entities/session_headers"
|
|
5
|
+
|
|
6
|
+
module HermesAgent
|
|
7
|
+
class Client
|
|
8
|
+
module Entities
|
|
9
|
+
##
|
|
10
|
+
# The token usage reported for a {Response} ({Response#usage}). Note the
|
|
11
|
+
# Responses API uses different field names than chat completions:
|
|
12
|
+
# `input_tokens`/`output_tokens` rather than
|
|
13
|
+
# `prompt_tokens`/`completion_tokens`.
|
|
14
|
+
#
|
|
15
|
+
class ResponseUsage < Entity
|
|
16
|
+
##
|
|
17
|
+
# The number of tokens in the input.
|
|
18
|
+
# @return [Integer, nil]
|
|
19
|
+
#
|
|
20
|
+
def input_tokens
|
|
21
|
+
self["input_tokens"]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
# The number of tokens in the generated output.
|
|
26
|
+
# @return [Integer, nil]
|
|
27
|
+
#
|
|
28
|
+
def output_tokens
|
|
29
|
+
self["output_tokens"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# The total number of tokens used (input plus output).
|
|
34
|
+
# @return [Integer, nil]
|
|
35
|
+
#
|
|
36
|
+
def total_tokens
|
|
37
|
+
self["total_tokens"]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# One content part within a message {ResponseOutputItem} (an entry of
|
|
43
|
+
# {ResponseOutputItem#content}), e.g. `{type: "output_text", text: "…"}`.
|
|
44
|
+
#
|
|
45
|
+
class ResponseContent < Entity
|
|
46
|
+
##
|
|
47
|
+
# The content-part type, e.g. `"output_text"`.
|
|
48
|
+
# @return [String, nil]
|
|
49
|
+
#
|
|
50
|
+
def type
|
|
51
|
+
self["type"]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# The text of the part (for an `output_text` part).
|
|
56
|
+
# @return [String, nil]
|
|
57
|
+
#
|
|
58
|
+
def text
|
|
59
|
+
self["text"]
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# One item in a {Response}'s `output` array. The output is heterogeneous:
|
|
65
|
+
# an item's `type` selects which readers are meaningful. Observed types
|
|
66
|
+
# are `"message"` (an assistant reply, with {#role} and {#content}),
|
|
67
|
+
# `"function_call"` (a tool invocation, with {#name}, {#arguments}, and
|
|
68
|
+
# {#call_id}), and `"function_call_output"` (a tool result, with
|
|
69
|
+
# {#call_id}, {#output}, and {#output_text}). Readers for fields that do
|
|
70
|
+
# not apply to the item's type return `nil`. {#id} and {#status} are
|
|
71
|
+
# populated only on items carried by streaming `response.output_item.*`
|
|
72
|
+
# events, not on items read off a final {Response}.
|
|
73
|
+
#
|
|
74
|
+
class ResponseOutputItem < Entity
|
|
75
|
+
##
|
|
76
|
+
# The item type: `"message"`, `"function_call"`, or
|
|
77
|
+
# `"function_call_output"`.
|
|
78
|
+
# @return [String, nil]
|
|
79
|
+
#
|
|
80
|
+
def type
|
|
81
|
+
self["type"]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
##
|
|
85
|
+
# The item id (`"msg_…"`, `"fc_…"`, or `"fco_…"`). Present on items
|
|
86
|
+
# carried by streaming `response.output_item.*` events; `nil` for items
|
|
87
|
+
# read off a final {Response}'s `output` (the server omits it there).
|
|
88
|
+
# @return [String, nil]
|
|
89
|
+
#
|
|
90
|
+
def id
|
|
91
|
+
self["id"]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
##
|
|
95
|
+
# The item lifecycle status, e.g. `"in_progress"` or `"completed"`.
|
|
96
|
+
# Present on items carried by streaming `response.output_item.*` events;
|
|
97
|
+
# `nil` for items read off a final {Response}'s `output`. Like all such
|
|
98
|
+
# statuses it is a lifecycle marker, not a success signal.
|
|
99
|
+
# @return [String, nil]
|
|
100
|
+
#
|
|
101
|
+
def status
|
|
102
|
+
self["status"]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
##
|
|
106
|
+
# The author role of a `message` item, e.g. `"assistant"`.
|
|
107
|
+
# @return [String, nil]
|
|
108
|
+
#
|
|
109
|
+
def role
|
|
110
|
+
self["role"]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
##
|
|
114
|
+
# The content parts of a `message` item, each wrapped in a
|
|
115
|
+
# {ResponseContent}. Returns `nil` when the field is absent.
|
|
116
|
+
# @return [Array<ResponseContent>, nil]
|
|
117
|
+
#
|
|
118
|
+
def content
|
|
119
|
+
raw = self["content"]
|
|
120
|
+
return nil unless raw.is_a?(::Array)
|
|
121
|
+
|
|
122
|
+
raw.map { |part| ResponseContent.new(part) }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
##
|
|
126
|
+
# The tool name of a `function_call` item.
|
|
127
|
+
# @return [String, nil]
|
|
128
|
+
#
|
|
129
|
+
def name
|
|
130
|
+
self["name"]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
##
|
|
134
|
+
# The arguments of a `function_call` item, as the raw JSON string the
|
|
135
|
+
# server emitted (not parsed).
|
|
136
|
+
# @return [String, nil]
|
|
137
|
+
#
|
|
138
|
+
def arguments
|
|
139
|
+
self["arguments"]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
##
|
|
143
|
+
# The tool-call id linking a `function_call` to its
|
|
144
|
+
# `function_call_output`.
|
|
145
|
+
# @return [String, nil]
|
|
146
|
+
#
|
|
147
|
+
def call_id
|
|
148
|
+
self["call_id"]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
##
|
|
152
|
+
# The raw result of a `function_call_output` item, as the server
|
|
153
|
+
# emitted it. The shape differs by representation: a **raw JSON string**
|
|
154
|
+
# in non-streaming (POST/GET) bodies, but an **array of content parts**
|
|
155
|
+
# (`[{ "type" => "input_text", "text" => … }]`) in the streaming
|
|
156
|
+
# representation. Use {#output_text} for the result text regardless of
|
|
157
|
+
# shape.
|
|
158
|
+
# @return [String, Array<Hash>, nil]
|
|
159
|
+
#
|
|
160
|
+
def output
|
|
161
|
+
self["output"]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
##
|
|
165
|
+
# The result text of a `function_call_output` item, normalized across
|
|
166
|
+
# both {#output} shapes: the string itself when non-streaming, or the
|
|
167
|
+
# concatenated `text` of the content parts when streaming. Returns `nil`
|
|
168
|
+
# when there is no `output` (e.g. a non-output item).
|
|
169
|
+
# @return [String, nil]
|
|
170
|
+
#
|
|
171
|
+
def output_text
|
|
172
|
+
raw = self["output"]
|
|
173
|
+
return raw if raw.is_a?(::String)
|
|
174
|
+
return nil unless raw.is_a?(::Array)
|
|
175
|
+
|
|
176
|
+
raw.filter_map { |part| part["text"] if part.is_a?(::Hash) }.join
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
##
|
|
180
|
+
# The assembled text of a `message` item: the concatenation of its
|
|
181
|
+
# `output_text` content parts. Returns `nil` for an item with no
|
|
182
|
+
# content (e.g. a tool item).
|
|
183
|
+
# @return [String, nil]
|
|
184
|
+
#
|
|
185
|
+
def text
|
|
186
|
+
parts = content
|
|
187
|
+
return nil unless parts
|
|
188
|
+
|
|
189
|
+
parts.filter_map { |part| part.text if part.type == "output_text" }.join
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
##
|
|
194
|
+
# A response from the Responses API (`POST /v1/responses`,
|
|
195
|
+
# `GET /v1/responses/{id}`). The server persists conversation state, so a
|
|
196
|
+
# response can be retrieved later and chained from. Field readers are
|
|
197
|
+
# best-effort; {#to_h} remains the source of truth.
|
|
198
|
+
#
|
|
199
|
+
class Response < Entity
|
|
200
|
+
include SessionHeaders
|
|
201
|
+
|
|
202
|
+
##
|
|
203
|
+
# Build a {Response} from a streamed turn's events. The terminal
|
|
204
|
+
# `response.completed` event carries the full final response object, so
|
|
205
|
+
# this takes the last `response` payload seen across the events (which
|
|
206
|
+
# is that terminal one — `response.created` carries an interim one).
|
|
207
|
+
# Returns a {Response} wrapping an empty payload if no event carried a
|
|
208
|
+
# `response` object.
|
|
209
|
+
#
|
|
210
|
+
# @param events [Array<ResponseStreamEvent>] The streamed events, in
|
|
211
|
+
# order.
|
|
212
|
+
# @param session_id [String, nil] The session id from the response
|
|
213
|
+
# headers, carried onto the assembled response.
|
|
214
|
+
# @param session_key [String, nil] The session key from the response
|
|
215
|
+
# headers, carried onto the assembled response.
|
|
216
|
+
# @return [Response]
|
|
217
|
+
#
|
|
218
|
+
def self.from_events(events, session_id: nil, session_key: nil)
|
|
219
|
+
payload = {}
|
|
220
|
+
events.each do |event|
|
|
221
|
+
raw = event["response"]
|
|
222
|
+
payload = raw if raw.is_a?(::Hash)
|
|
223
|
+
end
|
|
224
|
+
new(payload, session_id: session_id, session_key: session_key)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
##
|
|
228
|
+
# The response id, e.g. `"resp_…"`. Pass it as `previous_response_id` to
|
|
229
|
+
# chain a follow-up turn.
|
|
230
|
+
# @return [String, nil]
|
|
231
|
+
#
|
|
232
|
+
def id
|
|
233
|
+
self["id"]
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
##
|
|
237
|
+
# The object type, `"response"`.
|
|
238
|
+
# @return [String, nil]
|
|
239
|
+
#
|
|
240
|
+
def object
|
|
241
|
+
self["object"]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
##
|
|
245
|
+
# The response status, e.g. `"completed"` or `"in_progress"`.
|
|
246
|
+
# @return [String, nil]
|
|
247
|
+
#
|
|
248
|
+
def status
|
|
249
|
+
self["status"]
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
##
|
|
253
|
+
# When the response was created, as a Unix timestamp (seconds).
|
|
254
|
+
# @return [Integer, nil]
|
|
255
|
+
#
|
|
256
|
+
def created_at
|
|
257
|
+
self["created_at"]
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
##
|
|
261
|
+
# The model that produced the response, e.g. `"hermes-test"`.
|
|
262
|
+
# @return [String, nil]
|
|
263
|
+
#
|
|
264
|
+
def model
|
|
265
|
+
self["model"]
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
##
|
|
269
|
+
# The output items, each wrapped in a {ResponseOutputItem}. Returns
|
|
270
|
+
# `nil` when the field is absent.
|
|
271
|
+
# @return [Array<ResponseOutputItem>, nil]
|
|
272
|
+
#
|
|
273
|
+
def output
|
|
274
|
+
raw = self["output"]
|
|
275
|
+
return nil unless raw.is_a?(::Array)
|
|
276
|
+
|
|
277
|
+
raw.map { |item| ResponseOutputItem.new(item) }
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
##
|
|
281
|
+
# The token usage, wrapped in a {ResponseUsage}. Returns `nil` when the
|
|
282
|
+
# field is absent.
|
|
283
|
+
# @return [ResponseUsage, nil]
|
|
284
|
+
#
|
|
285
|
+
def usage
|
|
286
|
+
raw = self["usage"]
|
|
287
|
+
raw.is_a?(::Hash) ? ResponseUsage.new(raw) : nil
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
##
|
|
291
|
+
# The assistant's text: the concatenation of the text of every
|
|
292
|
+
# `message` output item, ignoring tool items. A convenience over
|
|
293
|
+
# {#output} for the common single-message case. Returns `nil` when
|
|
294
|
+
# there is no output.
|
|
295
|
+
# @return [String, nil]
|
|
296
|
+
#
|
|
297
|
+
def output_text
|
|
298
|
+
items = output
|
|
299
|
+
return nil unless items
|
|
300
|
+
|
|
301
|
+
items.select { |item| item.type == "message" }.filter_map(&:text).join
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
##
|
|
306
|
+
# One event in a streamed Responses turn ({Resources::Responses#stream_create}).
|
|
307
|
+
#
|
|
308
|
+
# The Responses API emits **named** SSE events; each payload repeats the
|
|
309
|
+
# name in its {#type} and carries a 0-based {#sequence_number}. The
|
|
310
|
+
# observed sequence for a simple turn is `response.created` →
|
|
311
|
+
# `response.output_item.added` → `response.output_text.delta` (one per
|
|
312
|
+
# delta) → `response.output_text.done` → `response.output_item.done` →
|
|
313
|
+
# `response.completed` (terminal; there is no `[DONE]` sentinel). Which
|
|
314
|
+
# readers are meaningful depends on {#type}; the rest return `nil`.
|
|
315
|
+
#
|
|
316
|
+
class ResponseStreamEvent < Entity
|
|
317
|
+
##
|
|
318
|
+
# The event type, e.g. `"response.output_text.delta"` or
|
|
319
|
+
# `"response.completed"`.
|
|
320
|
+
# @return [String, nil]
|
|
321
|
+
#
|
|
322
|
+
def type
|
|
323
|
+
self["type"]
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
##
|
|
327
|
+
# The 0-based sequence number of this event within the turn.
|
|
328
|
+
# @return [Integer, nil]
|
|
329
|
+
#
|
|
330
|
+
def sequence_number
|
|
331
|
+
self["sequence_number"]
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
##
|
|
335
|
+
# The incremental text on a `response.output_text.delta` event.
|
|
336
|
+
# @return [String, nil]
|
|
337
|
+
#
|
|
338
|
+
def delta
|
|
339
|
+
self["delta"]
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
##
|
|
343
|
+
# The assembled text on a `response.output_text.done` event.
|
|
344
|
+
# @return [String, nil]
|
|
345
|
+
#
|
|
346
|
+
def text
|
|
347
|
+
self["text"]
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
##
|
|
351
|
+
# The id of the output item a text delta/done event applies to
|
|
352
|
+
# (`"msg_…"`).
|
|
353
|
+
# @return [String, nil]
|
|
354
|
+
#
|
|
355
|
+
def item_id
|
|
356
|
+
self["item_id"]
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
##
|
|
360
|
+
# The index of the output item this event applies to.
|
|
361
|
+
# @return [Integer, nil]
|
|
362
|
+
#
|
|
363
|
+
def output_index
|
|
364
|
+
self["output_index"]
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
##
|
|
368
|
+
# The index of the content part within the item this event applies to.
|
|
369
|
+
# @return [Integer, nil]
|
|
370
|
+
#
|
|
371
|
+
def content_index
|
|
372
|
+
self["content_index"]
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
##
|
|
376
|
+
# The nested response object on a `response.created` or
|
|
377
|
+
# `response.completed` event, wrapped in a {Response}. Returns `nil` on
|
|
378
|
+
# events that carry no response object.
|
|
379
|
+
# @return [Response, nil]
|
|
380
|
+
#
|
|
381
|
+
def response
|
|
382
|
+
raw = self["response"]
|
|
383
|
+
raw.is_a?(::Hash) ? Response.new(raw) : nil
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
##
|
|
387
|
+
# The nested output item on a `response.output_item.added` or
|
|
388
|
+
# `response.output_item.done` event, wrapped in a
|
|
389
|
+
# {ResponseOutputItem}. Returns `nil` on events that carry no item.
|
|
390
|
+
# @return [ResponseOutputItem, nil]
|
|
391
|
+
#
|
|
392
|
+
def item
|
|
393
|
+
raw = self["item"]
|
|
394
|
+
raw.is_a?(::Hash) ? ResponseOutputItem.new(raw) : nil
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
##
|
|
399
|
+
# The result of deleting a response (`DELETE /v1/responses/{id}`):
|
|
400
|
+
# `{id, object: "response", deleted: true}`.
|
|
401
|
+
#
|
|
402
|
+
class ResponseDeletion < Entity
|
|
403
|
+
##
|
|
404
|
+
# The id of the deleted response.
|
|
405
|
+
# @return [String, nil]
|
|
406
|
+
#
|
|
407
|
+
def id
|
|
408
|
+
self["id"]
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
##
|
|
412
|
+
# The object type, `"response"`.
|
|
413
|
+
# @return [String, nil]
|
|
414
|
+
#
|
|
415
|
+
def object
|
|
416
|
+
self["object"]
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
##
|
|
420
|
+
# Whether the response was deleted.
|
|
421
|
+
# @return [boolean, nil]
|
|
422
|
+
#
|
|
423
|
+
def deleted?
|
|
424
|
+
self["deleted"]
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
end
|