typesafe-sdk-ruby 0.6.0.1 → 0.7.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 +37 -0
- data/README.md +128 -7
- data/docs/COMPATIBILITY.md +48 -18
- data/docs/UPSTREAM_ISSUES.md +18 -8
- data/lib/typesafe/sdk/assets.rb +326 -0
- data/lib/typesafe/sdk/client.rb +57 -13
- data/lib/typesafe/sdk/compression.rb +50 -0
- data/lib/typesafe/sdk/errors.rb +19 -2
- data/lib/typesafe/sdk/http.rb +2 -1
- data/lib/typesafe/sdk/logging.rb +14 -6
- data/lib/typesafe/sdk/questions.rb +17 -4
- data/lib/typesafe/sdk/types.rb +58 -11
- data/lib/typesafe/sdk/version.rb +2 -2
- data/lib/typesafe-sdk-ruby.rb +30 -0
- metadata +7 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d9c873fb65592342cbe6b344decd33db540327f8dac873d1ad2c42e1edea58f1
|
|
4
|
+
data.tar.gz: 6d1a977041d8d12fb31b0654e7ffae904170d2d3432951a67751dc30b9aa6d20
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e51268bb25700c0c833013b3a6ba850379d86522a72d7438efe2c4b29fd9c868599486408459349f4a3281a6bb7768f20189fc16bc0c6220872f22ec3dd940c
|
|
7
|
+
data.tar.gz: e4971f23dde4a6c7672c4c922ef3fe72460c520919b868e4ec08730ba4f32136659dca3c48e1e18728a8c3e920c4c6405cdf6acc9e0fa5245003983f7d6477b9
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,43 @@ 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
7
|
corrections. See [RELEASING.md](docs/RELEASING.md).
|
|
8
8
|
|
|
9
|
+
## [0.7.0] - 2026-10-16
|
|
10
|
+
|
|
11
|
+
Compatibility target: official JavaScript SDK 0.7.0, commit `b844cd1`.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Images, an experimental API feature enabled with the `TypeSafe-Danger-Zone-Images: true`
|
|
16
|
+
header: create them with `Typesafe::SDK.image` from file bytes, a `Pathname`, or an IO, or
|
|
17
|
+
with `Typesafe::SDK::Image.from_base64`. Place them in `state`, question instructions, or
|
|
18
|
+
criteria; join them into text with `Typesafe::SDK.with_assets` and `Typesafe::SDK.join_assets`;
|
|
19
|
+
or reference them by names you choose in `assets:`. Requests with images are sent as
|
|
20
|
+
`multipart/form-data`, with each image's bytes in its own part.
|
|
21
|
+
- `box` questions and `BoxResponse` answers, which locate what their instructions describe in an
|
|
22
|
+
image; use them with the `jev-preview` model.
|
|
23
|
+
- zstd compression for request bodies of 1 KiB or more when the optional `zstd-ruby` gem is
|
|
24
|
+
available; pass `compression: false` to send bodies uncompressed.
|
|
25
|
+
- `#to_h` on every answer, returning all fields the API sent, including unmodeled ones.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Non-finite floats (`NaN`, `Infinity`, `-Infinity`) in a request are sent as strings, as
|
|
30
|
+
JS 0.7.0 does, instead of raising `JSON::GeneratorError` (upstream #17).
|
|
31
|
+
- Noul questions whose `criteria` is not a Hash raise `TypeSafeError` before sending.
|
|
32
|
+
- Usage token counts are documented as possibly `nil` when the API doesn't report them.
|
|
33
|
+
- A request containing a circular reference raises `TypeSafeError` instead of a JSON error.
|
|
34
|
+
- Custom `http:` adapters can receive binary bodies: `multipart/form-data` for requests with
|
|
35
|
+
images, and zstd-compressed bodies of 1 KiB or more when `zstd-ruby` is loaded. Send the body
|
|
36
|
+
unchanged with the given headers, or pass `compression: false` to keep JSON bodies uncompressed.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- Debug logs no longer show an `x-api-key` value or a spaceless credential as an
|
|
41
|
+
authorization scheme; only an alphabetic scheme followed by a credential is kept
|
|
42
|
+
(upstream #18).
|
|
43
|
+
- HTML error pages from Cloudflare raise errors whose message names the edge block and its
|
|
44
|
+
`cf-ray` ID instead of quoting the HTML (upstream #15). The error classes are unchanged.
|
|
45
|
+
|
|
9
46
|
## [0.6.0.1] - 2026-09-21
|
|
10
47
|
|
|
11
48
|
Compatibility target: official JavaScript SDK 0.6.0, commit `66880cc`.
|
data/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
7
|
A community-maintained Ruby client for [TypeSafe AI](https://typesafe.ai), with typed answer
|
|
8
|
-
objects, retries, timeouts, cancellation, and configurable logging.
|
|
8
|
+
objects, image assets, retries, timeouts, cancellation, request compression, and configurable logging.
|
|
9
9
|
|
|
10
10
|
TypeSafe's Jev model evaluates state against questions defined by your application. It returns
|
|
11
11
|
structured decisions that your code can use for classification, ranking, and routing.
|
|
@@ -13,10 +13,10 @@ Read the [official introduction](https://docs.typesafe.ai/introduction) for the
|
|
|
13
13
|
|
|
14
14
|
**Unofficial:** this project is not affiliated with, endorsed by, or supported by TypeSafe AI.
|
|
15
15
|
It targets the official [JavaScript SDK](https://github.com/typesafe-ai/typesafe-sdk-js)
|
|
16
|
-
**0.
|
|
16
|
+
**0.7.0**. Official [JavaScript and Python SDKs](https://docs.typesafe.ai/sdk) are maintained
|
|
17
17
|
by TypeSafe AI.
|
|
18
18
|
|
|
19
|
-
**Version:** this README documents Ruby **0.
|
|
19
|
+
**Version:** this README documents Ruby **0.7.0**, targeting JS **0.7.0**.
|
|
20
20
|
Ruby-only corrections add a fourth version component. Check
|
|
21
21
|
[RubyGems](https://rubygems.org/gems/typesafe-sdk-ruby) and
|
|
22
22
|
[GitHub releases](https://github.com/afurm/typesafe-sdk-ruby/releases) for published versions;
|
|
@@ -76,6 +76,7 @@ puts response.usage.input_tokens
|
|
|
76
76
|
| `noul` | A yes/no judgment | `noul`: probability of yes, from 0 to 1 |
|
|
77
77
|
| `choice` | Selecting one named option | `choice`, `probabilities`, `confidence` |
|
|
78
78
|
| `score` | Rating against ordered descriptions | `score`, `legend`, `probabilities`, `confidence` |
|
|
79
|
+
| `box` | Locating something in an image ([Images](#images)) | `box` (`top`, `bottom`, `left`, `right`), `confidence` |
|
|
79
80
|
|
|
80
81
|
Mix question types in a single call. Each question is evaluated against the same state,
|
|
81
82
|
independently of the other questions. Put the meaning in the instructions and criteria;
|
|
@@ -124,17 +125,113 @@ See [structured questions](https://docs.typesafe.ai/primitives/advanced) for mor
|
|
|
124
125
|
|
|
125
126
|
From Ruby **0.6.0.1**, builders and raw question hashes are checked before sending:
|
|
126
127
|
|
|
127
|
-
- State must be a string, object, or
|
|
128
|
-
- Noul needs instructions or at least one non-nil true/false outcome description
|
|
128
|
+
- State must be a string, object, array, or image; `nil` is rejected, while `""`, `{}`, and `[]` are allowed.
|
|
129
|
+
- Noul needs instructions or at least one non-nil true/false outcome description; its criteria,
|
|
130
|
+
when given, must be a Hash (from 0.7.0).
|
|
129
131
|
- Score needs **2–10** levels. A nil level is rejected; use `""` explicitly to keep an
|
|
130
132
|
undescribed position. The SDK never drops or renumbers levels.
|
|
131
133
|
- Choice needs **1–255** options. Nil descriptions remain valid for choice labels.
|
|
132
134
|
- Question names must not be empty strings.
|
|
133
135
|
|
|
134
136
|
Failures raise `Typesafe::SDK::TypeSafeError` without an HTTP request. These checks address
|
|
135
|
-
request shapes reported as rejected by the API
|
|
137
|
+
request shapes reported as rejected by the API. JS 0.7.0 now rejects nil state and empty noul
|
|
138
|
+
questions too; the score and choice bounds, nil score levels, and empty names remain Ruby-only checks.
|
|
136
139
|
See the [upstream issue audit](docs/UPSTREAM_ISSUES.md) for evidence and limitations.
|
|
137
140
|
|
|
141
|
+
From Ruby **0.7.0**, as in JS 0.7.0, non-finite floats such as `Float::NAN` are sent as the
|
|
142
|
+
strings `"NaN"`, `"Infinity"`, and `"-Infinity"`, because JSON cannot represent them. Every answer
|
|
143
|
+
also responds to `to_h`, which returns all of its fields, including ones this SDK doesn't model.
|
|
144
|
+
Usage token counts are `nil` when the API doesn't report them.
|
|
145
|
+
|
|
146
|
+
## Images
|
|
147
|
+
|
|
148
|
+
Added in Ruby **0.7.0**. Create an image with `Typesafe::SDK.image` from file bytes (a binary
|
|
149
|
+
`String`), a `Pathname` (read when the request is sent), or an IO (read immediately), or with
|
|
150
|
+
`Typesafe::SDK::Image.from_base64` from base64 text. Images must be PNG or JPEG. The API detects
|
|
151
|
+
the format from the file bytes and rejects other formats, such as WebP and GIF, with a
|
|
152
|
+
`BadRequestError`. You can use an image in a request in three ways, and you can combine them.
|
|
153
|
+
|
|
154
|
+
Image support is an experimental API feature. Opt in by sending the
|
|
155
|
+
`TypeSafe-Danger-Zone-Images: true` header for a client, or per call with `headers:`:
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
client = Typesafe::SDK::Client.new(default_headers: { "TypeSafe-Danger-Zone-Images" => "true" })
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Place the image in the content.** Put it anywhere in `state`, question instructions, or criteria:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
require "pathname"
|
|
165
|
+
|
|
166
|
+
photo = Typesafe::SDK.image(Pathname("returns/RMA-1042.jpg"))
|
|
167
|
+
response = client.system_one(
|
|
168
|
+
state: { claim: "The mug arrived with a chipped rim.", photo: photo },
|
|
169
|
+
questions: {
|
|
170
|
+
damaged: Typesafe::SDK.noul("Is the product in the photo damaged?"),
|
|
171
|
+
severity: Typesafe::SDK.score("How severe is the damage?", %w[none cosmetic unusable]),
|
|
172
|
+
},
|
|
173
|
+
)
|
|
174
|
+
puts response[:damaged].noul, response[:severity].score
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Write the image into text with `with_assets`.** Ruby has no tagged template literals, so pass
|
|
178
|
+
the text and images as separate arguments, in order; other values are converted with `to_s`.
|
|
179
|
+
`join_assets` joins a list like `Array#join`, with `","` as the default separator, as in
|
|
180
|
+
JavaScript's `withAssets.join`:
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
upload = Typesafe::SDK.image(params[:photo].read)
|
|
184
|
+
catalog = Typesafe::SDK.image(Pathname("catalog/mug.png"))
|
|
185
|
+
uploads = params[:photos].map { |file| Typesafe::SDK.image(file.read) }
|
|
186
|
+
|
|
187
|
+
response = client.system_one(
|
|
188
|
+
state: Typesafe::SDK.with_assets("The seller uploaded ", Typesafe::SDK.join_assets(uploads, ", "), "."),
|
|
189
|
+
questions: {
|
|
190
|
+
same_product: Typesafe::SDK.noul(Typesafe::SDK.with_assets("Does ", upload, " show the product in ", catalog, "?")),
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Each image is sent once, however many times it is used. Plain string interpolation, such as
|
|
196
|
+
`"Compare #{upload}"`, writes only the image's reference, so the call raises `TypeSafeError`
|
|
197
|
+
before the request is sent.
|
|
198
|
+
|
|
199
|
+
**Choose your own references with `assets:`.** Pass images keyed by names of your choice, then
|
|
200
|
+
use those names in plain strings, such as prompts stored in configuration:
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
response = client.system_one(
|
|
204
|
+
state: "A customer returned <delivered>. The catalog photo is <catalog>.",
|
|
205
|
+
questions: { matches: Typesafe::SDK.noul("Is <delivered> the product shown in <catalog>?") },
|
|
206
|
+
assets: {
|
|
207
|
+
"<delivered>" => Typesafe::SDK.image(File.binread("returns/RMA-1042.jpg")),
|
|
208
|
+
"<catalog>" => Typesafe::SDK.image(Pathname("catalog/mug.png")),
|
|
209
|
+
},
|
|
210
|
+
)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Use distinct delimiters, such as `<delivered>`, so names don't match ordinary text. An image in
|
|
214
|
+
`assets:` that also appears in content or `with_assets` text is written there under its name.
|
|
215
|
+
Every name in `assets:` must appear in the request; otherwise the call raises `TypeSafeError`
|
|
216
|
+
before sending.
|
|
217
|
+
|
|
218
|
+
**Locate something with `box`.** A box question locates what its instructions describe. The
|
|
219
|
+
answer's `box` holds the edges as fractions of the image's height (`top`, `bottom`) and width
|
|
220
|
+
(`left`, `right`). Its `confidence` is `nil` when the API doesn't report one. Use the
|
|
221
|
+
`jev-preview` model for box questions:
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
response = client.system_one(
|
|
225
|
+
state: Typesafe::SDK.image(Pathname("returns/RMA-1042.jpg")),
|
|
226
|
+
questions: { damage: Typesafe::SDK.box("The damaged area of the product") },
|
|
227
|
+
model: "jev-preview",
|
|
228
|
+
)
|
|
229
|
+
box = response[:damage].box
|
|
230
|
+
puts [box.top, box.bottom, box.left, box.right].inspect
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Requests with images are sent as `multipart/form-data`, with each image's file bytes in its own part.
|
|
234
|
+
|
|
138
235
|
## Using confidence
|
|
139
236
|
|
|
140
237
|
Choice and Score include confidence derived from their probability distributions. Confidence
|
|
@@ -162,6 +259,7 @@ construction. Error messages do not include the key.
|
|
|
162
259
|
| `base_url:` | `TYPESAFE_BASE_URL` | `https://api.typesafe.ai` |
|
|
163
260
|
| `default_model:` | `TYPESAFE_DEFAULT_MODEL` | `jev-latest` |
|
|
164
261
|
| `log_level:` | `TYPESAFE_LOG_LEVEL` | `warn` |
|
|
262
|
+
| `compression:` | None | `:zstd` when `zstd-ruby` is installed, otherwise `false` |
|
|
165
263
|
|
|
166
264
|
```ruby
|
|
167
265
|
client = Typesafe::SDK::Client.new(
|
|
@@ -172,6 +270,19 @@ client = Typesafe::SDK::Client.new(
|
|
|
172
270
|
)
|
|
173
271
|
```
|
|
174
272
|
|
|
273
|
+
### Request compression
|
|
274
|
+
|
|
275
|
+
Added in Ruby **0.7.0**. When the optional [`zstd-ruby`](https://rubygems.org/gems/zstd-ruby) gem
|
|
276
|
+
is available (add `gem "zstd-ruby"` to your Gemfile), the client compresses request bodies of
|
|
277
|
+
1 KiB or more with zstd, which reduces upload size and time, especially for large states and
|
|
278
|
+
images. Smaller bodies are sent uncompressed. Without the gem, all bodies are sent uncompressed;
|
|
279
|
+
`compression: :zstd` then raises `TypeSafeError`. Pass `compression: false` to send all bodies
|
|
280
|
+
uncompressed, for example to an API that doesn't accept compressed requests:
|
|
281
|
+
|
|
282
|
+
```ruby
|
|
283
|
+
client = Typesafe::SDK::Client.new(compression: false)
|
|
284
|
+
```
|
|
285
|
+
|
|
175
286
|
`jev-latest` is a moving alias. For reproducible deployments, select an explicit model from
|
|
176
287
|
`client.models.list` and set `default_model:` or a per-call `model:`. See
|
|
177
288
|
[available models and aliases](https://docs.typesafe.ai/models).
|
|
@@ -239,6 +350,10 @@ Errors reach your code after any configured retries. All SDK errors inherit from
|
|
|
239
350
|
| `APITimeoutError` | Subclass of `APIConnectionError`; exposes `timeout_ms` |
|
|
240
351
|
| `APIUserAbortError` | Caller cancellation; never retried |
|
|
241
352
|
|
|
353
|
+
From Ruby 0.7.0, when an HTML page from Cloudflare answers instead of the API, for example because
|
|
354
|
+
its firewall matched text such as a shell command in `state`, the error message says so and
|
|
355
|
+
includes the `cf-ray` ID to report to TypeSafe support. The error class and `body` are unchanged.
|
|
356
|
+
|
|
242
357
|
## Response metadata and additional fields
|
|
243
358
|
|
|
244
359
|
Added for Ruby **0.6.0.1**:
|
|
@@ -292,7 +407,9 @@ end
|
|
|
292
407
|
|
|
293
408
|
The default logger writes to `$stderr` with a `[typesafe-ai]` prefix. `info` includes request
|
|
294
409
|
summaries; `debug` adds redacted credential headers and request/response bodies. Bodies may
|
|
295
|
-
contain application data and are **not** redacted.
|
|
410
|
+
contain application data and are **not** redacted. Image bytes are not logged; requests show each
|
|
411
|
+
image's multipart part name. From Ruby 0.7.0, `x-api-key` values are always fully masked, and
|
|
412
|
+
authorization headers keep only an alphabetic scheme, such as `Bearer`, that a credential follows.
|
|
296
413
|
|
|
297
414
|
From Ruby 0.6.0.1, standard Ruby `Logger` and `Rails.logger` also accept debug details:
|
|
298
415
|
|
|
@@ -316,6 +433,10 @@ application if your Ruby version does not provide it by default.
|
|
|
316
433
|
| `retry: { maxRetries: 0 }` | `retry_policy: { max_retries: 0 }` |
|
|
317
434
|
| `.withResponse()` | `with_response: true` (Ruby 0.6.0.1+) |
|
|
318
435
|
| `AbortController` | `Typesafe::SDK::Signal` |
|
|
436
|
+
| `image(blob)`, `Image.fromBase64(text)` | `Typesafe::SDK.image(bytes_or_pathname)`, `Typesafe::SDK::Image.from_base64(text)` |
|
|
437
|
+
| `` withAssets`Compare ${a} with ${b}` `` | `Typesafe::SDK.with_assets("Compare ", a, " with ", b)` |
|
|
438
|
+
| `withAssets.join(images, ", ")` | `Typesafe::SDK.join_assets(images, ", ")` |
|
|
439
|
+
| `compression: false` | `compression: false` |
|
|
319
440
|
| `answers.category` | `response[:category]` or `response.answers["category"]` |
|
|
320
441
|
|
|
321
442
|
Ruby calls are synchronous and return Ruby answer objects. They do not provide TypeScript
|
data/docs/COMPATIBILITY.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# JavaScript SDK compatibility
|
|
2
2
|
|
|
3
|
-
This unofficial Ruby client targets the official `@typesafe-ai/sdk` **0.
|
|
4
|
-
commit [`
|
|
5
|
-
Audit date: 2026-
|
|
3
|
+
This unofficial Ruby client targets the official `@typesafe-ai/sdk` **0.7.0** release,
|
|
4
|
+
commit [`b844cd1aca12855a5d24a2df52a1401c86e7d37a`](https://github.com/typesafe-ai/typesafe-sdk-js/tree/b844cd1aca12855a5d24a2df52a1401c86e7d37a).
|
|
5
|
+
Audit date: 2026-10-16. This is a tested compatibility target, not a claim of endorsement
|
|
6
6
|
or a guarantee about future API changes.
|
|
7
7
|
|
|
8
8
|
## What is covered
|
|
@@ -11,44 +11,72 @@ or a guarantee about future API changes.
|
|
|
11
11
|
| --- | --- |
|
|
12
12
|
| Endpoints | `POST /v1/systemone`, `GET /v1/models` |
|
|
13
13
|
| Defaults | `jev-latest`, 10-second attempt timeout, two retries |
|
|
14
|
-
| Questions | Noul, choice, ordered score criteria; structured instructions/state and nullable choice descriptions |
|
|
14
|
+
| Questions | Noul, choice, ordered score criteria, and box; structured instructions/state and nullable choice descriptions |
|
|
15
|
+
| Images | `image`, `Image.from_base64`, images in content, `with_assets`/`join_assets` text, named `assets:`; multipart encoding, part names, content types, and reference validation |
|
|
16
|
+
| Compression | zstd for bodies of 1 KiB or more (UTF-8 bytes), `compression: false`, protected `Content-Encoding`, same bytes on every retry |
|
|
17
|
+
| Serialization | Non-finite floats sent as `"NaN"`, `"Infinity"`, `"-Infinity"`; the request is copied when the call starts |
|
|
15
18
|
| Configuration | Explicit settings override trimmed environment values |
|
|
16
19
|
| Retries | 408, 429, 500–599; connection and timeout failures independently configurable |
|
|
17
20
|
| Server delays | Fractional milliseconds/seconds, HTTP dates, maximum delay and jitter |
|
|
18
21
|
| Headers | Case-insensitive overrides; protected auth, content type, SDK and retry headers |
|
|
19
22
|
| Errors | HTTP subclasses, validation messages, request IDs, connection/timeout/cancellation errors |
|
|
20
23
|
| 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 |
|
|
24
|
+
| Results | Typed answers (including box answers and nullable usage), `to_h` with unmodeled fields; model cards; `with_response: true` for HTTP metadata |
|
|
22
25
|
| Extension fields | `extra_body:` forwards additional JSON fields without overriding named arguments |
|
|
23
26
|
|
|
24
|
-
The checked-in `spec/fixtures/js-0.
|
|
27
|
+
The checked-in `spec/fixtures/js-0.7.0.json` was generated by executing the published
|
|
25
28
|
JavaScript package, rather than by copying expected output from the Ruby implementation.
|
|
26
|
-
`parity_spec.rb` compares question JSON, request payloads
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
`parity_spec.rb` compares question JSON, request payloads (including non-finite numbers),
|
|
30
|
+
multipart requests and parts with images in content, text, and named assets, `withAssets.join`
|
|
31
|
+
text, asset validation messages, base64 decoding, compression thresholds, metadata, error
|
|
32
|
+
mapping/messages, complete answer fields (including box answers, fractional scores, and string
|
|
33
|
+
rubric keys), retry header parsing, and nullable retry flags. Image references are random, so
|
|
34
|
+
the fixture replaces them with stable labels. `http_spec.rb` uses real loopback sockets to exercise behavior
|
|
29
35
|
that mocked HTTP adapters cannot verify. Other specs cover Ruby configuration and logging.
|
|
30
36
|
Known upstream bugs are corrected explicitly in the comparisons; the fixtures retain the
|
|
31
37
|
original JS behavior for those cases.
|
|
32
38
|
See the [issue-by-issue audit](UPSTREAM_ISSUES.md) for every upstream issue reviewed.
|
|
33
39
|
|
|
34
|
-
To regenerate the fixtures,
|
|
35
|
-
|
|
40
|
+
To regenerate the fixtures with Node.js 22.15 or newer, unpack the official package
|
|
41
|
+
(`npm pack @typesafe-ai/sdk@0.7.0`, or the asset from the
|
|
42
|
+
[GitHub release](https://github.com/typesafe-ai/typesafe-sdk-js/releases/tag/v0.7.0)) and run:
|
|
36
43
|
|
|
37
44
|
```sh
|
|
38
|
-
node script/generate_parity_fixtures.cjs /path/to/package > spec/fixtures/js-0.
|
|
45
|
+
node script/generate_parity_fixtures.cjs /path/to/package > spec/fixtures/js-0.7.0.json
|
|
39
46
|
bundle exec rake
|
|
40
47
|
```
|
|
41
48
|
|
|
42
49
|
## Intentional Ruby differences and limits
|
|
43
50
|
|
|
44
|
-
- Ruby
|
|
51
|
+
- Images: Ruby accepts a binary `String`, a `Pathname` (read when the request is sent, like a
|
|
52
|
+
JavaScript `Blob`), or an IO (read immediately, since it may not be readable twice). Ruby has
|
|
53
|
+
no tagged template literals, so `with_assets(*parts)` joins text and images given as separate
|
|
54
|
+
arguments, converting other values with `to_s` (`nil` becomes `""`, as in interpolation; the
|
|
55
|
+
JavaScript template writes `null`). A Hash or Array part is written with `to_s`, which includes
|
|
56
|
+
the references of images inside it, so those images are attached; JavaScript writes
|
|
57
|
+
`[object Object]` and attaches none. `join_assets` defaults to `","`, as `withAssets.join` does.
|
|
58
|
+
- `assets:` must be a Hash of nonempty names to images; Ruby checks this before sending.
|
|
59
|
+
A request containing a cycle raises `TypeSafeError`; JavaScript raises a `TypeError`.
|
|
60
|
+
- Compression needs the optional `zstd-ruby` gem; it is detected once per process, as JS
|
|
61
|
+
detects Node.js zstd support. `compression:` accepts `:zstd`, `"zstd"`, or `false`, and
|
|
62
|
+
`client.compression` returns `:zstd` or `false`.
|
|
63
|
+
- JS 0.7.0 returns rejected promises instead of throwing for invalid requests. Ruby calls are
|
|
64
|
+
synchronous, so invalid requests and options raise `TypeSafeError` from the call, before any
|
|
65
|
+
image is read or request is sent.
|
|
66
|
+
- Ruby keeps its 0.6.0.1 validation messages. JS 0.7.0 adds similar checks for nil state and
|
|
67
|
+
empty noul questions with its own wording, and both now reject non-Hash noul criteria.
|
|
68
|
+
|
|
69
|
+
- Since 0.6.0.1, Ruby validates API keys without echoing their contents and rejects reported
|
|
45
70
|
API-invalid state/question shapes locally. Score levels must be non-nil and number 2–10;
|
|
46
71
|
choice options must number 1–255; noul requires instructions or an outcome description;
|
|
47
|
-
state must be a string, object, or
|
|
72
|
+
state must be a string, object, array, or image; question names cannot be empty.
|
|
48
73
|
Builders and raw question hashes use the same checks. This intentionally tightens the
|
|
49
|
-
|
|
74
|
+
JS 0.7.0 behavior, which still accepts nil score levels and unbounded score/choice sizes; local validation is not a complete server schema validator.
|
|
50
75
|
- Blank retry headers fall back to configured backoff (or another valid delay header).
|
|
51
|
-
JS 0.
|
|
76
|
+
JS 0.7.0 still treats blanks as zero. Literal zero remains valid in Ruby.
|
|
77
|
+
- Debug logs fully mask `x-api-key` and keep only an alphabetic authorization scheme followed
|
|
78
|
+
by a credential (upstream #18, unchanged in JS 0.7.0). Cloudflare HTML error pages get a
|
|
79
|
+
message naming the edge block and `cf-ray` ID (upstream #15).
|
|
52
80
|
- HTTP 402, 409, and 413 have `PaymentRequiredError`, `ConflictError`, and
|
|
53
81
|
`PayloadTooLargeError` subclasses. Existing `APIError` rescue clauses still catch them.
|
|
54
82
|
- Methods/options use snake_case. `timeout:` is in **seconds**; retry settings ending in
|
|
@@ -78,7 +106,8 @@ Offline tests do not prove account authentication, current model availability, b
|
|
|
78
106
|
production inference behavior. No live API key was available during this audit, so no live
|
|
79
107
|
TypeSafe request was made.
|
|
80
108
|
|
|
81
|
-
An explicit smoke test checks model listing and
|
|
109
|
+
An explicit smoke test checks model listing and the noul, choice, and score answer types with
|
|
110
|
+
synthetic input:
|
|
82
111
|
|
|
83
112
|
```sh
|
|
84
113
|
# Set TYPESAFE_API_KEY securely in your environment first.
|
|
@@ -86,4 +115,5 @@ TYPESAFE_LIVE_TEST=1 bundle exec ruby script/smoke_test.rb
|
|
|
86
115
|
```
|
|
87
116
|
|
|
88
117
|
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.
|
|
118
|
+
print the API key, input bodies, or full responses. Add `TYPESAFE_LIVE_IMAGES=1` for an account
|
|
119
|
+
with image access to also send one image with a box question to `jev-preview`.
|
data/docs/UPSTREAM_ISSUES.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Upstream issue audit
|
|
2
2
|
|
|
3
|
-
Reviewed all **
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Reviewed all **17 issues** in the official JavaScript repository's issue list, including their
|
|
4
|
+
comments: the first 11 on **2026-09-21** against `@typesafe-ai/sdk` **0.6.0** (commit
|
|
5
|
+
`66880cc`), with Ruby fixes in **0.6.0.1**, and #15–#20 on **2026-10-16** against **0.7.0**
|
|
6
|
+
(commit `b844cd1aca12855a5d24a2df52a1401c86e7d37a`), with Ruby fixes in **0.7.0**. The issue
|
|
7
|
+
page showed no closed issues; it does not show each issue's state, so the six new issues were
|
|
8
|
+
checked individually and were open.
|
|
7
9
|
|
|
8
10
|
This audit distinguishes client fixes from runtime differences and server feature requests.
|
|
9
11
|
It does not claim that the upstream issues are closed or that every requested API feature
|
|
@@ -14,22 +16,28 @@ known JS bugs is not a compatibility goal.
|
|
|
14
16
|
|
|
15
17
|
| Upstream issue | Ruby disposition | Evidence or remaining limitation |
|
|
16
18
|
| --- | --- | --- |
|
|
19
|
+
| [#20: 0.7.0 fails every request on Bun](https://github.com/typesafe-ai/typesafe-sdk-js/issues/20) | Bun/Fetch-specific; not applicable | The failure is in JS 0.7.0's Fetch body buffering under Bun. Ruby's Net::HTTP transport reads each body once in its own worker; real-socket tests cover body delivery, timeouts, and cancellation. |
|
|
20
|
+
| [#19: custom `fetch` called with the client as `this`](https://github.com/typesafe-ai/typesafe-sdk-js/issues/19) | JavaScript-specific; not applicable | Ruby calls a custom `http:` adapter's `request` method on the adapter itself, so there is no receiver mismatch. |
|
|
21
|
+
| [#18: secrets shown as an authorization scheme in logs](https://github.com/typesafe-ai/typesafe-sdk-js/issues/18) | Fixed locally | Ruby had the same logic. `x-api-key` values are always fully masked; authorization headers keep only an alphabetic scheme followed by a credential. `"sk-live-… x"` and `"TOKEN "` are no longer shown. Unchanged in JS 0.7.0. |
|
|
22
|
+
| [#17: non-finite numbers serialized as null](https://github.com/typesafe-ai/typesafe-sdk-js/issues/17) | Matches JS 0.7.0 | Ruby 0.6.0.1 raised `JSON::GeneratorError`. Like JS 0.7.0, Ruby now sends `"NaN"`, `"Infinity"`, and `"-Infinity"` instead; the issue proposed rejecting them, and Ruby follows the released upstream behavior. |
|
|
23
|
+
| [#16: token admission bounds and billing reconciliation](https://github.com/typesafe-ai/typesafe-sdk-js/issues/16) | Requires upstream API/documentation | There is no official tokenizer, token-count endpoint, or charge export to wrap. Ruby doesn't estimate tokens; `with_response: true` exposes the request ID and raw `usage` for reconciliation. |
|
|
24
|
+
| [#15: Cloudflare HTML 403 for state containing shell commands](https://github.com/typesafe-ai/typesafe-sdk-js/issues/15) | Server-side; clearer Ruby error | The block happens before the API, so no client change can let these requests through. Ruby now names a Cloudflare HTML page and its `cf-ray` ID in the error message instead of quoting HTML. The class stays `PermissionDeniedError`, and the HTML stays in `body`. |
|
|
17
25
|
| [#14: API key disclosure and empty keys](https://github.com/typesafe-ai/typesafe-sdk-js/issues/14) | Fixed locally | Validate explicit and environment keys at construction, trim outer spaces/tabs/line endings, reject empty/non-ASCII/control/embedded-whitespace values, and copy/freeze the key. Validation errors contain no key and make no request or retry. Header-construction failures also propagate without connection retries. |
|
|
18
26
|
| [#13: actionable HTTP error classes](https://github.com/typesafe-ai/typesafe-sdk-js/issues/13) | Implemented | HTTP 402, 409, and 413 map to `PaymentRequiredError`, `ConflictError`, and `PayloadTooLargeError`. Each remains an `APIError`, retains response metadata, and is not retried by default. |
|
|
19
|
-
| [#12: null score levels](https://github.com/typesafe-ai/typesafe-sdk-js/issues/12) | Fixed locally | Builders and raw hashes reject nil levels. Use an explicit empty string for an undescribed position; entries are never removed or renumbered. This workaround is reported accepted in the upstream issue, not independently verified against the live API here. |
|
|
27
|
+
| [#12: null score levels](https://github.com/typesafe-ai/typesafe-sdk-js/issues/12) | Fixed locally; still accepted by JS 0.7.0 | Builders and raw hashes reject nil levels. Use an explicit empty string for an undescribed position; entries are never removed or renumbered. This workaround is reported accepted in the upstream issue, not independently verified against the live API here. |
|
|
20
28
|
| [#11: calibrated multi-label selection](https://github.com/typesafe-ai/typesafe-sdk-js/issues/11) | Requires upstream API/model support | Choice selects one option. Multiple noul questions are independent judgments, not a calibrated competitive multi-label substitute. The Ruby SDK does not invent such an endpoint or promise equivalent calibration. |
|
|
21
29
|
| [#10: cached or registered criteria](https://github.com/typesafe-ai/typesafe-sdk-js/issues/10) | Requires upstream API support | Reusing a Ruby question Hash still sends the criteria on each request and does not reduce billed tokens. There is no supported registration/cache API in the audited JS SDK. No synthetic `cached_tokens` field is exposed. |
|
|
22
|
-
| [#9: blank retry delay headers](https://github.com/typesafe-ai/typesafe-sdk-js/issues/9) | Fixed locally | Blank/whitespace-only delays fall back to configured backoff; blank milliseconds can fall through to valid seconds. Literal zero is still honored. Request-path tests verify the actual retry delay. |
|
|
30
|
+
| [#9: blank retry delay headers](https://github.com/typesafe-ai/typesafe-sdk-js/issues/9) | Fixed locally; unchanged in JS 0.7.0 | Blank/whitespace-only delays fall back to configured backoff; blank milliseconds can fall through to valid seconds. Literal zero is still honored. Request-path tests verify the actual retry delay. |
|
|
23
31
|
| [#8: Node timer overflow](https://github.com/typesafe-ai/typesafe-sdk-js/issues/8) | Node-specific; Ruby equivalent tested | Ruby does not use Node timers. Real loopback requests with delayed responses succeed at and just above Node's 2,147,483,647 ms boundary, for both constructor and per-call settings. This is not a guarantee for every arbitrarily large OS timeout. |
|
|
24
32
|
| [#7: list the community Go SDK](https://github.com/typesafe-ai/typesafe-sdk-js/issues/7) | Upstream documentation request | No Ruby defect. Whether TypeSafe lists third-party clients is an upstream decision; this repository clearly identifies itself as unofficial. |
|
|
25
|
-
| [#6: API-invalid request shapes and limits](https://github.com/typesafe-ai/typesafe-sdk-js/issues/6) | Fixed locally | Require string/object/array state and reject nil; require noul instructions or a non-nil true/false description; enforce 2–10 score levels, 1–255 choice options, and nonempty question names. Empty state containers, single-option choice, and whitespace-only names remain allowed. |
|
|
33
|
+
| [#6: API-invalid request shapes and limits](https://github.com/typesafe-ai/typesafe-sdk-js/issues/6) | Fixed locally; partly in JS 0.7.0 | Require string/object/array/image state and reject nil; require noul instructions or a non-nil true/false description; enforce 2–10 score levels, 1–255 choice options, and nonempty question names. Empty state containers, single-option choice, and whitespace-only names remain allowed. JS 0.7.0 now rejects null state, empty noul questions, and empty or non-map choice criteria, but not the size bounds. |
|
|
26
34
|
| [#4: numeric choice label inference](https://github.com/typesafe-ai/typesafe-sdk-js/issues/4) | TypeScript-specific; Ruby equivalent tested | Ruby has no TypeScript `never` inference. Numeric labels serialize as JSON string keys and choice answers/probability keys remain strings. |
|
|
27
35
|
| [#2: handled cancellation crashes Node](https://github.com/typesafe-ai/typesafe-sdk-js/issues/2) | Different transport; Ruby equivalent tested | The Net::HTTP worker is cleaned up before cancellation returns. Separate processes with `Thread.abort_on_exception = true` catch `APIUserAbortError` and exit normally after cancellation during successful and error response bodies, for inference and model listing. Existing socket tests also exercise cancellation and worker cleanup. |
|
|
28
36
|
|
|
29
37
|
## Compatibility and migration
|
|
30
38
|
|
|
31
39
|
The local validation, blank-header handling, and added error subclasses intentionally differ
|
|
32
|
-
from JS 0.6.0. Previously accepted local inputs that are reported rejected by the API now raise
|
|
40
|
+
from JS 0.6.0, and most still differ from JS 0.7.0. Previously accepted local inputs that are reported rejected by the API now raise
|
|
33
41
|
`TypeSafeError` earlier. In particular, replace `noul()` with an actual question or outcome
|
|
34
42
|
criteria, supply non-nil state, and replace nil score placeholders explicitly with descriptions
|
|
35
43
|
or empty strings. Do not drop score slots to pass validation, because their positions define
|
|
@@ -51,6 +59,8 @@ upstream documentation before relying on newly introduced fields or sending `ext
|
|
|
51
59
|
|
|
52
60
|
- [Issue regressions](../spec/typesafe/sdk/upstream_issues_spec.rb): request validation,
|
|
53
61
|
API key handling, retry scheduling, error mapping, and numeric labels using synthetic data.
|
|
62
|
+
- [0.7.0 regressions](../spec/typesafe/sdk/release_0_7_spec.rb): non-finite numbers (#17),
|
|
63
|
+
credential redaction (#18), Cloudflare error pages (#15), and box/validation changes.
|
|
54
64
|
- [Socket regressions](../spec/typesafe/sdk/http_spec.rb): actual Ruby transport behavior,
|
|
55
65
|
including the Node timer boundary, cancellation, and slow/truncated bodies.
|
|
56
66
|
- [Subprocess cancellation](../spec/typesafe/sdk/cancellation_process_spec.rb): handled
|