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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a081c1995e40528b73408af6b6188d0c9784b497de463b2821694e6a1a895c81
4
- data.tar.gz: 7c7363485285de50ef55a68dfe3f6606e3c0a08f0f41133fbb182010626144bc
3
+ metadata.gz: d9c873fb65592342cbe6b344decd33db540327f8dac873d1ad2c42e1edea58f1
4
+ data.tar.gz: 6d1a977041d8d12fb31b0654e7ffae904170d2d3432951a67751dc30b9aa6d20
5
5
  SHA512:
6
- metadata.gz: 26a637c0d1fc8afb9f29e20ab2860d287d12491c6a93c7c28c2db574adce747abba8c60eae1d70cff7efd3610b08db1f9ea0fdf4d727c58aeaee08cc12c35811
7
- data.tar.gz: 1e9bfee179126a48aa3f640b87149f9af99a8e47c03bb13d29c2d550a520522e98496ceeabd63aa9949fced46f242cc600133c34aab5685b74a23ecfd47a14a5
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: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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.6.0**. Official [JavaScript and Python SDKs](https://docs.typesafe.ai/sdk) are maintained
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.6.0.1**, targeting JS **0.6.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 array; `nil` is rejected, while `""`, `{}`, and `[]` are allowed.
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, even though JS 0.6.0 accepts them locally.
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
@@ -1,8 +1,8 @@
1
1
  # JavaScript SDK compatibility
2
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
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.6.0.json` was generated by executing the published
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, 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
+ `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, 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:
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.6.0.json
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 0.6.0.1 validates API keys without echoing their contents and rejects reported
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 array; question names cannot be empty.
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
- permissive JS 0.6.0 behavior; local validation is not a complete server schema validator.
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.6.0 incorrectly treats blanks as zero. Literal zero remains valid in Ruby.
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 all three answer types with synthetic input:
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`.
@@ -1,9 +1,11 @@
1
1
  # Upstream issue audit
2
2
 
3
- Reviewed all **11 issues** returned by the official JavaScript repository's all-state issue
4
- list on **2026-09-21**, including their comments. All were open at review time. The baseline
5
- is `@typesafe-ai/sdk` **0.6.0**, commit `66880ccded6cb642dc1809620c2b108c33730214`.
6
- Ruby changes below are included in **0.6.0.1**.
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