Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Changelog

## [1.6.1] - 2026-10-05

### Changed

- Throw `SeclaiError` from the list methods of version-gated endpoints when a successful response is not a list — an error-shaped object, text, or an empty body. Most of them handed that body back as if it were the list; the cloud-drive listings returned `[]`, which reads as "no results". An explicit `data: null` is still an empty list

### Fixed

- Return the declared type from every list method once `apiVersion` is `2026-07-27` or later. The API then answers list endpoints with `{data, pagination}`, and the methods that cast the body handed that object back as their array or keyed type: `getAgentCallers()`, `listModels()`, `listInboundEmailRejections()`, `listGovernanceAiConversations()` and `listSolutionConversations()` returned an object instead of an array, and `listAgentEmailOptOuts()`, `listBlockedEmailSenders()`, `setAutoBlockMode()`, `listAlertConfigs()`, `listOrganizationAlertPreferences()`, `listEmailDomains()`, `listKnowledgeBases()`, `listMemoryBanks()`, `getGenerationTiers()`, `listModelAlerts()` and `listExperiments()` lost their `items`, `configs`, `preferences`, `domains`, `knowledge_bases`, `memory_banks`, `tiers`, `alerts` or `experiments` key. The items are now where each type declares them on both shapes, and `data` and `pagination` stay on the object types, which declare them as optional fields ([#14](https://github.com/seclai/seclai-javascript/issues/14))
- Fill the flat `total`, `page` and `limit` from `pagination` when `apiVersion` is `2026-07-27` or later, on `listEvaluationResults()`, `listAgentEvaluationResults()`, `listRunEvaluationResults()`, `listEvaluationRuns()`, `listCompatibleRuns()`, `listKnowledgeBases()`, `listMemoryBanks()` and the `total` of the keyed listings above. They were `undefined` although several of those types declare them required ([#14](https://github.com/seclai/seclai-javascript/issues/14))
- Return an array from `listMemoryBankTemplates()` and `getAgentsUsingMemoryBank()` when `apiVersion` is `2026-07-27` or later, as they do by default. Both are typed `unknown` and returned the `{data, pagination}` object; code that worked around it by reading `.data` must now read the array itself ([#14](https://github.com/seclai/seclai-javascript/issues/14))
- Reject an unknown `Seclai-Version` passed in the per-request `headers` of `request()` or `requestRaw()` with `SeclaiConfigurationError`, in any letter case. It was sent unchecked, bypassing the guard on `apiVersion`; a caller who relied on that to send a version this release does not know must now set `allowUnknownApiVersion` ([#15](https://github.com/seclai/seclai-javascript/issues/15))
- Reject an empty `Seclai-Version` in `defaultHeaders` or per-request `headers`. It passed the guard and replaced the configured version with an empty header ([#15](https://github.com/seclai/seclai-javascript/issues/15))
- Send exactly one value per header on the plain, download, upload and streaming paths. A default or per-request header that differed only in case from another layer's was sent beside it and joined by `fetch`: a default `X-API-Key` went out as `other, real`, a default `Authorization` beside the bearer token, and a per-request `Content-Type` could not replace the JSON one. Which layer wins is unchanged ([#15](https://github.com/seclai/seclai-javascript/issues/15))
- Keep the multipart boundary on uploads when `defaultHeaders` sets a content type in any case other than `content-type` or `Content-Type` ([#15](https://github.com/seclai/seclai-javascript/issues/15))
- Correct the README's API-versioning section: `2026-08-03` also rejects a non-zero `max_age_days` on `updateMemoryBank()`; `2026-09-30` breaks code that parses a run's or step's `output` as a JSON manifest; and `SeclaiApiVersion.Latest` moves with each SDK release — `1.6.0` moved it across that `2026-09-30` change — so pin a dated constant to keep behaviour fixed

## [1.6.0] - 2026-10-04

### Changed
Expand Down Expand Up @@ -206,6 +223,7 @@ _Stable release. No functional changes since 0.0.1._

_Initial release._

[1.6.1]: https://github.com/seclai/seclai-javascript/releases/tag/1.6.1
[1.6.0]: https://github.com/seclai/seclai-javascript/releases/tag/1.6.0
[1.5.0]: https://github.com/seclai/seclai-javascript/releases/tag/1.5.0
[1.4.0]: https://github.com/seclai/seclai-javascript/releases/tag/1.4.0
Expand Down
70 changes: 44 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,48 +131,66 @@ Known versions are on `SeclaiApiVersion` (`V2026_07_01` through `V2026_10_03`,
plus `Default` and `Latest`), imported from `@seclai/sdk`. A version this release was
**not** built against throws at construction: a newer version can reshape
responses, and this client would decode them incorrectly rather than reject them.
Upgrade the package to adopt a new version, or set `allowUnknownApiVersion` if
you have to move first and accept that risk.
The same check applies to a `Seclai-Version` set through `defaultHeaders` or the
per-request `headers` of `request()` / `requestRaw()`, in any letter case, and an
empty value is rejected. Upgrade the package to adopt a new version, or set
`allowUnknownApiVersion` if you have to move first and accept that risk.

The guard only covers the header. An account pinned server-side can still be
newer than this release — `getApiVersion()` reports the `effective_version` the
request resolved to, and comparing it against `SeclaiApiVersion.Latest` is how
you detect the gap.

**What `2026-07-27` changes.** Undeclared query parameters become a 422 instead
of being ignored, and list endpoints move to the canonical `{data, pagination}`
envelope. The affected methods read both shapes, so they keep working either way
— but the metadata moves:
of being ignored, and every list endpoint that answered with a bare array or
under a per-resource key moves to the canonical `{data, pagination}` envelope.
The methods for those endpoints return their declared type on either shape, so
code written against the default still reads the result after you opt in. Four
of them return fewer rows once you do, covered below the table:

| Method | Before | From 2026-07-27 |
| Declared return | Methods | From 2026-07-27 |
| --- | --- | --- |
| `listEvaluationCriteriaPage()` | bare array | `data` + `pagination` |
| `listRunEvaluationResults()` | bare array | `data` + `pagination` |
| `listAlertConfigs()` | `configs` + `total` | `data` + `pagination` |
| `listModelAlerts()` | `alerts` + `total` | `data` + `pagination` |

Prefer `pagination` over the flat `total`/`page`/`limit` properties, and read the
last two with `res.data ?? res.configs` / `res.data ?? res.alerts`. The legacy
keys will be deprecated and then removed once the canonical envelope is the
default.

The cloud-drive listings (`listCloudDriveProviders()`, `listCloudDrives()`,
`getAgentsUsingCloudDrive()`, `listCloudDriveRejections()`) follow the same rule
and return the items as an array on either shape. `listEmbeddingModels()` and
`listRerankerModels()` move their list from `models` to `data`; both methods
populate `models` on either shape, with the defaults and pricing beside it.

**Later versions.** Each is cumulative, and none changes a response shape this
client decodes:
| An array | `listEvaluationCriteria()`, `getAgentCallers()`, `listInboundEmailRejections()`, `listGovernanceAiConversations()`, `listSolutionConversations()`, `listModels()`, `listMemoryBankTemplates()`, `getAgentsUsingMemoryBank()`, `listCloudDriveProviders()`, `listCloudDrives()`, `getAgentsUsingCloudDrive()`, `listCloudDriveRejections()` | Still the array of items; the page metadata is not returned |
| `data`, bare array by default | `listEvaluationCriteriaPage()`, `listRunEvaluationResults()` | `data`, plus `pagination` |
| `data` with flat `total`/`page`/`limit` | `listEvaluationResults()`, `listAgentEvaluationResults()`, `listEvaluationRuns()`, `listCompatibleRuns()` | Unchanged, plus `pagination` |
| A per-resource key | `listAgentEmailOptOuts()` and `listBlockedEmailSenders()` / `setAutoBlockMode()` (`items`), `listAlertConfigs()` (`configs`), `listOrganizationAlertPreferences()` (`preferences`), `listEmailDomains()` (`domains`), `listKnowledgeBases()` (`knowledge_bases`), `listMemoryBanks()` (`memory_banks`), `getGenerationTiers()` (`tiers`), `listModelAlerts()` (`alerts`), `listExperiments()` (`experiments`), `listEmbeddingModels()` and `listRerankerModels()` (`models`) | The same key and any flat `total`/`page`/`limit`, plus `data` and `pagination` |

Where a type declares flat `total`, `page` or `limit`, the client fills them
from `pagination` after you opt in. `listRunEvaluationResults()` has no counters
on its default bare array, and gains them with `pagination`. Fields that sit
beside a list, such as `auto_block_mode` or the email-domain plan capabilities,
are present on both shapes.

Opting in also turns paging on for endpoints that returned everything by
default, so the same call can return fewer rows:

- `listEvaluationCriteria()` and `listRunEvaluationResults()` return every item
by default and one page (20 unless you pass `limit`) after you opt in. The
array from `listEvaluationCriteria()` carries no sign of that; use
`listEvaluationCriteriaPage()` to see `pagination`.
- `listAlertConfigs()` ignores `page` and `limit` by default and returns every
config; after you opt in it returns one page.
- `setAutoBlockMode()` reports the account's full `total` by default, and the
number of rows it returned after you opt in.

**Later versions.** Each is cumulative. None changes a response shape this
client decodes, but `2026-09-30` changes what a string you may be parsing
contains:

| Version | What it changes |
| --- | --- |
| `2026-08-03` | `createMemoryBank()` rejects `max_age_days` with a 400, and an omitted `retention_days` resolves per bank type instead of to 30 |
| `2026-08-03` | `createMemoryBank()` and `updateMemoryBank()` reject a non-zero `max_age_days` with a 400, and a memory bank's `max_age_days` reads as `null`. On create, an omitted `retention_days` resolves per bank type instead of to 30 |
| `2026-08-21` | `createSource()` rejects an embedding dimension its embedder does not support with a 400 — `listEmbeddingModels()` reports the supported ones |
| `2026-09-28` | Agent-definition writes use the current file-list grammar: an omitted `attachments` keeps the stored list and `[]` means no files |
| `2026-09-30` | A run's and a step's `output`, and a step's `input`, are the text rather than a JSON manifest; files are in `attachments` on every version |
| `2026-09-30` | **Breaks code that parses `output`.** A run's and a step's `output`, and a step's `input`, are the plain text; below this version an output that has files is a JSON manifest string (`{schema, text, attachments}`). Read files from `attachments`, which is populated on every version |
| `2026-10-03` | A new LLM step written without `attachments` takes its parent's files, and a new retrieval step's matched media are its files |

**`Latest` moves with the SDK.** `SeclaiApiVersion.Latest` is the newest version
the installed release knows, so upgrading the package can opt a client that
passes it into every version added since — `1.6.0` moved it from `2026-07-27` to
`2026-10-03`, across the `2026-09-30` output change. Pass a dated constant such
as `SeclaiApiVersion.V2026_07_27` to keep behaviour fixed across upgrades.

## Resources

### Identity
Expand Down
Loading
Loading