typesafe-sdk-ruby 0.6.0 → 0.6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -1
- data/LICENSE +1 -0
- data/README.md +241 -91
- data/docs/COMPATIBILITY.md +89 -0
- data/docs/RELEASING.md +38 -0
- data/docs/UPSTREAM_ISSUES.md +64 -0
- data/lib/typesafe/sdk/client.rb +45 -21
- data/lib/typesafe/sdk/errors.rb +16 -4
- data/lib/typesafe/sdk/http.rb +56 -8
- data/lib/typesafe/sdk/logging.rb +5 -1
- data/lib/typesafe/sdk/questions.rb +60 -34
- data/lib/typesafe/sdk/resources/models.rb +6 -4
- data/lib/typesafe/sdk/retry.rb +4 -4
- data/lib/typesafe/sdk/types.rb +11 -0
- data/lib/typesafe/sdk/version.rb +3 -2
- metadata +12 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a081c1995e40528b73408af6b6188d0c9784b497de463b2821694e6a1a895c81
|
|
4
|
+
data.tar.gz: 7c7363485285de50ef55a68dfe3f6606e3c0a08f0f41133fbb182010626144bc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 26a637c0d1fc8afb9f29e20ab2860d287d12491c6a93c7c28c2db574adce747abba8c60eae1d70cff7efd3610b08db1f9ea0fdf4d727c58aeaee08cc12c35811
|
|
7
|
+
data.tar.gz: 1e9bfee179126a48aa3f640b87149f9af99a8e47c03bb13d29c2d550a520522e98496ceeabd63aa9949fced46f242cc600133c34aab5685b74a23ecfd47a14a5
|
data/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,50 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
-
|
|
6
|
+
Versions follow the official JavaScript SDK, with a fourth numeric component for Ruby-only
|
|
7
|
+
corrections. See [RELEASING.md](docs/RELEASING.md).
|
|
8
|
+
|
|
9
|
+
## [0.6.0.1] - 2026-09-21
|
|
10
|
+
|
|
11
|
+
Compatibility target: official JavaScript SDK 0.6.0, commit `66880cc`.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Reject malformed/blank API keys at construction without including credentials in errors
|
|
16
|
+
(upstream #14); trim surrounding spaces and line endings.
|
|
17
|
+
- Validate state types, meaningful noul questions, score/choice bounds, and empty question
|
|
18
|
+
names before HTTP requests (upstream #6).
|
|
19
|
+
- Reject nil score levels without dropping or renumbering positions; document an explicit
|
|
20
|
+
empty string for an undescribed position (upstream #12).
|
|
21
|
+
- Fall back to configured backoff for blank retry headers while preserving explicit zero
|
|
22
|
+
and valid alternate headers (upstream #9).
|
|
23
|
+
|
|
24
|
+
- Match upstream fallback error messages when API error fields are empty strings.
|
|
25
|
+
- Treat nil retry flags as inherited settings, while keeping explicit false overrides.
|
|
26
|
+
- Enforce the timeout across the entire HTTP attempt, including slow response bodies.
|
|
27
|
+
- Interrupt active requests on cancellation and close/join the request worker before returning.
|
|
28
|
+
- Disable Net::HTTP's implicit retries; detect truncated response bodies as connection errors.
|
|
29
|
+
- Support Ruby/Rails loggers when debug or error details are present.
|
|
30
|
+
- Validate JSON-loaded score questions and reject infinite/NaN/complex timeout and backoff values.
|
|
31
|
+
- Preserve fractional server retry delays; capture HTTP-date rate-limit delays once.
|
|
32
|
+
- Freeze copied retry status overrides to prevent accidental policy mutation.
|
|
33
|
+
- Preserve upstream license attribution and remove the misleading official-company contact.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- `PaymentRequiredError` (402), `ConflictError` (409), and `PayloadTooLargeError` (413),
|
|
38
|
+
retaining `APIError` inheritance and existing retry defaults (upstream #13).
|
|
39
|
+
- An audit of all 11 upstream issues, numeric-label coverage, subprocess cancellation checks,
|
|
40
|
+
and real-socket timeout tests around Node's timer limit (upstream #2, #4, #8).
|
|
41
|
+
|
|
42
|
+
- Complete answer and nullable retry flag comparisons against the published JS package.
|
|
43
|
+
- README onboarding, primitive/confidence examples, JS-to-Ruby mapping, and support routes.
|
|
44
|
+
- `with_response: true` on inference and model listing for data, HTTP metadata, and request ID.
|
|
45
|
+
- `extra_body:` for additional inference fields, including explicit null values.
|
|
46
|
+
- Fixtures generated by executing the published JS SDK, real socket regression tests, and an
|
|
47
|
+
opt-in live API smoke test.
|
|
48
|
+
- A single gem version source, explicit `UPSTREAM_VERSION`, release validation, packaged-gem
|
|
49
|
+
CI checks, Ruby 4.0 coverage, and compatibility/release documentation.
|
|
7
50
|
|
|
8
51
|
## [0.6.0] - 2026-09-20
|
|
9
52
|
|
data/LICENSE
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2026 TypeSafe Ruby SDK Contributors
|
|
4
|
+
Copyright (c) 2026 TypeSafe (upstream JavaScript SDK)
|
|
4
5
|
|
|
5
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
7
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
|
@@ -4,37 +4,42 @@
|
|
|
4
4
|
[](https://github.com/afurm/typesafe-sdk-ruby/actions/workflows/ci.yml)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
score questions — with retries, timeouts, structured logging, and typed errors.
|
|
7
|
+
A community-maintained Ruby client for [TypeSafe AI](https://typesafe.ai), with typed answer
|
|
8
|
+
objects, retries, timeouts, cancellation, and configurable logging.
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
TypeSafe's Jev model evaluates state against questions defined by your application. It returns
|
|
11
|
+
structured decisions that your code can use for classification, ranking, and routing.
|
|
12
|
+
Read the [official introduction](https://docs.typesafe.ai/introduction) for the product model.
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
**Unofficial:** this project is not affiliated with, endorsed by, or supported by TypeSafe AI.
|
|
15
|
+
It targets the official [JavaScript SDK](https://github.com/typesafe-ai/typesafe-sdk-js)
|
|
16
|
+
**0.6.0**. Official [JavaScript and Python SDKs](https://docs.typesafe.ai/sdk) are maintained
|
|
17
|
+
by TypeSafe AI.
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
19
|
+
**Version:** this README documents Ruby **0.6.0.1**, targeting JS **0.6.0**.
|
|
20
|
+
Ruby-only corrections add a fourth version component. Check
|
|
21
|
+
[RubyGems](https://rubygems.org/gems/typesafe-sdk-ruby) and
|
|
22
|
+
[GitHub releases](https://github.com/afurm/typesafe-sdk-ruby/releases) for published versions;
|
|
23
|
+
merging changes into `main` does not publish a gem. See the [changelog](CHANGELOG.md)
|
|
24
|
+
for changes and migration notes.
|
|
22
25
|
|
|
23
26
|
## Installation
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
Requires Ruby **3.1 or newer**; CI tests Ruby 3.1, 3.2, 3.3, 3.4, and 4.0.
|
|
26
29
|
|
|
27
30
|
```sh
|
|
28
31
|
gem install typesafe-sdk-ruby
|
|
29
32
|
```
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
For Bundler, add the gem to your application's `Gemfile` and run `bundle install`:
|
|
32
35
|
|
|
33
36
|
```ruby
|
|
34
37
|
gem "typesafe-sdk-ruby"
|
|
35
38
|
```
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
Create an API key in the [TypeSafe console](https://console.typesafe.ai/), then set
|
|
41
|
+
`TYPESAFE_API_KEY` in your environment or application secret store. The SDK reads it
|
|
42
|
+
automatically. See the [official quick start](https://docs.typesafe.ai/introduction/quickstart).
|
|
38
43
|
|
|
39
44
|
## Quickstart
|
|
40
45
|
|
|
@@ -42,159 +47,304 @@ and run `bundle install`.
|
|
|
42
47
|
require "typesafe-sdk-ruby"
|
|
43
48
|
|
|
44
49
|
client = Typesafe::SDK::Client.new
|
|
45
|
-
|
|
46
50
|
response = client.system_one(
|
|
47
51
|
state: { document: "I was charged twice. Please fix this ASAP." },
|
|
48
52
|
questions: {
|
|
49
53
|
category: Typesafe::SDK.choice("What is this ticket about?", {
|
|
50
|
-
billing:
|
|
51
|
-
technical:
|
|
52
|
-
other:
|
|
54
|
+
billing: "Payments, invoices, or refunds",
|
|
55
|
+
technical: "A product error or technical problem",
|
|
56
|
+
other: "A different subject",
|
|
53
57
|
}),
|
|
54
58
|
},
|
|
55
59
|
)
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
category = response[:category]
|
|
62
|
+
puts category.choice
|
|
63
|
+
puts category.confidence
|
|
64
|
+
puts category.probabilities
|
|
65
|
+
puts response.model
|
|
66
|
+
puts response.usage.input_tokens
|
|
58
67
|
```
|
|
59
68
|
|
|
60
|
-
|
|
69
|
+
`response[:category]` and `response["category"]` work interchangeably.
|
|
70
|
+
`response.answers` is a regular Hash with string keys.
|
|
71
|
+
|
|
72
|
+
## Choosing a question type
|
|
61
73
|
|
|
62
|
-
|
|
|
74
|
+
| Builder | Use it for | Result |
|
|
63
75
|
| --- | --- | --- |
|
|
64
|
-
| `noul` |
|
|
65
|
-
| `choice` |
|
|
66
|
-
| `score` |
|
|
76
|
+
| `noul` | A yes/no judgment | `noul`: probability of yes, from 0 to 1 |
|
|
77
|
+
| `choice` | Selecting one named option | `choice`, `probabilities`, `confidence` |
|
|
78
|
+
| `score` | Rating against ordered descriptions | `score`, `legend`, `probabilities`, `confidence` |
|
|
79
|
+
|
|
80
|
+
Mix question types in a single call. Each question is evaluated against the same state,
|
|
81
|
+
independently of the other questions. Put the meaning in the instructions and criteria;
|
|
82
|
+
question IDs identify answers and are not used for inference.
|
|
83
|
+
See [primitives](https://docs.typesafe.ai/primitives) and the [API reference](https://docs.typesafe.ai/api).
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
response = client.system_one(
|
|
87
|
+
state: "The export fails in Safari, but I can finish the task in Firefox.",
|
|
88
|
+
questions: {
|
|
89
|
+
workaround: Typesafe::SDK.noul("Does the user describe a working alternative?"),
|
|
90
|
+
category: Typesafe::SDK.choice("Which area is affected?", {
|
|
91
|
+
export: "Exporting application data",
|
|
92
|
+
login: "Signing in to the application",
|
|
93
|
+
other: "Another area",
|
|
94
|
+
}),
|
|
95
|
+
severity: Typesafe::SDK.score("How much does this issue affect the user's task?", [
|
|
96
|
+
"The task works; only its appearance is affected",
|
|
97
|
+
"The task needs an alternative method to complete",
|
|
98
|
+
"The task cannot be completed",
|
|
99
|
+
]),
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
puts response[:workaround].noul
|
|
104
|
+
puts response[:severity].score
|
|
105
|
+
puts response[:severity].probabilities["1"]
|
|
106
|
+
puts response[:severity].legend["1"]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Score criteria are an **ordered array**, with positions starting at zero. A returned score
|
|
110
|
+
is a probability-weighted mean and can be fractional. `legend` and `probabilities` retain
|
|
111
|
+
string keys such as `"0"` and `"1"`. See the [Score guide](https://docs.typesafe.ai/primitives/score).
|
|
112
|
+
|
|
113
|
+
State, instructions, and criterion descriptions can also contain JSON objects or arrays.
|
|
114
|
+
Optional noul criteria can describe either outcome:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
question = Typesafe::SDK.noul("Is this an explicit cancellation request?", criteria: {
|
|
118
|
+
true: "The customer asks to end the subscription",
|
|
119
|
+
false: "The customer only asks about cancellation terms",
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
See [structured questions](https://docs.typesafe.ai/primitives/advanced) for more examples.
|
|
124
|
+
|
|
125
|
+
From Ruby **0.6.0.1**, builders and raw question hashes are checked before sending:
|
|
126
|
+
|
|
127
|
+
- State must be a string, object, or array; `nil` is rejected, while `""`, `{}`, and `[]` are allowed.
|
|
128
|
+
- Noul needs instructions or at least one non-nil true/false outcome description.
|
|
129
|
+
- Score needs **2–10** levels. A nil level is rejected; use `""` explicitly to keep an
|
|
130
|
+
undescribed position. The SDK never drops or renumbers levels.
|
|
131
|
+
- Choice needs **1–255** options. Nil descriptions remain valid for choice labels.
|
|
132
|
+
- Question names must not be empty strings.
|
|
133
|
+
|
|
134
|
+
Failures raise `Typesafe::SDK::TypeSafeError` without an HTTP request. These checks address
|
|
135
|
+
request shapes reported as rejected by the API, even though JS 0.6.0 accepts them locally.
|
|
136
|
+
See the [upstream issue audit](docs/UPSTREAM_ISSUES.md) for evidence and limitations.
|
|
137
|
+
|
|
138
|
+
## Using confidence
|
|
139
|
+
|
|
140
|
+
Choice and Score include confidence derived from their probability distributions. Confidence
|
|
141
|
+
is distinct from the probability of the selected option and is not a guarantee of correctness.
|
|
142
|
+
Noul returns the probability of yes and has no separate confidence field.
|
|
143
|
+
See the [official confidence guide](https://docs.typesafe.ai/confidence).
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
category = response[:category]
|
|
147
|
+
threshold = 0.8 # Illustrative: evaluate a suitable threshold on your own labeled examples.
|
|
148
|
+
puts(category.confidence >= threshold ? "Route to #{category.choice}" : "Needs review")
|
|
149
|
+
```
|
|
67
150
|
|
|
68
151
|
## Configuration
|
|
69
152
|
|
|
70
153
|
Explicit options take precedence over environment variables, then SDK defaults.
|
|
154
|
+
Blank environment values are ignored for optional settings. An API key is required.
|
|
155
|
+
From Ruby 0.6.0.1, outer spaces, tabs, and line endings are trimmed from keys; blank keys,
|
|
156
|
+
non-ASCII text, embedded whitespace, and control characters raise `TypeSafeError` at
|
|
157
|
+
construction. Error messages do not include the key.
|
|
71
158
|
|
|
72
159
|
| Option | Environment variable | Default |
|
|
73
160
|
| --- | --- | --- |
|
|
74
|
-
| `api_key:` | `TYPESAFE_API_KEY` |
|
|
161
|
+
| `api_key:` | `TYPESAFE_API_KEY` | Required |
|
|
75
162
|
| `base_url:` | `TYPESAFE_BASE_URL` | `https://api.typesafe.ai` |
|
|
76
163
|
| `default_model:` | `TYPESAFE_DEFAULT_MODEL` | `jev-latest` |
|
|
77
164
|
| `log_level:` | `TYPESAFE_LOG_LEVEL` | `warn` |
|
|
78
165
|
|
|
79
166
|
```ruby
|
|
80
167
|
client = Typesafe::SDK::Client.new(
|
|
81
|
-
|
|
82
|
-
base_url: "https://api.typesafe.ai",
|
|
83
|
-
default_model: "jev-latest",
|
|
84
|
-
log_level: :info, # :debug, :info, :warn, :error, :off
|
|
85
|
-
timeout: 10, # seconds per attempt
|
|
168
|
+
timeout: 10, # Seconds per attempt, including connection setup and response body receipt.
|
|
86
169
|
retry_policy: { max_retries: 2, backoff_initial_ms: 500 },
|
|
87
|
-
|
|
170
|
+
log_level: :info, # :debug, :info, :warn, :error, or :off
|
|
171
|
+
default_headers: { "X-My-App" => "support" },
|
|
88
172
|
)
|
|
89
173
|
```
|
|
90
174
|
|
|
175
|
+
`jev-latest` is a moving alias. For reproducible deployments, select an explicit model from
|
|
176
|
+
`client.models.list` and set `default_model:` or a per-call `model:`. See
|
|
177
|
+
[available models and aliases](https://docs.typesafe.ai/models).
|
|
178
|
+
|
|
91
179
|
## Retries and timeouts
|
|
92
180
|
|
|
93
|
-
|
|
94
|
-
with
|
|
95
|
-
|
|
181
|
+
By default, the SDK retries HTTP 408, 429, and 5xx responses, connection failures, and timeouts,
|
|
182
|
+
with up to two retries after the initial attempt. Backoff starts at 500 ms, doubles up to
|
|
183
|
+
5,000 ms, and uses up to 25% downward jitter. Server `Retry-After` and `retry-after-ms` delays
|
|
184
|
+
are honored up to 60,000 ms; larger delays fall back to backoff. From Ruby 0.6.0.1, blank
|
|
185
|
+
or malformed delay headers also fall back to backoff; an explicit zero remains valid.
|
|
186
|
+
A blank `retry-after-ms` still permits a valid `Retry-After` header to be used.
|
|
187
|
+
|
|
188
|
+
Each attempt receives a fresh timeout, so total call time can include several attempts and
|
|
189
|
+
backoff. Override settings per client or per call:
|
|
96
190
|
|
|
97
191
|
```ruby
|
|
98
192
|
client.system_one(
|
|
99
|
-
state: "
|
|
100
|
-
questions: {
|
|
193
|
+
state: "A refund request",
|
|
194
|
+
questions: { billing: Typesafe::SDK.noul("Is this about billing?") },
|
|
101
195
|
timeout: 30,
|
|
102
|
-
retry_policy: { max_retries: 0 },
|
|
196
|
+
retry_policy: { max_retries: 0 },
|
|
103
197
|
)
|
|
104
198
|
```
|
|
105
199
|
|
|
200
|
+
The full retry policy supports `max_retries`, `backoff_initial_ms`, `backoff_max_ms`,
|
|
201
|
+
`backoff_jitter`, `http_statuses`, `respect_retry_after`, `max_retry_after_ms`,
|
|
202
|
+
`api_connection_error`, and `api_timeout_error`. Overrides merge field by field.
|
|
203
|
+
Nil boolean flags inherit the existing setting; explicit `false` disables that behavior.
|
|
204
|
+
|
|
106
205
|
## Error handling
|
|
107
206
|
|
|
108
207
|
```ruby
|
|
109
208
|
begin
|
|
110
|
-
client.system_one(
|
|
209
|
+
client.system_one(
|
|
210
|
+
state: "A refund request",
|
|
211
|
+
questions: { billing: Typesafe::SDK.noul("Is this about billing?") },
|
|
212
|
+
)
|
|
111
213
|
rescue Typesafe::SDK::RateLimitError => e
|
|
112
|
-
|
|
214
|
+
warn "Rate limited; suggested retry delay: #{e.retry_after_ms.inspect}ms"
|
|
113
215
|
rescue Typesafe::SDK::APIError => e
|
|
114
|
-
warn "API error #{e.status}
|
|
216
|
+
warn "API error #{e.status}; request ID: #{e.request_id}"
|
|
217
|
+
rescue Typesafe::SDK::APIConnectionError => e
|
|
218
|
+
warn e.message
|
|
115
219
|
end
|
|
116
220
|
```
|
|
117
221
|
|
|
118
|
-
|
|
222
|
+
Errors reach your code after any configured retries. All SDK errors inherit from
|
|
223
|
+
`Typesafe::SDK::TypeSafeError`:
|
|
224
|
+
|
|
225
|
+
| Error | Meaning |
|
|
226
|
+
| --- | --- |
|
|
227
|
+
| `BadRequestError` | HTTP 400 |
|
|
228
|
+
| `AuthenticationError` | HTTP 401 |
|
|
229
|
+
| `PaymentRequiredError` | HTTP 402; added in 0.6.0.1 |
|
|
230
|
+
| `PermissionDeniedError` | HTTP 403 |
|
|
231
|
+
| `NotFoundError` | HTTP 404 |
|
|
232
|
+
| `ConflictError` | HTTP 409; added in 0.6.0.1 |
|
|
233
|
+
| `PayloadTooLargeError` | HTTP 413; added in 0.6.0.1 |
|
|
234
|
+
| `UnprocessableEntityError` | HTTP 422 |
|
|
235
|
+
| `RateLimitError` | HTTP 429; exposes `retry_after_ms` |
|
|
236
|
+
| `InternalServerError` | HTTP 5xx |
|
|
237
|
+
| `APIError` | Other non-2xx responses; exposes status, headers, body, and request ID |
|
|
238
|
+
| `APIConnectionError` | Connection or response-body delivery failure |
|
|
239
|
+
| `APITimeoutError` | Subclass of `APIConnectionError`; exposes `timeout_ms` |
|
|
240
|
+
| `APIUserAbortError` | Caller cancellation; never retried |
|
|
241
|
+
|
|
242
|
+
## Response metadata and additional fields
|
|
243
|
+
|
|
244
|
+
Added for Ruby **0.6.0.1**:
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
result = client.system_one(
|
|
248
|
+
state: "A refund request",
|
|
249
|
+
questions: { billing: Typesafe::SDK.noul("Is this about billing?") },
|
|
250
|
+
with_response: true,
|
|
251
|
+
)
|
|
252
|
+
puts result.data[:billing].noul
|
|
253
|
+
puts result.request_id
|
|
254
|
+
puts result.response.status
|
|
255
|
+
```
|
|
119
256
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
`InternalServerError` (5xx)
|
|
125
|
-
- `Typesafe::SDK::APIConnectionError` — DNS, TLS, connection failures
|
|
126
|
-
- `Typesafe::SDK::APITimeoutError`
|
|
127
|
-
- `Typesafe::SDK::APIUserAbortError` — caller cancellation
|
|
257
|
+
`client.models.list(with_response: true)` wraps the model cards the same way. The response
|
|
258
|
+
exposes case-insensitive `headers` and a parsed `body`.
|
|
259
|
+
`extra_body: { new_option: nil }` forwards additional JSON fields; named `state`, `questions`,
|
|
260
|
+
and `model` arguments take precedence. The API decides whether a field is supported.
|
|
128
261
|
|
|
129
262
|
## Cancellation
|
|
130
263
|
|
|
131
264
|
```ruby
|
|
132
265
|
signal = Typesafe::SDK::Signal.new
|
|
133
|
-
Thread.new { sleep 5; signal.cancel }
|
|
134
|
-
|
|
135
|
-
client.system_one(
|
|
136
|
-
|
|
266
|
+
canceller = Thread.new { sleep 5; signal.cancel }
|
|
267
|
+
begin
|
|
268
|
+
client.system_one(
|
|
269
|
+
state: "A refund request",
|
|
270
|
+
questions: { billing: Typesafe::SDK.noul("Is this about billing?") },
|
|
271
|
+
signal: signal,
|
|
272
|
+
)
|
|
273
|
+
rescue Typesafe::SDK::APIUserAbortError
|
|
274
|
+
warn "Request canceled"
|
|
275
|
+
ensure
|
|
276
|
+
canceller.kill.join
|
|
277
|
+
end
|
|
137
278
|
```
|
|
138
279
|
|
|
280
|
+
From Ruby 0.6.0.1, cancellation interrupts active requests and retry backoff, and cleans up
|
|
281
|
+
the request worker and socket before returning.
|
|
282
|
+
|
|
139
283
|
## Listing models
|
|
140
284
|
|
|
141
285
|
```ruby
|
|
142
286
|
client.models.list.each do |model|
|
|
143
|
-
puts "#{model.name}: #{model.description}"
|
|
287
|
+
puts "#{model.name}: #{model.description} (#{model.release_date})"
|
|
144
288
|
end
|
|
145
289
|
```
|
|
146
290
|
|
|
147
|
-
## Logging
|
|
291
|
+
## Logging and Rails
|
|
148
292
|
|
|
149
|
-
The default logger writes to `$stderr` with a `[typesafe-
|
|
150
|
-
summaries; `debug` adds
|
|
151
|
-
|
|
293
|
+
The default logger writes to `$stderr` with a `[typesafe-ai]` prefix. `info` includes request
|
|
294
|
+
summaries; `debug` adds redacted credential headers and request/response bodies. Bodies may
|
|
295
|
+
contain application data and are **not** redacted.
|
|
296
|
+
|
|
297
|
+
From Ruby 0.6.0.1, standard Ruby `Logger` and `Rails.logger` also accept debug details:
|
|
152
298
|
|
|
153
299
|
```ruby
|
|
154
|
-
|
|
300
|
+
# config/initializers/typesafe.rb; configure TYPESAFE_API_KEY through your secret store.
|
|
301
|
+
TYPESAFE = Typesafe::SDK::Client.new(logger: Rails.logger, log_level: :info)
|
|
155
302
|
```
|
|
156
303
|
|
|
157
|
-
|
|
304
|
+
Call `TYPESAFE.system_one(...)` from application code. In plain Ruby, use
|
|
305
|
+
`require "logger"` and pass `logger: Logger.new($stderr)`; add the `logger` gem to your
|
|
306
|
+
application if your Ruby version does not provide it by default.
|
|
158
307
|
|
|
159
|
-
|
|
160
|
-
wrapped initializer:
|
|
308
|
+
## Coming from JavaScript
|
|
161
309
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
310
|
+
| JavaScript | Ruby |
|
|
311
|
+
| --- | --- |
|
|
312
|
+
| `new TypeSafeClient()` | `Typesafe::SDK::Client.new` |
|
|
313
|
+
| `systemOne(request, options)` | `system_one(state:, questions:, **options)` |
|
|
314
|
+
| `apiKey`, `baseURL`, `defaultModel` | `api_key:`, `base_url:`, `default_model:` |
|
|
315
|
+
| `timeout` in milliseconds | `timeout:` in seconds |
|
|
316
|
+
| `retry: { maxRetries: 0 }` | `retry_policy: { max_retries: 0 }` |
|
|
317
|
+
| `.withResponse()` | `with_response: true` (Ruby 0.6.0.1+) |
|
|
318
|
+
| `AbortController` | `Typesafe::SDK::Signal` |
|
|
319
|
+
| `answers.category` | `response[:category]` or `response.answers["category"]` |
|
|
166
320
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def classify(text)
|
|
171
|
-
TYPESAFE.system_one(
|
|
172
|
-
state: text,
|
|
173
|
-
questions: {
|
|
174
|
-
category: Typesafe::SDK.choice("Category?", {
|
|
175
|
-
billing: nil, technical: nil, other: nil,
|
|
176
|
-
}),
|
|
177
|
-
},
|
|
178
|
-
).answers["category"].choice
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
```
|
|
321
|
+
Ruby calls are synchronous and return Ruby answer objects. They do not provide TypeScript
|
|
322
|
+
compile-time inference, Fetch streams, connection pooling, or automatic redirect following.
|
|
323
|
+
See [tested compatibility and intentional differences](docs/COMPATIBILITY.md).
|
|
182
324
|
|
|
183
|
-
## Development
|
|
325
|
+
## Development and contributing
|
|
184
326
|
|
|
185
327
|
```sh
|
|
186
328
|
bundle install
|
|
187
|
-
bundle exec rake
|
|
188
|
-
bundle exec rspec # specs only
|
|
189
|
-
bundle exec rubocop # lint only
|
|
329
|
+
bundle exec rake # Tests and RuboCop; no API key or external API calls required.
|
|
190
330
|
```
|
|
191
331
|
|
|
192
|
-
|
|
332
|
+
Native transport tests bind loopback sockets. CI also builds and installs the packaged gem.
|
|
333
|
+
For an explicit live check, follow [live verification](docs/COMPATIBILITY.md#live-verification).
|
|
334
|
+
The [demo](examples/demo.rb) uses real API calls and can be run with
|
|
335
|
+
`bundle exec ruby examples/demo.rb` after configuring your key.
|
|
336
|
+
|
|
337
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md), the [PR template](.github/PULL_REQUEST_TEMPLATE.md),
|
|
338
|
+
and the [release process](docs/RELEASING.md). Changes are reviewed and merged by the repository owner.
|
|
339
|
+
|
|
340
|
+
## Support and security
|
|
193
341
|
|
|
194
|
-
|
|
195
|
-
[
|
|
196
|
-
[
|
|
342
|
+
Use [issues](https://github.com/afurm/typesafe-sdk-ruby/issues) for Ruby client bugs and
|
|
343
|
+
[discussions](https://github.com/afurm/typesafe-sdk-ruby/discussions) for usage questions.
|
|
344
|
+
See [SUPPORT.md](SUPPORT.md) for API/account questions and useful report details.
|
|
345
|
+
Report vulnerabilities privately using [SECURITY.md](SECURITY.md).
|
|
346
|
+
All participation follows our [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
197
347
|
|
|
198
348
|
## License
|
|
199
349
|
|
|
200
|
-
|
|
350
|
+
[MIT](LICENSE), including attribution to the upstream TypeSafe JavaScript SDK.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# JavaScript SDK compatibility
|
|
2
|
+
|
|
3
|
+
This unofficial Ruby client targets the official `@typesafe-ai/sdk` **0.6.0** release,
|
|
4
|
+
commit [`66880ccded6cb642dc1809620c2b108c33730214`](https://github.com/typesafe-ai/typesafe-sdk-js/tree/66880ccded6cb642dc1809620c2b108c33730214).
|
|
5
|
+
Audit date: 2026-09-21. This is a tested compatibility target, not a claim of endorsement
|
|
6
|
+
or a guarantee about future API changes.
|
|
7
|
+
|
|
8
|
+
## What is covered
|
|
9
|
+
|
|
10
|
+
| Surface | Ruby behavior |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| Endpoints | `POST /v1/systemone`, `GET /v1/models` |
|
|
13
|
+
| Defaults | `jev-latest`, 10-second attempt timeout, two retries |
|
|
14
|
+
| Questions | Noul, choice, ordered score criteria; structured instructions/state and nullable choice descriptions |
|
|
15
|
+
| Configuration | Explicit settings override trimmed environment values |
|
|
16
|
+
| Retries | 408, 429, 500–599; connection and timeout failures independently configurable |
|
|
17
|
+
| Server delays | Fractional milliseconds/seconds, HTTP dates, maximum delay and jitter |
|
|
18
|
+
| Headers | Case-insensitive overrides; protected auth, content type, SDK and retry headers |
|
|
19
|
+
| Errors | HTTP subclasses, validation messages, request IDs, connection/timeout/cancellation errors |
|
|
20
|
+
| Transport | Timeout includes full body delivery; cancellation interrupts an active attempt and backoff; truncated bodies are retried |
|
|
21
|
+
| Results | Typed answers and usage; model cards; `with_response: true` for HTTP metadata |
|
|
22
|
+
| Extension fields | `extra_body:` forwards additional JSON fields without overriding named arguments |
|
|
23
|
+
|
|
24
|
+
The checked-in `spec/fixtures/js-0.6.0.json` was generated by executing the published
|
|
25
|
+
JavaScript package, rather than by copying expected output from the Ruby implementation.
|
|
26
|
+
`parity_spec.rb` compares question JSON, request payloads, metadata, error mapping/messages,
|
|
27
|
+
complete answer fields (including fractional scores and string rubric keys), retry header
|
|
28
|
+
parsing, and nullable retry flags. `http_spec.rb` uses real loopback sockets to exercise behavior
|
|
29
|
+
that mocked HTTP adapters cannot verify. Other specs cover Ruby configuration and logging.
|
|
30
|
+
Known upstream bugs are corrected explicitly in the comparisons; the fixtures retain the
|
|
31
|
+
original JS behavior for those cases.
|
|
32
|
+
See the [issue-by-issue audit](UPSTREAM_ISSUES.md) for every upstream issue reviewed.
|
|
33
|
+
|
|
34
|
+
To regenerate the fixtures, download the official release asset from
|
|
35
|
+
[GitHub](https://github.com/typesafe-ai/typesafe-sdk-js/releases/tag/v0.6.0), unpack it, and run:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
node script/generate_parity_fixtures.cjs /path/to/package > spec/fixtures/js-0.6.0.json
|
|
39
|
+
bundle exec rake
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Intentional Ruby differences and limits
|
|
43
|
+
|
|
44
|
+
- Ruby 0.6.0.1 validates API keys without echoing their contents and rejects reported
|
|
45
|
+
API-invalid state/question shapes locally. Score levels must be non-nil and number 2–10;
|
|
46
|
+
choice options must number 1–255; noul requires instructions or an outcome description;
|
|
47
|
+
state must be a string, object, or array; question names cannot be empty.
|
|
48
|
+
Builders and raw question hashes use the same checks. This intentionally tightens the
|
|
49
|
+
permissive JS 0.6.0 behavior; local validation is not a complete server schema validator.
|
|
50
|
+
- Blank retry headers fall back to configured backoff (or another valid delay header).
|
|
51
|
+
JS 0.6.0 incorrectly treats blanks as zero. Literal zero remains valid in Ruby.
|
|
52
|
+
- HTTP 402, 409, and 413 have `PaymentRequiredError`, `ConflictError`, and
|
|
53
|
+
`PayloadTooLargeError` subclasses. Existing `APIError` rescue clauses still catch them.
|
|
54
|
+
- Methods/options use snake_case. `timeout:` is in **seconds**; retry settings ending in
|
|
55
|
+
`_ms` remain milliseconds. `retry_policy:` corresponds to JavaScript's `retry` option.
|
|
56
|
+
- Calls are synchronous. There is no JavaScript `Promise`, `AbortController`, or compile-time
|
|
57
|
+
inference. Ruby returns answer classes and accepts `Typesafe::SDK::Signal` for cancellation.
|
|
58
|
+
- `with_response: true` returns `.data`, `.response`, and `.request_id`. The response exposes
|
|
59
|
+
`.status`, `.headers`, and an already parsed `.body`; it is not a Fetch response or stream.
|
|
60
|
+
Non-2xx responses still raise typed errors. Unlike JavaScript's `asResponse()`, these
|
|
61
|
+
resource methods always construct typed results; there is no unparsed Fetch body API.
|
|
62
|
+
- `result[:question]` and `result["question"]` are both supported. `result.answers` itself
|
|
63
|
+
is a Hash with string keys, as received in JSON.
|
|
64
|
+
- The noul builder serializes omitted criteria as JSON null; upstream omits the property.
|
|
65
|
+
Both are accepted by the upstream question type. Explicit criteria serialize identically.
|
|
66
|
+
- The built-in transport opens one connection and one private worker per attempt. It closes
|
|
67
|
+
and joins them on completion, timeout, or cancellation. It does not pool connections or
|
|
68
|
+
implement a fiber-native asynchronous interface. Custom adapters own timeout/cancellation
|
|
69
|
+
enforcement and must implement the documented `HTTP#request` contract.
|
|
70
|
+
- Net::HTTP does not follow redirects automatically; a 3xx raises `APIError`. Use the canonical
|
|
71
|
+
API URL. JavaScript Fetch may follow redirects.
|
|
72
|
+
- Ruby's HTTP-date parser and JavaScript's Date parser may differ for malformed, non-HTTP dates.
|
|
73
|
+
Standard HTTP dates and the tested numeric forms are covered.
|
|
74
|
+
|
|
75
|
+
## Live verification
|
|
76
|
+
|
|
77
|
+
Offline tests do not prove account authentication, current model availability, billing, or
|
|
78
|
+
production inference behavior. No live API key was available during this audit, so no live
|
|
79
|
+
TypeSafe request was made.
|
|
80
|
+
|
|
81
|
+
An explicit smoke test checks model listing and all three answer types with synthetic input:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
# Set TYPESAFE_API_KEY securely in your environment first.
|
|
85
|
+
TYPESAFE_LIVE_TEST=1 bundle exec ruby script/smoke_test.rb
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This makes one inference request, which may incur normal API charges. It does not retry or
|
|
89
|
+
print the API key, input bodies, or full responses.
|
data/docs/RELEASING.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
Only the repository owner merges pull requests and initiates releases. Opening or merging
|
|
4
|
+
a pull request does not publish a gem. Publication starts when the owner pushes a version tag.
|
|
5
|
+
|
|
6
|
+
## Versions
|
|
7
|
+
|
|
8
|
+
`Typesafe::SDK::UPSTREAM_VERSION` records the official JS compatibility target.
|
|
9
|
+
`Typesafe::SDK::VERSION` is the Ruby package version and is the single source for the gemspec.
|
|
10
|
+
|
|
11
|
+
- A new upstream port uses its exact three-part version, for example `0.7.0`.
|
|
12
|
+
- Ruby-only corrections use a fourth numeric component, for example `0.6.0.1` and `0.6.0.2`.
|
|
13
|
+
These still target JS `0.6.0`; they do not imply an upstream `0.6.1` release.
|
|
14
|
+
- Published versions and tags must not be replaced or reused. RubyGems accepts numeric
|
|
15
|
+
fourth-component versions as stable releases.
|
|
16
|
+
|
|
17
|
+
Before a new upstream port, compare the tagged source and tests, update the compatibility
|
|
18
|
+
document and generated fixtures, and run the suite. Version equality alone is not evidence
|
|
19
|
+
of compatibility.
|
|
20
|
+
|
|
21
|
+
## Owner checklist
|
|
22
|
+
|
|
23
|
+
1. Review and merge the release pull request after all CI matrix jobs pass.
|
|
24
|
+
2. Run the opt-in live smoke test described in [COMPATIBILITY.md](COMPATIBILITY.md).
|
|
25
|
+
3. Set the changelog release date and update the README release-status notice in a reviewed
|
|
26
|
+
pull request before tagging.
|
|
27
|
+
4. On the merged main commit, run `bundle exec rake`, `ruby script/check_release.rb`, and
|
|
28
|
+
`gem build typesafe-sdk-ruby.gemspec --strict`.
|
|
29
|
+
5. Create and push the matching annotated tag, for example `v0.6.0.1`.
|
|
30
|
+
6. Verify the Release workflow, RubyGems version, GitHub release, and attached gem artifact.
|
|
31
|
+
|
|
32
|
+
The workflow rejects non-owner runs, tags that differ from the gem version, commits outside
|
|
33
|
+
main's history, and upstream versions that do not match the official tagged package.
|
|
34
|
+
It reruns tests and lint before publishing with the repository's `RUBYGEMS_API_KEY` secret.
|
|
35
|
+
Keep that credential restricted to this gem. Required branch checks remain in force.
|
|
36
|
+
|
|
37
|
+
If RubyGems publication succeeds but GitHub release creation fails, verify the published
|
|
38
|
+
artifact before recovering the GitHub release; do not delete the tag or republish the version.
|