lex-claude 0.1.3 → 0.3.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/CHANGELOG.md +28 -0
- data/README.md +144 -37
- data/lib/legion/extensions/claude/client.rb +1 -0
- data/lib/legion/extensions/claude/helpers/client.rb +49 -4
- data/lib/legion/extensions/claude/helpers/errors.rb +71 -0
- data/lib/legion/extensions/claude/helpers/models.rb +48 -0
- data/lib/legion/extensions/claude/helpers/response.rb +61 -0
- data/lib/legion/extensions/claude/helpers/retry.rb +41 -0
- data/lib/legion/extensions/claude/helpers/sse.rb +69 -0
- data/lib/legion/extensions/claude/helpers/tools.rb +32 -0
- data/lib/legion/extensions/claude/runners/batches.rb +6 -5
- data/lib/legion/extensions/claude/runners/messages.rb +96 -24
- data/lib/legion/extensions/claude/runners/models.rb +3 -2
- data/lib/legion/extensions/claude/version.rb +1 -1
- data/lib/legion/extensions/claude.rb +6 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0b422c38d51391b457e88f4760b941afe22a835be9ba5405812a89024e082bb4
|
|
4
|
+
data.tar.gz: 23c95a1d66de519a7f24d9bdf86d522d13cc53b47732bdce3709dcee09df20bd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aa72850dab4628a3fcf6e6f6bd479ec5e3d4c9518710f05ab28f695c3f4b6834bb3615fbb79630b111d66317b13938a0145f281b93b6e0edab3a2e3e91f48c14
|
|
7
|
+
data.tar.gz: a0a9cff50d0bbd456f5eb2fd0270132721ddfad15d60d140aa63277bbb80a604c27aafb1e56ddacfdd89e85d573aec8dc755fa58a8f7fcb907f672f543eb5cec
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-03-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `Helpers::Errors` — structured exception hierarchy (`ApiError`, `RateLimitError`, `OverloadedError`, `AuthenticationError`, `PermissionError`, `NotFoundError`, `InvalidRequestError`, `ServerError`, `StreamingError`); `from_response` factory; `retryable?` predicate
|
|
7
|
+
- `Helpers::Retry` — exponential backoff retry wrapper (`with_retry`) with configurable `max_attempts`, `base_delay`, `max_delay`
|
|
8
|
+
- `Helpers::Sse` — SSE event stream parser (`parse_stream`), text assembler (`collect_text`), usage merger (`collect_usage`)
|
|
9
|
+
- `Helpers::Response` — `handle_response` raises typed exceptions on non-2xx, parses 9 Anthropic rate limit headers, `parse_usage` extracts standard + cache token counts
|
|
10
|
+
- `Helpers::Client::BETA_HEADERS` — registry of 18 named beta identifiers; `client` factory accepts `betas:` array
|
|
11
|
+
- `Helpers::Client.streaming_client` — Faraday connection for SSE responses
|
|
12
|
+
- `Helpers::Tools` — `web_search` factory, `cache_control` helper, `required_betas_for` inspector
|
|
13
|
+
- `Helpers::Models` — registry of 11 canonical Claude model IDs with Symbol alias resolution; `adaptive_thinking?` predicate
|
|
14
|
+
- `Runners::Messages#create_stream` — streaming message creation with SSE event yielding
|
|
15
|
+
- `cache_system:` wraps system prompt in ephemeral cache_control block
|
|
16
|
+
- `cache_scope: :global` auto-injects `prompt-caching-scope-2026-01-05` beta
|
|
17
|
+
- `thinking:` for extended thinking with temperature auto-omission and beta auto-injection
|
|
18
|
+
- `output_config:` for structured output (JSON schema), effort control, task budgets with auto-beta
|
|
19
|
+
- `fast_mode: true` sends `speed: 'fast'` with `fast-mode-2026-02-01` beta
|
|
20
|
+
- `context_management:` with `context-management-2025-06-27` beta auto-injection
|
|
21
|
+
- `:usage` key in all `create` results with `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_write_tokens`
|
|
22
|
+
- All new helpers wired into main `require 'legion/extensions/claude'` tree
|
|
23
|
+
- Updated README with comprehensive examples for all new features
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- All runners raise typed `Helpers::Errors::*` exceptions instead of returning raw status codes
|
|
27
|
+
- `Messages#create` and `#create_stream` refactored to use shared `build_message_body` and `resolve_feature_betas` helpers
|
|
28
|
+
- `Messages#count_tokens` now accepts `thinking:`, `cache_system:` keywords
|
|
29
|
+
- Added `rubocop-legion` for consistent linting
|
|
30
|
+
|
|
3
31
|
## [0.1.3] - 2026-03-30
|
|
4
32
|
|
|
5
33
|
### Changed
|
data/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# lex-claude
|
|
2
2
|
|
|
3
|
-
Claude Anthropic API integration for LegionIO. Provides runners for creating messages
|
|
3
|
+
Production-grade Claude Anthropic API integration for LegionIO. Provides runners for creating messages (streaming and batch), counting tokens, listing models, managing message batches, and accessing all modern Anthropic API features.
|
|
4
4
|
|
|
5
5
|
## Purpose
|
|
6
6
|
|
|
7
|
-
Wraps the Anthropic Claude REST API as named runners consumable by any LegionIO task chain.
|
|
7
|
+
Wraps the Anthropic Claude REST API as named runners consumable by any LegionIO task chain. Supports streaming, prompt caching, extended thinking, structured output, web search, effort control, fast mode, and all beta API features. For simple chat/embed workflows, consider `legion-llm` instead.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
@@ -21,23 +21,27 @@ gem 'lex-claude'
|
|
|
21
21
|
## Functions
|
|
22
22
|
|
|
23
23
|
### Messages
|
|
24
|
-
- `create`
|
|
25
|
-
- `
|
|
24
|
+
- `create` — Create a message (supports caching, thinking, tools, structured output)
|
|
25
|
+
- `create_stream` — Streaming message creation with SSE event yielding
|
|
26
|
+
- `count_tokens` — Count input tokens (supports tools, thinking, caching)
|
|
26
27
|
|
|
27
28
|
### Models
|
|
28
|
-
- `list`
|
|
29
|
-
- `retrieve`
|
|
29
|
+
- `list` — List available Claude models
|
|
30
|
+
- `retrieve` — Get details for a specific model
|
|
30
31
|
|
|
31
32
|
### Batches
|
|
32
|
-
- `create_batch`
|
|
33
|
-
- `list_batches`
|
|
34
|
-
- `retrieve_batch`
|
|
35
|
-
- `cancel_batch`
|
|
36
|
-
- `batch_results`
|
|
33
|
+
- `create_batch` — Create an asynchronous message batch
|
|
34
|
+
- `list_batches` — List message batches
|
|
35
|
+
- `retrieve_batch` — Get details for a specific batch
|
|
36
|
+
- `cancel_batch` — Cancel an in-progress batch
|
|
37
|
+
- `batch_results` — Retrieve results for a completed batch
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
### Helpers
|
|
40
|
+
- `Helpers::Tools.web_search` — Build web search tool descriptor
|
|
41
|
+
- `Helpers::Models.resolve` — Resolve model Symbol aliases to canonical IDs
|
|
42
|
+
- `Helpers::Errors` — Structured exception hierarchy
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
## Configuration
|
|
41
45
|
|
|
42
46
|
```json
|
|
43
47
|
{
|
|
@@ -47,47 +51,150 @@ Set your API key in your LegionIO settings:
|
|
|
47
51
|
}
|
|
48
52
|
```
|
|
49
53
|
|
|
50
|
-
##
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### Basic message
|
|
51
57
|
|
|
52
58
|
```ruby
|
|
53
59
|
require 'legion/extensions/claude/client'
|
|
54
60
|
|
|
55
61
|
client = Legion::Extensions::Claude::Client.new(api_key: ENV['ANTHROPIC_API_KEY'])
|
|
56
62
|
|
|
57
|
-
# Create a message
|
|
58
63
|
result = client.create(
|
|
59
|
-
model: 'claude-
|
|
60
|
-
messages: [{ role: 'user', content: 'Hello
|
|
64
|
+
model: 'claude-sonnet-4-6',
|
|
65
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
61
66
|
max_tokens: 1024
|
|
62
67
|
)
|
|
63
68
|
puts result[:result]['content'].first['text']
|
|
69
|
+
puts result[:usage].inspect
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Streaming
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
client.create_stream(
|
|
76
|
+
model: 'claude-sonnet-4-6',
|
|
77
|
+
messages: [{ role: 'user', content: 'Tell me a story.' }],
|
|
78
|
+
max_tokens: 2048
|
|
79
|
+
) do |event|
|
|
80
|
+
print event[:data].dig('delta', 'text') if event[:event] == 'content_block_delta'
|
|
81
|
+
end
|
|
82
|
+
```
|
|
64
83
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
84
|
+
### Prompt caching
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
result = client.create(
|
|
88
|
+
model: 'claude-sonnet-4-6',
|
|
89
|
+
messages: [{ role: 'user', content: 'Summarize this.' }],
|
|
90
|
+
system: 'You are a helpful assistant with deep context about...',
|
|
91
|
+
cache_system: true,
|
|
92
|
+
cache_scope: :global,
|
|
93
|
+
max_tokens: 512
|
|
94
|
+
)
|
|
95
|
+
puts result[:usage][:cache_read_tokens]
|
|
96
|
+
```
|
|
68
97
|
|
|
69
|
-
|
|
70
|
-
|
|
98
|
+
### Extended thinking
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
result = client.create(
|
|
71
102
|
model: 'claude-opus-4-6',
|
|
72
|
-
messages: [{ role: 'user', content: '
|
|
103
|
+
messages: [{ role: 'user', content: 'Solve this complex problem...' }],
|
|
104
|
+
thinking: { type: 'adaptive' },
|
|
105
|
+
max_tokens: 8192
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Structured output
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
result = client.create(
|
|
113
|
+
model: 'claude-sonnet-4-6',
|
|
114
|
+
messages: [{ role: 'user', content: 'Extract the name and age.' }],
|
|
115
|
+
max_tokens: 256,
|
|
116
|
+
output_config: {
|
|
117
|
+
format: {
|
|
118
|
+
type: 'json_schema',
|
|
119
|
+
json_schema: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: { name: { type: 'string' }, age: { type: 'integer' } },
|
|
122
|
+
required: %w[name age]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Web search
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
web_tool = Legion::Extensions::Claude::Helpers::Tools.web_search(max_uses: 3)
|
|
133
|
+
|
|
134
|
+
result = client.create(
|
|
135
|
+
model: 'claude-sonnet-4-6',
|
|
136
|
+
messages: [{ role: 'user', content: 'What happened in the news today?' }],
|
|
137
|
+
tools: [web_tool],
|
|
138
|
+
betas: [:web_search],
|
|
139
|
+
max_tokens: 1024
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Effort control and fast mode
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
result = client.create(
|
|
147
|
+
model: 'claude-sonnet-4-6',
|
|
148
|
+
messages: messages,
|
|
149
|
+
max_tokens: 2048,
|
|
150
|
+
output_config: { effort: 'high' }
|
|
73
151
|
)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
messages: [{ role: 'user', content: 'Hello' }],
|
|
81
|
-
max_tokens: 100 } }
|
|
82
|
-
]
|
|
152
|
+
|
|
153
|
+
result = client.create(
|
|
154
|
+
model: 'claude-sonnet-4-6',
|
|
155
|
+
messages: messages,
|
|
156
|
+
max_tokens: 512,
|
|
157
|
+
fast_mode: true
|
|
83
158
|
)
|
|
84
|
-
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Beta headers
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
result = client.create(
|
|
165
|
+
model: 'claude-sonnet-4-6',
|
|
166
|
+
messages: messages,
|
|
167
|
+
max_tokens: 1024,
|
|
168
|
+
betas: [:token_efficient_tools, :advanced_tool_use]
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Error handling
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
begin
|
|
176
|
+
result = client.create(model: 'claude-sonnet-4-6', messages: messages, max_tokens: 512)
|
|
177
|
+
rescue Legion::Extensions::Claude::Helpers::Errors::RateLimitError => e
|
|
178
|
+
puts "Rate limited (#{e.status}): #{e.message}"
|
|
179
|
+
rescue Legion::Extensions::Claude::Helpers::Errors::AuthenticationError
|
|
180
|
+
puts 'Check your API key'
|
|
181
|
+
rescue Legion::Extensions::Claude::Helpers::Errors::ApiError => e
|
|
182
|
+
puts "API error #{e.status}: #{e.message}"
|
|
183
|
+
end
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Auto-retry
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
result = Legion::Extensions::Claude::Helpers::Retry.with_retry(max_attempts: 3) do
|
|
190
|
+
client.create(model: 'claude-sonnet-4-6', messages: messages, max_tokens: 512)
|
|
191
|
+
end
|
|
85
192
|
```
|
|
86
193
|
|
|
87
194
|
## Dependencies
|
|
88
195
|
|
|
89
|
-
- `faraday` >= 2.0
|
|
90
|
-
- `multi_json`
|
|
196
|
+
- `faraday` >= 2.0 — HTTP client
|
|
197
|
+
- `multi_json` — JSON parser abstraction
|
|
91
198
|
|
|
92
199
|
## Requirements
|
|
93
200
|
|
|
@@ -97,8 +204,8 @@ puts batch[:result]['id']
|
|
|
97
204
|
|
|
98
205
|
## Related
|
|
99
206
|
|
|
100
|
-
- `lex-bedrock` — Access Claude models via AWS Bedrock
|
|
101
|
-
- `legion-llm` — High-level LLM interface
|
|
207
|
+
- `lex-bedrock` — Access Claude models via AWS Bedrock
|
|
208
|
+
- `legion-llm` — High-level LLM interface
|
|
102
209
|
- `extensions-ai/CLAUDE.md` — Architecture patterns shared across all AI extensions
|
|
103
210
|
|
|
104
211
|
## License
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/claude/helpers/client'
|
|
4
|
+
require 'legion/extensions/claude/helpers/models'
|
|
4
5
|
require 'legion/extensions/claude/runners/messages'
|
|
5
6
|
require 'legion/extensions/claude/runners/models'
|
|
6
7
|
require 'legion/extensions/claude/runners/batches'
|
|
@@ -9,19 +9,64 @@ module Legion
|
|
|
9
9
|
module Helpers
|
|
10
10
|
module Client
|
|
11
11
|
DEFAULT_HOST = 'https://api.anthropic.com'
|
|
12
|
-
API_VERSION
|
|
12
|
+
API_VERSION = '2023-06-01'
|
|
13
|
+
|
|
14
|
+
BETA_HEADERS = {
|
|
15
|
+
interleaved_thinking: 'interleaved-thinking-2025-05-14',
|
|
16
|
+
context_1m: 'context-1m-2025-08-07',
|
|
17
|
+
context_management: 'context-management-2025-06-27',
|
|
18
|
+
structured_outputs: 'structured-outputs-2025-12-15',
|
|
19
|
+
web_search: 'web-search-2025-03-05',
|
|
20
|
+
advanced_tool_use: 'advanced-tool-use-2025-11-20',
|
|
21
|
+
effort: 'effort-2025-11-24',
|
|
22
|
+
task_budgets: 'task-budgets-2026-03-13',
|
|
23
|
+
prompt_caching_scope: 'prompt-caching-scope-2026-01-05',
|
|
24
|
+
fast_mode: 'fast-mode-2026-02-01',
|
|
25
|
+
redact_thinking: 'redact-thinking-2026-02-12',
|
|
26
|
+
token_efficient_tools: 'token-efficient-tools-2026-03-28',
|
|
27
|
+
summarize_connector: 'summarize-connector-text-2026-03-13',
|
|
28
|
+
afk_mode: 'afk-mode-2026-01-31',
|
|
29
|
+
advisor: 'advisor-tool-2026-03-01',
|
|
30
|
+
files_api: 'files-api-2025-04-14',
|
|
31
|
+
claude_code: 'claude-code-20250219',
|
|
32
|
+
tool_search: 'tool-search-tool-2025-10-19'
|
|
33
|
+
}.freeze
|
|
13
34
|
|
|
14
35
|
module_function
|
|
15
36
|
|
|
16
|
-
def client(api_key:, host: DEFAULT_HOST, **_opts)
|
|
37
|
+
def client(api_key:, host: DEFAULT_HOST, betas: nil, **_opts)
|
|
38
|
+
beta_list = resolve_betas(betas)
|
|
39
|
+
|
|
17
40
|
Faraday.new(url: host) do |conn|
|
|
18
41
|
conn.request :json
|
|
19
42
|
conn.response :json, content_type: /\bjson$/
|
|
20
43
|
conn.headers['x-api-key'] = api_key
|
|
21
|
-
conn.headers['anthropic-version']
|
|
22
|
-
conn.headers['Content-Type']
|
|
44
|
+
conn.headers['anthropic-version'] = API_VERSION
|
|
45
|
+
conn.headers['Content-Type'] = 'application/json'
|
|
46
|
+
conn.headers['anthropic-beta'] = beta_list.join(',') if beta_list.any?
|
|
23
47
|
end
|
|
24
48
|
end
|
|
49
|
+
|
|
50
|
+
def streaming_client(api_key:, host: DEFAULT_HOST, betas: nil, **_opts)
|
|
51
|
+
beta_list = resolve_betas(betas)
|
|
52
|
+
|
|
53
|
+
Faraday.new(url: host) do |conn|
|
|
54
|
+
conn.headers['x-api-key'] = api_key
|
|
55
|
+
conn.headers['anthropic-version'] = API_VERSION
|
|
56
|
+
conn.headers['Content-Type'] = 'application/json'
|
|
57
|
+
conn.headers['Accept'] = 'text/event-stream'
|
|
58
|
+
conn.headers['anthropic-beta'] = beta_list.join(',') if beta_list.any?
|
|
59
|
+
conn.adapter Faraday.default_adapter
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def resolve_betas(betas)
|
|
64
|
+
return [] if betas.nil? || betas.empty?
|
|
65
|
+
|
|
66
|
+
betas.filter_map do |b|
|
|
67
|
+
b.is_a?(Symbol) ? BETA_HEADERS[b] : b.to_s
|
|
68
|
+
end.uniq
|
|
69
|
+
end
|
|
25
70
|
end
|
|
26
71
|
end
|
|
27
72
|
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Claude
|
|
6
|
+
module Helpers
|
|
7
|
+
module Errors
|
|
8
|
+
class ApiError < StandardError
|
|
9
|
+
attr_reader :status, :error_type, :body
|
|
10
|
+
|
|
11
|
+
def initialize(message = nil, status: nil, error_type: nil, body: nil)
|
|
12
|
+
super(message)
|
|
13
|
+
@status = status
|
|
14
|
+
@error_type = error_type
|
|
15
|
+
@body = body
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class AuthenticationError < ApiError; end
|
|
20
|
+
class PermissionError < ApiError; end
|
|
21
|
+
class NotFoundError < ApiError; end
|
|
22
|
+
class RateLimitError < ApiError; end
|
|
23
|
+
class OverloadedError < ApiError; end
|
|
24
|
+
class InvalidRequestError < ApiError; end
|
|
25
|
+
class ServerError < ApiError; end
|
|
26
|
+
class StreamingError < ApiError; end
|
|
27
|
+
|
|
28
|
+
STATUS_MAP = {
|
|
29
|
+
401 => AuthenticationError,
|
|
30
|
+
403 => PermissionError,
|
|
31
|
+
404 => NotFoundError,
|
|
32
|
+
429 => RateLimitError,
|
|
33
|
+
529 => OverloadedError
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
TYPE_MAP = {
|
|
37
|
+
'authentication_error' => AuthenticationError,
|
|
38
|
+
'permission_error' => PermissionError,
|
|
39
|
+
'not_found_error' => NotFoundError,
|
|
40
|
+
'rate_limit_error' => RateLimitError,
|
|
41
|
+
'overloaded_error' => OverloadedError,
|
|
42
|
+
'invalid_request_error' => InvalidRequestError,
|
|
43
|
+
'server_error' => ServerError,
|
|
44
|
+
'streaming_error' => StreamingError
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
RETRYABLE = [RateLimitError, OverloadedError].freeze
|
|
48
|
+
|
|
49
|
+
module_function
|
|
50
|
+
|
|
51
|
+
def from_response(status:, body:)
|
|
52
|
+
error_hash = body.is_a?(Hash) ? (body[:error] || body['error']) : nil # rubocop:disable Legion/Framework/ApiStringKeys
|
|
53
|
+
error_type = error_hash.is_a?(Hash) ? (error_hash[:type] || error_hash['type']) : nil
|
|
54
|
+
message = error_hash.is_a?(Hash) ? (error_hash[:message] || error_hash['message']) : nil
|
|
55
|
+
message ||= body.to_s
|
|
56
|
+
|
|
57
|
+
klass = TYPE_MAP[error_type] ||
|
|
58
|
+
STATUS_MAP[status] ||
|
|
59
|
+
(status >= 500 ? ServerError : InvalidRequestError)
|
|
60
|
+
|
|
61
|
+
klass.new(message, status: status, error_type: error_type, body: body)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def retryable?(error)
|
|
65
|
+
RETRYABLE.any? { |klass| error.is_a?(klass) }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Claude
|
|
6
|
+
module Helpers
|
|
7
|
+
module Models
|
|
8
|
+
# rubocop:disable Naming/VariableNumber
|
|
9
|
+
MODELS = {
|
|
10
|
+
haiku_3_5: 'claude-3-5-haiku-20241022',
|
|
11
|
+
haiku_4_5: 'claude-haiku-4-5-20251001',
|
|
12
|
+
sonnet_3_5: 'claude-3-5-sonnet-20241022',
|
|
13
|
+
sonnet_3_7: 'claude-3-7-sonnet-20250219',
|
|
14
|
+
sonnet_4: 'claude-sonnet-4-20250514',
|
|
15
|
+
sonnet_4_5: 'claude-sonnet-4-5-20250929',
|
|
16
|
+
sonnet_4_6: 'claude-sonnet-4-6',
|
|
17
|
+
opus_4: 'claude-opus-4-20250514',
|
|
18
|
+
opus_4_1: 'claude-opus-4-1-20250805',
|
|
19
|
+
opus_4_5: 'claude-opus-4-5-20251101',
|
|
20
|
+
opus_4_6: 'claude-opus-4-6'
|
|
21
|
+
}.freeze
|
|
22
|
+
# rubocop:enable Naming/VariableNumber
|
|
23
|
+
|
|
24
|
+
ADAPTIVE_THINKING_MODELS = %w[
|
|
25
|
+
claude-sonnet-4-20250514
|
|
26
|
+
claude-sonnet-4-5-20250929
|
|
27
|
+
claude-sonnet-4-6
|
|
28
|
+
claude-opus-4-20250514
|
|
29
|
+
claude-opus-4-1-20250805
|
|
30
|
+
claude-opus-4-5-20251101
|
|
31
|
+
claude-opus-4-6
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
module_function
|
|
35
|
+
|
|
36
|
+
def resolve(model)
|
|
37
|
+
key = model.is_a?(Symbol) ? model : model.to_s.to_sym
|
|
38
|
+
MODELS.fetch(key, model.to_s)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def adaptive_thinking?(model_id)
|
|
42
|
+
ADAPTIVE_THINKING_MODELS.include?(model_id.to_s)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/claude/helpers/errors'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Claude
|
|
8
|
+
module Helpers
|
|
9
|
+
module Response
|
|
10
|
+
RATE_LIMIT_HEADERS = {
|
|
11
|
+
'anthropic-ratelimit-unified-status' => :status,
|
|
12
|
+
'anthropic-ratelimit-unified-reset' => :reset,
|
|
13
|
+
'anthropic-ratelimit-unified-fallback' => :fallback,
|
|
14
|
+
'anthropic-ratelimit-unified-5h-utilization' => :utilization_5h,
|
|
15
|
+
'anthropic-ratelimit-unified-5h-reset' => :reset_5h,
|
|
16
|
+
'anthropic-ratelimit-unified-7d-utilization' => :utilization_7d,
|
|
17
|
+
'anthropic-ratelimit-unified-7d-reset' => :reset_7d,
|
|
18
|
+
'anthropic-ratelimit-unified-overage-status' => :overage_status,
|
|
19
|
+
'anthropic-ratelimit-unified-overage-reset' => :overage_reset
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
FLOAT_KEYS = %i[utilization_5h utilization_7d].freeze
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
def handle_response(response)
|
|
27
|
+
raise Errors.from_response(status: response.status, body: response.body) unless response.status >= 200 && response.status < 300
|
|
28
|
+
|
|
29
|
+
result = { result: response.body, status: response.status }
|
|
30
|
+
rate_info = parse_rate_limit_headers(response.headers)
|
|
31
|
+
result[:rate_limit] = rate_info unless rate_info.empty?
|
|
32
|
+
result
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def parse_rate_limit_headers(headers)
|
|
36
|
+
return {} if headers.nil? || headers.empty?
|
|
37
|
+
|
|
38
|
+
parsed = {}
|
|
39
|
+
RATE_LIMIT_HEADERS.each do |header_name, key|
|
|
40
|
+
value = headers[header_name]
|
|
41
|
+
next if value.nil?
|
|
42
|
+
|
|
43
|
+
parsed[key] = FLOAT_KEYS.include?(key) ? value.to_f : value
|
|
44
|
+
end
|
|
45
|
+
parsed
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def parse_usage(body)
|
|
49
|
+
usage = body.is_a?(Hash) ? (body[:usage] || body['usage'] || {}) : {} # rubocop:disable Legion/Framework/ApiStringKeys
|
|
50
|
+
{
|
|
51
|
+
input_tokens: (usage[:input_tokens] || usage['input_tokens'] || 0).to_i,
|
|
52
|
+
output_tokens: (usage[:output_tokens] || usage['output_tokens'] || 0).to_i,
|
|
53
|
+
cache_read_tokens: (usage[:cache_read_input_tokens] || usage['cache_read_input_tokens'] || 0).to_i,
|
|
54
|
+
cache_write_tokens: (usage[:cache_creation_input_tokens] || usage['cache_creation_input_tokens'] || 0).to_i
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/claude/helpers/errors'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Claude
|
|
8
|
+
module Helpers
|
|
9
|
+
module Retry
|
|
10
|
+
DEFAULT_MAX_ATTEMPTS = 3
|
|
11
|
+
DEFAULT_BASE_DELAY = 1.0
|
|
12
|
+
DEFAULT_MAX_DELAY = 60.0
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def with_retry(max_attempts: DEFAULT_MAX_ATTEMPTS, base_delay: DEFAULT_BASE_DELAY,
|
|
17
|
+
max_delay: DEFAULT_MAX_DELAY)
|
|
18
|
+
attempt = 0
|
|
19
|
+
begin
|
|
20
|
+
yield
|
|
21
|
+
rescue Errors::ApiError => e
|
|
22
|
+
raise unless Errors.retryable?(e)
|
|
23
|
+
|
|
24
|
+
attempt += 1
|
|
25
|
+
raise if attempt >= max_attempts
|
|
26
|
+
|
|
27
|
+
delay = backoff_seconds(attempt: attempt - 1, base_delay: base_delay, max_delay: max_delay)
|
|
28
|
+
sleep(delay) if delay.positive?
|
|
29
|
+
retry
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def backoff_seconds(attempt:, base_delay: DEFAULT_BASE_DELAY, max_delay: DEFAULT_MAX_DELAY)
|
|
34
|
+
raw = base_delay * (2**attempt)
|
|
35
|
+
[raw, max_delay].min.to_f
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'multi_json'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Claude
|
|
8
|
+
module Helpers
|
|
9
|
+
module Sse
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def parse_stream(raw, include_pings: false)
|
|
13
|
+
events = []
|
|
14
|
+
current_event = nil
|
|
15
|
+
|
|
16
|
+
raw.each_line do |line|
|
|
17
|
+
line = line.chomp
|
|
18
|
+
if line.start_with?('event:')
|
|
19
|
+
current_event = line.sub(/^event:\s*/, '').strip
|
|
20
|
+
elsif line.start_with?('data:')
|
|
21
|
+
next if current_event == 'ping' && !include_pings
|
|
22
|
+
|
|
23
|
+
json_str = line.sub(/^data:\s*/, '').strip
|
|
24
|
+
next if json_str.empty?
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
data = MultiJson.load(json_str)
|
|
28
|
+
events << { event: current_event, data: data }
|
|
29
|
+
rescue MultiJson::ParseError => e
|
|
30
|
+
log.warn("SSE parse error: #{e.message}")
|
|
31
|
+
next
|
|
32
|
+
end
|
|
33
|
+
current_event = nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
events
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def collect_text(events)
|
|
41
|
+
events
|
|
42
|
+
.select { |e| e[:event] == 'content_block_delta' && e[:data].dig('delta', 'type') == 'text_delta' }
|
|
43
|
+
.map { |e| e[:data].dig('delta', 'text').to_s }
|
|
44
|
+
.join
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def collect_usage(events)
|
|
48
|
+
input_tokens = 0
|
|
49
|
+
output_tokens = 0
|
|
50
|
+
|
|
51
|
+
events.each do |e|
|
|
52
|
+
case e[:event]
|
|
53
|
+
when 'message_start'
|
|
54
|
+
usage = e[:data].dig('message', 'usage') || {}
|
|
55
|
+
input_tokens += usage.fetch('input_tokens', 0).to_i
|
|
56
|
+
output_tokens += usage.fetch('output_tokens', 0).to_i
|
|
57
|
+
when 'message_delta'
|
|
58
|
+
usage = e[:data].fetch('usage', {})
|
|
59
|
+
output_tokens += usage.fetch('output_tokens', 0).to_i
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
{ input_tokens: input_tokens, output_tokens: output_tokens }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Claude
|
|
6
|
+
module Helpers
|
|
7
|
+
module Tools
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def web_search(max_uses: 5, allowed_domains: nil, blocked_domains: nil)
|
|
11
|
+
tool = { type: 'web_search_20250305', max_uses: max_uses }
|
|
12
|
+
tool[:allowed_domains] = allowed_domains if allowed_domains
|
|
13
|
+
tool[:blocked_domains] = blocked_domains if blocked_domains
|
|
14
|
+
tool
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def cache_control
|
|
18
|
+
{ type: 'ephemeral' }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def required_betas_for(tools)
|
|
22
|
+
return [] if tools.nil? || tools.empty?
|
|
23
|
+
|
|
24
|
+
betas = []
|
|
25
|
+
betas << :web_search if tools.any? { |t| t.is_a?(Hash) && t[:type].to_s.start_with?('web_search') }
|
|
26
|
+
betas
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/claude/helpers/client'
|
|
4
|
+
require 'legion/extensions/claude/helpers/response'
|
|
4
5
|
|
|
5
6
|
module Legion
|
|
6
7
|
module Extensions
|
|
@@ -12,7 +13,7 @@ module Legion
|
|
|
12
13
|
def create_batch(api_key:, requests:, **)
|
|
13
14
|
body = { requests: requests }
|
|
14
15
|
response = client(api_key: api_key, **).post('/v1/messages/batches', body)
|
|
15
|
-
|
|
16
|
+
Helpers::Response.handle_response(response)
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def list_batches(api_key:, limit: 20, before_id: nil, after_id: nil, **)
|
|
@@ -21,22 +22,22 @@ module Legion
|
|
|
21
22
|
params[:after_id] = after_id if after_id
|
|
22
23
|
|
|
23
24
|
response = client(api_key: api_key, **).get('/v1/messages/batches', params)
|
|
24
|
-
|
|
25
|
+
Helpers::Response.handle_response(response)
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def retrieve_batch(api_key:, batch_id:, **)
|
|
28
29
|
response = client(api_key: api_key, **).get("/v1/messages/batches/#{batch_id}")
|
|
29
|
-
|
|
30
|
+
Helpers::Response.handle_response(response)
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
def cancel_batch(api_key:, batch_id:, **)
|
|
33
34
|
response = client(api_key: api_key, **).post("/v1/messages/batches/#{batch_id}/cancel")
|
|
34
|
-
|
|
35
|
+
Helpers::Response.handle_response(response)
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def batch_results(api_key:, batch_id:, **)
|
|
38
39
|
response = client(api_key: api_key, **).get("/v1/messages/batches/#{batch_id}/results")
|
|
39
|
-
|
|
40
|
+
Helpers::Response.handle_response(response)
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/claude/helpers/client'
|
|
4
|
+
require 'legion/extensions/claude/helpers/response'
|
|
5
|
+
require 'legion/extensions/claude/helpers/sse'
|
|
4
6
|
|
|
5
7
|
module Legion
|
|
6
8
|
module Extensions
|
|
@@ -9,35 +11,105 @@ module Legion
|
|
|
9
11
|
module Messages
|
|
10
12
|
extend Legion::Extensions::Claude::Helpers::Client
|
|
11
13
|
|
|
12
|
-
def create(api_key:, model:, messages:, max_tokens: 1024,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
def create(api_key:, model:, messages:, max_tokens: 1024, stream: false, betas: nil, **opts)
|
|
15
|
+
body = build_message_body(model: model, messages: messages, max_tokens: max_tokens, stream: stream, **opts)
|
|
16
|
+
resolved_betas = resolve_feature_betas(betas, opts)
|
|
17
|
+
|
|
18
|
+
response = client(api_key: api_key, betas: resolved_betas, **opts).post('/v1/messages', body)
|
|
19
|
+
result = Helpers::Response.handle_response(response)
|
|
20
|
+
result[:usage] = Helpers::Response.parse_usage(response.body) if response.body.is_a?(Hash)
|
|
21
|
+
result
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_stream(api_key:, model:, messages:, max_tokens: 1024, betas: nil, **opts, &block)
|
|
25
|
+
body = build_message_body(model: model, messages: messages, max_tokens: max_tokens, stream: true, **opts)
|
|
26
|
+
resolved_betas = resolve_feature_betas(betas, opts)
|
|
27
|
+
|
|
28
|
+
raw_body = +''
|
|
29
|
+
conn = Helpers::Client.streaming_client(api_key: api_key, betas: resolved_betas)
|
|
30
|
+
response = conn.post('/v1/messages', MultiJson.dump(body)) do |req|
|
|
31
|
+
req.options.on_data = proc { |chunk, _bytes| raw_body << chunk }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
raise Helpers::Errors.from_response(status: response.status, body: {}) unless response.status == 200
|
|
35
|
+
|
|
36
|
+
raw_body = response.body if raw_body.empty? && response.body.is_a?(String)
|
|
37
|
+
|
|
38
|
+
events = Helpers::Sse.parse_stream(raw_body)
|
|
39
|
+
events.each(&block) if block
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
result: Helpers::Sse.collect_text(events),
|
|
43
|
+
events: events,
|
|
44
|
+
usage: Helpers::Sse.collect_usage(events),
|
|
45
|
+
status: 200
|
|
20
46
|
}
|
|
21
|
-
body[:system] = system if system
|
|
22
|
-
body[:temperature] = temperature if temperature
|
|
23
|
-
body[:top_p] = top_p if top_p
|
|
24
|
-
body[:top_k] = top_k if top_k
|
|
25
|
-
body[:stop_sequences] = stop_sequences if stop_sequences
|
|
26
|
-
body[:metadata] = metadata if metadata
|
|
27
|
-
body[:tools] = tools if tools
|
|
28
|
-
body[:tool_choice] = tool_choice if tool_choice
|
|
29
|
-
|
|
30
|
-
response = client(api_key: api_key, **).post('/v1/messages', body)
|
|
31
|
-
{ result: response.body, status: response.status }
|
|
32
47
|
end
|
|
33
48
|
|
|
34
|
-
def count_tokens(api_key:, model:, messages:,
|
|
49
|
+
def count_tokens(api_key:, model:, messages:, betas: nil, **opts)
|
|
50
|
+
system = opts[:system]
|
|
51
|
+
tools = opts[:tools]
|
|
52
|
+
thinking = opts[:thinking]
|
|
53
|
+
cache_system = opts.fetch(:cache_system, false)
|
|
54
|
+
|
|
35
55
|
body = { model: model, messages: messages }
|
|
36
|
-
body[:system]
|
|
37
|
-
body[:tools]
|
|
56
|
+
body[:system] = build_system(system, cache_system) if system
|
|
57
|
+
body[:tools] = tools if tools
|
|
58
|
+
body[:thinking] = thinking if thinking
|
|
59
|
+
|
|
60
|
+
resolved_betas = Array(betas).dup
|
|
61
|
+
resolved_betas << :interleaved_thinking if thinking && !resolved_betas.include?(:interleaved_thinking)
|
|
62
|
+
|
|
63
|
+
response = client(api_key: api_key, betas: resolved_betas).post('/v1/messages/count_tokens', body)
|
|
64
|
+
Helpers::Response.handle_response(response)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def build_message_body(model:, messages:, max_tokens:, stream:, system: nil, temperature: nil, # rubocop:disable Metrics/ParameterLists
|
|
70
|
+
top_p: nil, top_k: nil, stop_sequences: nil, metadata: nil, tools: nil,
|
|
71
|
+
tool_choice: nil, cache_system: false, thinking: nil, output_config: nil,
|
|
72
|
+
fast_mode: false, context_management: nil, **)
|
|
73
|
+
body = { model: model, messages: messages, max_tokens: max_tokens, stream: stream }
|
|
74
|
+
|
|
75
|
+
body[:system] = build_system(system, cache_system) if system
|
|
76
|
+
body[:top_p] = top_p if top_p
|
|
77
|
+
body[:top_k] = top_k if top_k
|
|
78
|
+
body[:stop_sequences] = stop_sequences if stop_sequences
|
|
79
|
+
body[:metadata] = metadata if metadata
|
|
80
|
+
body[:tools] = tools if tools
|
|
81
|
+
body[:tool_choice] = tool_choice if tool_choice
|
|
82
|
+
body[:output_config] = output_config if output_config
|
|
83
|
+
body[:speed] = 'fast' if fast_mode
|
|
84
|
+
body[:context_management] = context_management if context_management
|
|
85
|
+
|
|
86
|
+
if thinking
|
|
87
|
+
body[:thinking] = thinking
|
|
88
|
+
elsif temperature
|
|
89
|
+
body[:temperature] = temperature
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
body
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def resolve_feature_betas(betas, opts)
|
|
96
|
+
resolved = Array(betas).dup
|
|
97
|
+
resolved << :prompt_caching_scope if opts[:cache_scope] == :global
|
|
98
|
+
resolved << :interleaved_thinking if opts[:thinking] && !resolved.include?(:interleaved_thinking)
|
|
99
|
+
resolved << :structured_outputs if opts[:output_config]&.key?(:format)
|
|
100
|
+
resolved << :effort if opts[:output_config]&.key?(:effort)
|
|
101
|
+
resolved << :task_budgets if opts[:output_config]&.key?(:task_budget)
|
|
102
|
+
resolved << :fast_mode if opts[:fast_mode]
|
|
103
|
+
resolved << :context_management if opts[:context_management]
|
|
104
|
+
resolved
|
|
105
|
+
end
|
|
38
106
|
|
|
39
|
-
|
|
40
|
-
|
|
107
|
+
def build_system(system, cache_system)
|
|
108
|
+
if cache_system
|
|
109
|
+
[{ type: 'text', text: system, cache_control: { type: 'ephemeral' } }]
|
|
110
|
+
else
|
|
111
|
+
system
|
|
112
|
+
end
|
|
41
113
|
end
|
|
42
114
|
|
|
43
115
|
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/claude/helpers/client'
|
|
4
|
+
require 'legion/extensions/claude/helpers/response'
|
|
4
5
|
|
|
5
6
|
module Legion
|
|
6
7
|
module Extensions
|
|
@@ -15,12 +16,12 @@ module Legion
|
|
|
15
16
|
params[:after_id] = after_id if after_id
|
|
16
17
|
|
|
17
18
|
response = client(api_key: api_key, **).get('/v1/models', params)
|
|
18
|
-
|
|
19
|
+
Helpers::Response.handle_response(response)
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def retrieve(api_key:, model_id:, **)
|
|
22
23
|
response = client(api_key: api_key, **).get("/v1/models/#{model_id}")
|
|
23
|
-
|
|
24
|
+
Helpers::Response.handle_response(response)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/claude/version'
|
|
4
4
|
require 'legion/extensions/claude/helpers/client'
|
|
5
|
+
require 'legion/extensions/claude/helpers/errors'
|
|
6
|
+
require 'legion/extensions/claude/helpers/retry'
|
|
7
|
+
require 'legion/extensions/claude/helpers/sse'
|
|
8
|
+
require 'legion/extensions/claude/helpers/response'
|
|
9
|
+
require 'legion/extensions/claude/helpers/tools'
|
|
10
|
+
require 'legion/extensions/claude/helpers/models'
|
|
5
11
|
require 'legion/extensions/claude/runners/messages'
|
|
6
12
|
require 'legion/extensions/claude/runners/models'
|
|
7
13
|
require 'legion/extensions/claude/runners/batches'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-claude
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -158,6 +158,12 @@ files:
|
|
|
158
158
|
- lib/legion/extensions/claude.rb
|
|
159
159
|
- lib/legion/extensions/claude/client.rb
|
|
160
160
|
- lib/legion/extensions/claude/helpers/client.rb
|
|
161
|
+
- lib/legion/extensions/claude/helpers/errors.rb
|
|
162
|
+
- lib/legion/extensions/claude/helpers/models.rb
|
|
163
|
+
- lib/legion/extensions/claude/helpers/response.rb
|
|
164
|
+
- lib/legion/extensions/claude/helpers/retry.rb
|
|
165
|
+
- lib/legion/extensions/claude/helpers/sse.rb
|
|
166
|
+
- lib/legion/extensions/claude/helpers/tools.rb
|
|
161
167
|
- lib/legion/extensions/claude/runners/batches.rb
|
|
162
168
|
- lib/legion/extensions/claude/runners/messages.rb
|
|
163
169
|
- lib/legion/extensions/claude/runners/models.rb
|